Skip to content

Commit

Permalink
feat: Synchronize settings via iCloud
Browse files Browse the repository at this point in the history
  • Loading branch information
f-person committed Apr 8, 2023
1 parent 3848b2a commit 99a20eb
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 47 deletions.
71 changes: 59 additions & 12 deletions SharedDefaults/SharedDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,57 @@ import Foundation
public struct SharedDefaults {
public static let appGroupID = "group.dev.fperson.hayatar.shared"

public static func userDefaultsForAppGroup() -> UserDefaults? {
return UserDefaults(suiteName: appGroupID)
public static var localStore: UserDefaults? {
UserDefaults(suiteName: appGroupID)
}

private static var iCloudStore: NSUbiquitousKeyValueStore? {
if FileManager.default.ubiquityIdentityToken != nil {
return NSUbiquitousKeyValueStore.default
} else {
return nil
}
}

public static let enableHapticFeedbackKey = "enableHapticFeedback"
public static let enableAudioFeedbackKey = "enableAudioFeedback"
public static let colonCalloutCharactersKey = "colonCallouts"
public static let commaCalloutCharactersKey = "commaCallouts"

public static let defaultEnableHapticFeedback = true
public static let defaultEnableAudioFeedback = true
public static let defaultColonCalloutCharacters = "։,՞֊՛՝՜"
public static let defaultCommaCalloutCharacters = ",«»—՟()՚"

private static let enableSyncKey = "enableSync"
public static var enableSync: Bool {
set {
if newValue {
syncAllPreferencesToCloud()
}
iCloudStore?.set(newValue, forKey: enableSyncKey)
}
get { iCloudStore?.bool(forKey: enableSyncKey) ?? false }
}

public static var enableHapticFeedback: Bool {
set { userDefaultsForAppGroup()?.set(newValue, forKey: enableHapticFeedbackKey) }
get { userDefaultsForAppGroup()?.object(forKey: enableHapticFeedbackKey) as? Bool ?? defaultEnableHapticFeedback }
set { set(newValue, forKey: enableHapticFeedbackKey) }
get { get(enableHapticFeedbackKey, defaultValue: defaultEnableHapticFeedback) }
}

public static var enableAudioFeedback: Bool {
set { userDefaultsForAppGroup()?.set(newValue, forKey: enableAudioFeedbackKey) }
get { userDefaultsForAppGroup()?.object(forKey: enableAudioFeedbackKey) as? Bool ?? defaultEnableAudioFeedback }
set { set(newValue, forKey: enableAudioFeedbackKey) }
get { get(enableAudioFeedbackKey, defaultValue: defaultEnableAudioFeedback) }
}

public static var colonCalloutCharacters: String {
set { userDefaultsForAppGroup()?.set(newValue, forKey: colonCalloutCharactersKey) }
get { userDefaultsForAppGroup()?.string(forKey: colonCalloutCharactersKey) ?? defaultColonCalloutCharacters }
set { set(newValue, forKey: colonCalloutCharactersKey) }
get { get(colonCalloutCharactersKey, defaultValue: defaultColonCalloutCharacters) }
}

public static var commaCalloutCharacters: String {
set { userDefaultsForAppGroup()?.set(newValue, forKey: commaCalloutCharactersKey) }
get { userDefaultsForAppGroup()?.string(forKey: commaCalloutCharactersKey) ?? defaultCommaCalloutCharacters }
set { set(newValue, forKey: commaCalloutCharactersKey) }
get { get(commaCalloutCharactersKey, defaultValue: defaultCommaCalloutCharacters) }
}

public static func resetToDefaults() {
Expand All @@ -50,5 +69,33 @@ public struct SharedDefaults {
colonCalloutCharacters = defaultColonCalloutCharacters
commaCalloutCharacters = defaultCommaCalloutCharacters
}

private static func set<T>(_ value: T, forKey key: String) {
if let store = iCloudStore {
store.set(value, forKey: key)
} else {
localStore?.set(value, forKey: key)
}
}

private static func get<T>(_ key: String, defaultValue: T) -> T {
if let store = iCloudStore {
return store.object(forKey: key) as? T ?? defaultValue
} else {
return localStore?.object(forKey: key) as? T ?? defaultValue
}
}

private static func syncAllPreferencesToCloud() {
guard let iCloudStore = iCloudStore else {
NSLog("Error: iCloud store is not available")
return
}

iCloudStore.set(enableHapticFeedback, forKey: enableHapticFeedbackKey)
iCloudStore.set(enableAudioFeedback, forKey: enableAudioFeedbackKey)
iCloudStore.set(colonCalloutCharacters, forKey: colonCalloutCharactersKey)
iCloudStore.set(commaCalloutCharacters, forKey: commaCalloutCharactersKey)
}
}

12 changes: 8 additions & 4 deletions hayatar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
02485ABE29DFBE2500244E8F /* LabelledTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02485ABD29DFBE2500244E8F /* LabelledTextField.swift */; };
02485AC029DFC06700244E8F /* ResetSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02485ABF29DFC06700244E8F /* ResetSettingsButton.swift */; };
0256824B29DB9D44004BD8D6 /* KeyboardFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0256824A29DB9D44004BD8D6 /* KeyboardFeedback.swift */; };
025FC95A29E0FE09008B7E6B /* SyncSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025FC95929E0FE09008B7E6B /* SyncSettingsView.swift */; };
0288C5F829D3518500AF6638 /* ArmenianCalloutProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0288C5F729D3518500AF6638 /* ArmenianCalloutProvider.swift */; };
02EE058A29D877C200C7752D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 02EE058929D877C200C7752D /* README.md */; };
02EE058C29D8B84500C7752D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EE058B29D8B84500C7752D /* Extensions.swift */; };
Expand Down Expand Up @@ -97,6 +98,7 @@
02485ABD29DFBE2500244E8F /* LabelledTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelledTextField.swift; sourceTree = "<group>"; };
02485ABF29DFC06700244E8F /* ResetSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetSettingsButton.swift; sourceTree = "<group>"; };
0256824A29DB9D44004BD8D6 /* KeyboardFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFeedback.swift; sourceTree = "<group>"; };
025FC95929E0FE09008B7E6B /* SyncSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncSettingsView.swift; sourceTree = "<group>"; };
0288C5F729D3518500AF6638 /* ArmenianCalloutProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArmenianCalloutProvider.swift; sourceTree = "<group>"; };
02D494BB29D5F1C000C1D087 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
02EE058929D877C200C7752D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -165,6 +167,7 @@
022D42E529CE69C6000F71B1 /* Preview Content */,
02485ABD29DFBE2500244E8F /* LabelledTextField.swift */,
02485ABF29DFC06700244E8F /* ResetSettingsButton.swift */,
025FC95929E0FE09008B7E6B /* SyncSettingsView.swift */,
);
path = hayatar;
sourceTree = "<group>";
Expand Down Expand Up @@ -361,6 +364,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
025FC95A29E0FE09008B7E6B /* SyncSettingsView.swift in Sources */,
02485AC029DFC06700244E8F /* ResetSettingsButton.swift in Sources */,
022D42E229CE69C5000F71B1 /* ContentView.swift in Sources */,
022D42E029CE69C5000F71B1 /* hayatarApp.swift in Sources */,
Expand Down Expand Up @@ -534,7 +538,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = hayatar/hayatar.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_ASSET_PATHS = "\"hayatar/Preview Content\"";
DEVELOPMENT_TEAM = 82MV322Q6H;
ENABLE_PREVIEWS = YES;
Expand Down Expand Up @@ -572,7 +576,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = hayatar/hayatar.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_ASSET_PATHS = "\"hayatar/Preview Content\"";
DEVELOPMENT_TEAM = 82MV322Q6H;
ENABLE_PREVIEWS = YES;
Expand Down Expand Up @@ -606,7 +610,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Armenian/Armenian.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = 82MV322Q6H;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Armenian/Info.plist;
Expand All @@ -632,7 +636,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Armenian/Armenian.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = 82MV322Q6H;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Armenian/Info.plist;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
<key>Armenian.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>2</integer>
</dict>
<key>SharedDefaults.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
<integer>1</integer>
</dict>
<key>hayatar.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
Expand Down
60 changes: 36 additions & 24 deletions hayatar/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,59 @@ import SwiftUI
import SharedDefaults

struct ContentView: View {
static private var store: UserDefaults? { SharedDefaults.userDefaultsForAppGroup() }

@AppStorage(SharedDefaults.enableHapticFeedbackKey, store: store)
var enableHapticFeedback = SharedDefaults.enableHapticFeedback

@AppStorage(SharedDefaults.enableAudioFeedbackKey, store: store)
var enableAudioFeedback = SharedDefaults.enableAudioFeedback

@AppStorage(SharedDefaults.commaCalloutCharactersKey, store: store)
var commaCalloutCharacters = SharedDefaults.defaultCommaCalloutCharacters

@AppStorage(SharedDefaults.colonCalloutCharactersKey, store: store)
var colonCalloutCharacters = SharedDefaults.defaultColonCalloutCharacters

@State private var enableHapticFeedback = SharedDefaults.enableHapticFeedback
@State private var enableAudioFeedback = SharedDefaults.enableAudioFeedback
@State private var commaCalloutCharacters = SharedDefaults.commaCalloutCharacters
@State private var colonCalloutCharacters = SharedDefaults.colonCalloutCharacters
@State private var enableSync = SharedDefaults.enableSync

@State private var showResetAlert = false

@State private var showSyncConfirmationAlert = false

var body: some View {
NavigationStack {
Form {
Section("Feedback") {
Toggle(isOn: $enableHapticFeedback) {
Toggle(isOn: Binding(
get: { enableHapticFeedback },
set: { enableHapticFeedback = $0; SharedDefaults.enableHapticFeedback = $0 }
)) {
Text("Haptic Feedback")
}
Toggle(isOn: $enableAudioFeedback) {
Toggle(isOn: Binding(
get: { enableAudioFeedback },
set: { enableAudioFeedback = $0; SharedDefaults.enableAudioFeedback = $0 }
)) {
Text("Input Sound")
}
}

Section("Layout") {
LabelledTextField(title: "Callout characters for \",\"", text: $commaCalloutCharacters)
LabelledTextField(title: "Callout characters for \"։\"", text: $colonCalloutCharacters)
LabelledTextField(title: "Callout characters for \",\"", text: Binding(
get: { commaCalloutCharacters },
set: { commaCalloutCharacters = $0; SharedDefaults.commaCalloutCharacters = $0 }
))
LabelledTextField(title: "Callout characters for \"։\"", text: Binding(
get: { colonCalloutCharacters },
set: { colonCalloutCharacters = $0; SharedDefaults.colonCalloutCharacters = $0 }
))
}

SyncSettingsView(enableSync: $enableSync)

Section {
ResetSettingsButton()
ResetSettingsButton(onReset: {
SharedDefaults.resetToDefaults()

enableHapticFeedback = SharedDefaults.enableHapticFeedback
enableAudioFeedback = SharedDefaults.enableAudioFeedback
commaCalloutCharacters = SharedDefaults.commaCalloutCharacters
colonCalloutCharacters = SharedDefaults.colonCalloutCharacters
})
}
}.navigationTitle("Armenian Keyboard")
}
}

func resetSettings() {
// Your reset settings code goes here
}
}

struct ContentView_Previews: PreviewProvider {
Expand Down
5 changes: 1 addition & 4 deletions hayatar/ResetSettingsButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
import SharedDefaults

struct ResetSettingsButton: View {
let onReset: () -> Void
@State private var showAlert = false

var body: some View {
Expand All @@ -26,8 +27,4 @@ struct ResetSettingsButton: View {
secondaryButton: .cancel())
}
}

func onReset() -> Void {
SharedDefaults.resetToDefaults()
}
}
58 changes: 58 additions & 0 deletions hayatar/SyncSettingsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// SyncSettingsView.swift
// hayatar
//
// Created by arshak ‎ on 08.04.23.
//

import SwiftUI
import SharedDefaults

struct SyncSettingsView: View {
@Binding var enableSync: Bool
@State private var showSyncConfirmationAlert = false
@State private var showErrorAlert = false

var body: some View {
Section("Synchronization") {
Toggle("Sync preferences via iCloud", isOn: $enableSync)
.onChange(of: enableSync) { newValue in
if newValue != SharedDefaults.enableSync {
if FileManager.default.ubiquityIdentityToken != nil {
showSyncConfirmationAlert = true
} else {
enableSync = false
showErrorAlert = true
}
}
}
.alert(isPresented: $showSyncConfirmationAlert) {
Alert(
title: Text("Change Sync Preferences?"),
message: Text("Are you sure you want to \(enableSync ? "enable" : "disable") sync via iCloud?"),
primaryButton: .default(Text("Yes")) {
SharedDefaults.enableSync = enableSync
},
secondaryButton: .cancel(Text("No")) {
enableSync = SharedDefaults.enableSync
}
)
}
.alert(isPresented: $showErrorAlert) {
Alert(
title: Text("iCloud Unavailable"),
message: Text("iCloud is not available on this device. Please log in to your iCloud account and try again."),
dismissButton: .default(Text("OK")) {
showErrorAlert = false
}
)
}
}
}
}

struct SyncSettingsView_Previews: PreviewProvider {
static var previews: some View {
SyncSettingsView(enableSync: .constant(true))
}
}
4 changes: 4 additions & 0 deletions hayatar/hayatar.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.dev.fperson.hayatar.shared</string>
Expand Down

0 comments on commit 99a20eb

Please sign in to comment.