@@ -17,6 +17,14 @@ final class WriteFreelyModel: ObservableObject { | |||||
@Published var hasError: Bool = false | @Published var hasError: Bool = false | ||||
var currentError: Error? { | var currentError: Error? { | ||||
didSet { | didSet { | ||||
if let localizedErrorDescription = currentError?.localizedDescription, | |||||
localizedErrorDescription == "The operation couldn’t be completed. (WriteFreely.WFError error -2.)", | |||||
!hasNetworkConnection { | |||||
#if DEBUG | |||||
print("⚠️ currentError is WriteFreely.WFError -2 and there is no network connection.") | |||||
#endif | |||||
currentError = NetworkError.noConnectionError | |||||
} | |||||
#if DEBUG | #if DEBUG | ||||
print("⚠️ currentError -> didSet \(currentError?.localizedDescription ?? "nil")") | print("⚠️ currentError -> didSet \(currentError?.localizedDescription ?? "nil")") | ||||
print(" > hasError was: \(self.hasError)") | print(" > hasError was: \(self.hasError)") | ||||
@@ -66,6 +74,10 @@ final class WriteFreelyModel: ObservableObject { | |||||
self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) | self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) | ||||
self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) | self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) | ||||
self.account.restoreState() | self.account.restoreState() | ||||
// Set the appearance | |||||
self.preferences.updateAppearance(to: Appearance(rawValue: self.preferences.appearance) ?? .system) | |||||
if self.account.isLoggedIn { | if self.account.isLoggedIn { | ||||
guard let serverURL = URL(string: self.account.server) else { | guard let serverURL = URL(string: self.account.server) else { | ||||
self.currentError = AccountError.invalidServerURL | self.currentError = AccountError.invalidServerURL | ||||
@@ -59,14 +59,7 @@ struct ContentView: View { | |||||
.withErrorHandling() | .withErrorHandling() | ||||
#endif | #endif | ||||
#if os(macOS) | |||||
Text("Select a post, or create a new local draft.") | |||||
.foregroundColor(.secondary) | |||||
.frame(width: 500, height: 500) | |||||
#else | |||||
Text("Select a post, or create a new local draft.") | |||||
.foregroundColor(.secondary) | |||||
#endif | |||||
NoSelectedPostView(isConnected: $model.hasNetworkConnection) | |||||
} | } | ||||
.environmentObject(model) | .environmentObject(model) | ||||
.onChange(of: model.hasError) { value in | .onChange(of: model.hasError) { value in | ||||
@@ -0,0 +1,29 @@ | |||||
import SwiftUI | |||||
struct NoSelectedPostView: View { | |||||
@Binding var isConnected: Bool | |||||
var body: some View { | |||||
VStack(spacing: 8) { | |||||
Text("Select a post, or create a new local draft.") | |||||
if !isConnected { | |||||
Label("You are not connected to the internet", systemImage: "wifi.exclamationmark") | |||||
.font(.caption) | |||||
.foregroundColor(.secondary) | |||||
} | |||||
} | |||||
.frame(width: 500, height: 500) | |||||
} | |||||
} | |||||
struct NoSelectedPostViewIsDisconnected_Previews: PreviewProvider { | |||||
static var previews: some View { | |||||
NoSelectedPostView(isConnected: Binding.constant(true)) | |||||
} | |||||
} | |||||
struct NoSelectedPostViewIsConnected_Previews: PreviewProvider { | |||||
static var previews: some View { | |||||
NoSelectedPostView(isConnected: Binding.constant(false)) | |||||
} | |||||
} |
@@ -97,19 +97,26 @@ struct PostListView: View { | |||||
.padding(.vertical, 4) | .padding(.vertical, 4) | ||||
.padding(.horizontal, 8) | .padding(.horizontal, 8) | ||||
} else { | } else { | ||||
Button(action: { | |||||
DispatchQueue.main.async { | |||||
model.fetchUserCollections() | |||||
model.fetchUserPosts() | |||||
} | |||||
}, label: { | |||||
Image(systemName: "arrow.clockwise") | |||||
if model.hasNetworkConnection { | |||||
Button(action: { | |||||
DispatchQueue.main.async { | |||||
model.fetchUserCollections() | |||||
model.fetchUserPosts() | |||||
} | |||||
}, label: { | |||||
Image(systemName: "arrow.clockwise") | |||||
.padding(.vertical, 4) | |||||
.padding(.horizontal, 8) | |||||
}) | |||||
.accessibilityLabel(Text("Refresh Posts")) | |||||
.accessibilityHint(Text("Fetch changes from the server")) | |||||
.disabled(!model.account.isLoggedIn) | |||||
} else { | |||||
Image(systemName: "wifi.exclamationmark") | |||||
.padding(.vertical, 4) | .padding(.vertical, 4) | ||||
.padding(.horizontal, 8) | .padding(.horizontal, 8) | ||||
}) | |||||
.accessibilityLabel(Text("Refresh Posts")) | |||||
.accessibilityHint(Text("Fetch changes from the server")) | |||||
.disabled(!model.account.isLoggedIn) | |||||
.foregroundColor(.secondary) | |||||
} | |||||
} | } | ||||
} | } | ||||
.padding(.top, 8) | .padding(.top, 8) | ||||
@@ -1,67 +1,54 @@ | |||||
import SwiftUI | import SwiftUI | ||||
enum Appearance: Int { | |||||
case system = 0 | |||||
case light = 1 | |||||
case dark = 2 | |||||
} | |||||
class PreferencesModel: ObservableObject { | class PreferencesModel: ObservableObject { | ||||
private let defaults = UserDefaults.shared | private let defaults = UserDefaults.shared | ||||
/* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, | |||||
* because setting the .preferredColorScheme modifier on views in SwiftUI is | |||||
* currently unreliable. | |||||
* | |||||
* Feedback submitted to Apple: | |||||
* | |||||
* FB8382883: "On macOS 11β4, preferredColorScheme modifier does not respect .light ColorScheme" | |||||
* FB8383053: "On iOS 14β4/macOS 11β4, it is not possible to unset preferredColorScheme after setting | |||||
* it to either .light or .dark" | |||||
*/ | |||||
#if os(iOS) | |||||
@available(iOSApplicationExtension, unavailable) | |||||
var window: UIWindow? { | |||||
guard let scene = UIApplication.shared.connectedScenes.first, | |||||
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, | |||||
let window = windowSceneDelegate.window else { | |||||
return nil | |||||
@Published var selectedColorScheme: ColorScheme? | |||||
@Published var appearance: Int = 0 | |||||
@Published var font: Int = 0 { | |||||
didSet { | |||||
defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) | |||||
} | } | ||||
return window | |||||
} | } | ||||
#endif | |||||
@available(iOSApplicationExtension, unavailable) | |||||
@Published var selectedColorScheme: ColorScheme? | |||||
@available(iOSApplicationExtension, unavailable) | @available(iOSApplicationExtension, unavailable) | ||||
@Published var appearance: Int = 0 { | |||||
didSet { | |||||
switch appearance { | |||||
case 1: | |||||
// selectedColorScheme = .light | |||||
#if os(macOS) | |||||
NSApp.appearance = NSAppearance(named: .aqua) | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .light | |||||
#endif | |||||
case 2: | |||||
// selectedColorScheme = .dark | |||||
#if os(macOS) | |||||
NSApp.appearance = NSAppearance(named: .darkAqua) | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .dark | |||||
#endif | |||||
default: | |||||
// selectedColorScheme = .none | |||||
#if os(macOS) | |||||
NSApp.appearance = nil | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .unspecified | |||||
#endif | |||||
func updateAppearance(to appearance: Appearance) { | |||||
#if os(iOS) | |||||
var window: UIWindow? { | |||||
guard let scene = UIApplication.shared.connectedScenes.first, | |||||
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, | |||||
let window = windowSceneDelegate.window else { | |||||
return nil | |||||
} | } | ||||
defaults.set(appearance, forKey: WFDefaults.colorSchemeIntegerKey) | |||||
return window | |||||
} | } | ||||
} | |||||
@Published var font: Int = 0 { | |||||
didSet { | |||||
defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) | |||||
#endif | |||||
switch appearance { | |||||
case .light: | |||||
#if os(macOS) | |||||
NSApp.appearance = NSAppearance(named: .aqua) | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .light | |||||
#endif | |||||
case .dark: | |||||
#if os(macOS) | |||||
NSApp.appearance = NSAppearance(named: .darkAqua) | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .dark | |||||
#endif | |||||
default: | |||||
#if os(macOS) | |||||
NSApp.appearance = nil | |||||
#else | |||||
window?.overrideUserInterfaceStyle = .unspecified | |||||
#endif | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -3,6 +3,28 @@ import SwiftUI | |||||
struct PreferencesView: View { | struct PreferencesView: View { | ||||
@ObservedObject var preferences: PreferencesModel | @ObservedObject var preferences: PreferencesModel | ||||
/* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, | |||||
* because setting the .preferredColorScheme modifier on views in SwiftUI is | |||||
* currently unreliable. | |||||
* | |||||
* Feedback submitted to Apple: | |||||
* | |||||
* FB8382883: "On macOS 11β4, preferredColorScheme modifier does not respect .light ColorScheme" | |||||
* FB8383053: "On iOS 14β4/macOS 11β4, it is not possible to unset preferredColorScheme after setting | |||||
* it to either .light or .dark" | |||||
*/ | |||||
#if os(iOS) | |||||
var window: UIWindow? { | |||||
guard let scene = UIApplication.shared.connectedScenes.first, | |||||
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, | |||||
let window = windowSceneDelegate.window else { | |||||
return nil | |||||
} | |||||
return window | |||||
} | |||||
#endif | |||||
var body: some View { | var body: some View { | ||||
VStack { | VStack { | ||||
VStack { | VStack { | ||||
@@ -46,6 +68,10 @@ struct PreferencesView: View { | |||||
} | } | ||||
.padding(.bottom) | .padding(.bottom) | ||||
} | } | ||||
.onChange(of: preferences.appearance) { value in | |||||
preferences.updateAppearance(to: Appearance(rawValue: value) ?? .system) | |||||
UserDefaults.shared.set(value, forKey: WFDefaults.colorSchemeIntegerKey) | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -136,6 +136,8 @@ | |||||
17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; | 17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; | ||||
17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; | 17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; | ||||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; }; | 17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; }; | ||||
37095AE02AA4A0E700C9C5F8 /* NoSelectedPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */; }; | |||||
37113EF92A98C10A00B36B98 /* NoSelectedPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */; }; | |||||
375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */; }; | 375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */; }; | ||||
3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779389629EC0C880032D6C1 /* HelpCommands.swift */; }; | 3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779389629EC0C880032D6C1 /* HelpCommands.swift */; }; | ||||
37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | 37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | ||||
@@ -270,6 +272,7 @@ | |||||
17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = "<group>"; }; | 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = "<group>"; }; | ||||
17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; }; | 17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; }; | ||||
17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; }; | 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; }; | ||||
37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoSelectedPostView.swift; sourceTree = "<group>"; }; | |||||
375A67E728FC555C007A1AC0 /* MultilineTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextView.swift; sourceTree = "<group>"; }; | 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextView.swift; sourceTree = "<group>"; }; | ||||
3779389629EC0C880032D6C1 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; }; | 3779389629EC0C880032D6C1 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; }; | ||||
37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePostListFilteredView.swift; sourceTree = "<group>"; }; | 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePostListFilteredView.swift; sourceTree = "<group>"; }; | ||||
@@ -586,6 +589,7 @@ | |||||
isa = PBXGroup; | isa = PBXGroup; | ||||
children = ( | children = ( | ||||
17DF328224C87D3300BCE2E3 /* ContentView.swift */, | 17DF328224C87D3300BCE2E3 /* ContentView.swift */, | ||||
37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */, | |||||
); | ); | ||||
path = Navigation; | path = Navigation; | ||||
sourceTree = "<group>"; | sourceTree = "<group>"; | ||||
@@ -922,6 +926,7 @@ | |||||
173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */, | 173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */, | ||||
17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, | 17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, | ||||
17C42E622507D8E600072984 /* PostStatus.swift in Sources */, | 17C42E622507D8E600072984 /* PostStatus.swift in Sources */, | ||||
37095AE02AA4A0E700C9C5F8 /* NoSelectedPostView.swift in Sources */, | |||||
1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, | 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, | ||||
1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */, | 1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */, | ||||
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, | 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, | ||||
@@ -975,6 +980,7 @@ | |||||
17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, | 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, | ||||
17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | ||||
172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */, | 172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */, | ||||
37113EF92A98C10A00B36B98 /* NoSelectedPostView.swift in Sources */, | |||||
1727526728099802003D0A6A /* ErrorConstants.swift in Sources */, | 1727526728099802003D0A6A /* ErrorConstants.swift in Sources */, | ||||
17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */, | 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */, | ||||
17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, | 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, | ||||
@@ -1060,7 +1066,7 @@ | |||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | ||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | ||||
CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
CURRENT_PROJECT_VERSION = 704; | |||||
CURRENT_PROJECT_VERSION = 706; | |||||
DEVELOPMENT_TEAM = TPPAB4YBA6; | DEVELOPMENT_TEAM = TPPAB4YBA6; | ||||
GENERATE_INFOPLIST_FILE = YES; | GENERATE_INFOPLIST_FILE = YES; | ||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | ||||
@@ -1072,7 +1078,7 @@ | |||||
"@executable_path/Frameworks", | "@executable_path/Frameworks", | ||||
"@executable_path/../../Frameworks", | "@executable_path/../../Frameworks", | ||||
); | ); | ||||
MARKETING_VERSION = 1.0.16; | |||||
MARKETING_VERSION = 1.0.17; | |||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; | PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; | ||||
PRODUCT_NAME = "$(TARGET_NAME)"; | PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
SDKROOT = iphoneos; | SDKROOT = iphoneos; | ||||
@@ -1091,7 +1097,7 @@ | |||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | ||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | ||||
CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
CURRENT_PROJECT_VERSION = 704; | |||||
CURRENT_PROJECT_VERSION = 706; | |||||
DEVELOPMENT_TEAM = TPPAB4YBA6; | DEVELOPMENT_TEAM = TPPAB4YBA6; | ||||
GENERATE_INFOPLIST_FILE = YES; | GENERATE_INFOPLIST_FILE = YES; | ||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | ||||
@@ -1103,7 +1109,7 @@ | |||||
"@executable_path/Frameworks", | "@executable_path/Frameworks", | ||||
"@executable_path/../../Frameworks", | "@executable_path/../../Frameworks", | ||||
); | ); | ||||
MARKETING_VERSION = 1.0.16; | |||||
MARKETING_VERSION = 1.0.17; | |||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; | PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; | ||||
PRODUCT_NAME = "$(TARGET_NAME)"; | PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
SDKROOT = iphoneos; | SDKROOT = iphoneos; | ||||
@@ -1234,7 +1240,7 @@ | |||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | ||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; | CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; | ||||
CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
CURRENT_PROJECT_VERSION = 704; | |||||
CURRENT_PROJECT_VERSION = 706; | |||||
DEVELOPMENT_TEAM = TPPAB4YBA6; | DEVELOPMENT_TEAM = TPPAB4YBA6; | ||||
ENABLE_PREVIEWS = YES; | ENABLE_PREVIEWS = YES; | ||||
INFOPLIST_FILE = iOS/Info.plist; | INFOPLIST_FILE = iOS/Info.plist; | ||||
@@ -1243,7 +1249,7 @@ | |||||
"$(inherited)", | "$(inherited)", | ||||
"@executable_path/Frameworks", | "@executable_path/Frameworks", | ||||
); | ); | ||||
MARKETING_VERSION = 1.0.16; | |||||
MARKETING_VERSION = 1.0.17; | |||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; | PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; | ||||
PRODUCT_NAME = "WriteFreely-MultiPlatform"; | PRODUCT_NAME = "WriteFreely-MultiPlatform"; | ||||
SDKROOT = iphoneos; | SDKROOT = iphoneos; | ||||
@@ -1260,7 +1266,7 @@ | |||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | ||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; | CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; | ||||
CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
CURRENT_PROJECT_VERSION = 704; | |||||
CURRENT_PROJECT_VERSION = 706; | |||||
DEVELOPMENT_TEAM = TPPAB4YBA6; | DEVELOPMENT_TEAM = TPPAB4YBA6; | ||||
ENABLE_PREVIEWS = YES; | ENABLE_PREVIEWS = YES; | ||||
INFOPLIST_FILE = iOS/Info.plist; | INFOPLIST_FILE = iOS/Info.plist; | ||||
@@ -1269,7 +1275,7 @@ | |||||
"$(inherited)", | "$(inherited)", | ||||
"@executable_path/Frameworks", | "@executable_path/Frameworks", | ||||
); | ); | ||||
MARKETING_VERSION = 1.0.16; | |||||
MARKETING_VERSION = 1.0.17; | |||||
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; | PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; | ||||
PRODUCT_NAME = "WriteFreely-MultiPlatform"; | PRODUCT_NAME = "WriteFreely-MultiPlatform"; | ||||
SDKROOT = iphoneos; | SDKROOT = iphoneos; | ||||
@@ -50,7 +50,10 @@ struct PostEditorView: View { | |||||
PostEditorStatusToolbarView(post: post) | PostEditorStatusToolbarView(post: post) | ||||
} | } | ||||
ToolbarItem(placement: .primaryAction) { | ToolbarItem(placement: .primaryAction) { | ||||
if model.isProcessingRequest { | |||||
if !model.hasNetworkConnection { | |||||
Image(systemName: "wifi.exclamationmark") | |||||
.foregroundColor(.secondary) | |||||
} else if model.isProcessingRequest { | |||||
ProgressView() | ProgressView() | ||||
} else { | } else { | ||||
Menu(content: { | Menu(content: { | ||||
@@ -115,6 +115,7 @@ final class CustomTextView: NSView { | |||||
let scrollView = NSScrollView() | let scrollView = NSScrollView() | ||||
scrollView.drawsBackground = false | scrollView.drawsBackground = false | ||||
scrollView.borderType = .noBorder | scrollView.borderType = .noBorder | ||||
scrollView.autohidesScrollers = true | |||||
scrollView.hasVerticalScroller = true | scrollView.hasVerticalScroller = true | ||||
scrollView.hasHorizontalRuler = false | scrollView.hasHorizontalRuler = false | ||||
scrollView.autoresizingMask = [.width, .height] | scrollView.autoresizingMask = [.width, .height] | ||||
@@ -167,6 +168,8 @@ final class CustomTextView: NSView { | |||||
.font: font ?? NSFont.systemFont(ofSize: 17), // Fall back to system font if we can't unwrap font argument | .font: font ?? NSFont.systemFont(ofSize: 17), // Fall back to system font if we can't unwrap font argument | ||||
.foregroundColor: NSColor.labelColor | .foregroundColor: NSColor.labelColor | ||||
] | ] | ||||
textView.textContainer?.lineFragmentPadding = 16 | |||||
textView.textContainerInset = NSSize(width: 0, height: 16) | |||||
return textView | return textView | ||||
}() | }() | ||||
@@ -9,65 +9,72 @@ struct PostEditorView: View { | |||||
@State private var updatingFromServer: Bool = false | @State private var updatingFromServer: Bool = false | ||||
var body: some View { | var body: some View { | ||||
PostTextEditingView( | |||||
post: post, | |||||
updatingFromServer: $updatingFromServer | |||||
) | |||||
.padding() | |||||
.background(Color(NSColor.controlBackgroundColor)) | |||||
.onAppear(perform: { | |||||
model.editor.setInitialValues(for: post) | |||||
if post.status != PostStatus.published.rawValue { | |||||
DispatchQueue.main.async { | |||||
self.model.editor.saveLastDraft(post) | |||||
} | |||||
} else { | |||||
self.model.editor.clearLastDraft() | |||||
} | |||||
}) | |||||
.onChange(of: post.hasNewerRemoteCopy, perform: { _ in | |||||
if !post.hasNewerRemoteCopy { | |||||
self.updatingFromServer = true | |||||
} | |||||
}) | |||||
.onChange(of: post.status, perform: { value in | |||||
if value != PostStatus.published.rawValue { | |||||
self.model.editor.saveLastDraft(post) | |||||
} else { | |||||
self.model.editor.clearLastDraft() | |||||
VStack { | |||||
if !model.hasNetworkConnection { | |||||
Label("You are not connected to the internet", systemImage: "wifi.exclamationmark") | |||||
.font(.caption) | |||||
.foregroundColor(.secondary) | |||||
.padding(.top, 8) | |||||
} | } | ||||
DispatchQueue.main.async { | |||||
LocalStorageManager.standard.saveContext() | |||||
} | |||||
}) | |||||
.onChange(of: model.hasError) { value in | |||||
if value { | |||||
if let error = model.currentError { | |||||
self.errorHandling.handle(error: error) | |||||
PostTextEditingView( | |||||
post: post, | |||||
updatingFromServer: $updatingFromServer | |||||
) | |||||
.background(Color(NSColor.controlBackgroundColor)) | |||||
.onAppear(perform: { | |||||
model.editor.setInitialValues(for: post) | |||||
if post.status != PostStatus.published.rawValue { | |||||
DispatchQueue.main.async { | |||||
self.model.editor.saveLastDraft(post) | |||||
} | |||||
} else { | } else { | ||||
self.errorHandling.handle(error: AppError.genericError()) | |||||
self.model.editor.clearLastDraft() | |||||
} | } | ||||
model.hasError = false | |||||
} | |||||
} | |||||
.onDisappear(perform: { | |||||
DispatchQueue.main.async { | |||||
model.editor.clearLastDraft() | |||||
} | |||||
if post.title.count == 0 | |||||
&& post.body.count == 0 | |||||
&& post.status == PostStatus.local.rawValue | |||||
&& post.updatedDate == nil | |||||
&& post.postId == nil { | |||||
DispatchQueue.main.async { | |||||
model.posts.remove(post) | |||||
}) | |||||
.onChange(of: post.hasNewerRemoteCopy, perform: { _ in | |||||
if !post.hasNewerRemoteCopy { | |||||
self.updatingFromServer = true | |||||
} | |||||
}) | |||||
.onChange(of: post.status, perform: { value in | |||||
if value != PostStatus.published.rawValue { | |||||
self.model.editor.saveLastDraft(post) | |||||
} else { | |||||
self.model.editor.clearLastDraft() | |||||
} | } | ||||
} else if post.status != PostStatus.published.rawValue { | |||||
DispatchQueue.main.async { | DispatchQueue.main.async { | ||||
LocalStorageManager.standard.saveContext() | LocalStorageManager.standard.saveContext() | ||||
} | } | ||||
}) | |||||
.onChange(of: model.hasError) { value in | |||||
if value { | |||||
if let error = model.currentError { | |||||
self.errorHandling.handle(error: error) | |||||
} else { | |||||
self.errorHandling.handle(error: AppError.genericError()) | |||||
} | |||||
model.hasError = false | |||||
} | |||||
} | } | ||||
}) | |||||
.onDisappear(perform: { | |||||
DispatchQueue.main.async { | |||||
model.editor.clearLastDraft() | |||||
} | |||||
if post.title.count == 0 | |||||
&& post.body.count == 0 | |||||
&& post.status == PostStatus.local.rawValue | |||||
&& post.updatedDate == nil | |||||
&& post.postId == nil { | |||||
DispatchQueue.main.async { | |||||
model.posts.remove(post) | |||||
} | |||||
} else if post.status != PostStatus.published.rawValue { | |||||
DispatchQueue.main.async { | |||||
LocalStorageManager.standard.saveContext() | |||||
} | |||||
} | |||||
}) | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -15,7 +15,8 @@ struct PostTextEditingView: View { | |||||
if combinedText.count == 0 { | if combinedText.count == 0 { | ||||
Text("Write…") | Text("Write…") | ||||
.foregroundColor(Color(NSColor.placeholderTextColor)) | .foregroundColor(Color(NSColor.placeholderTextColor)) | ||||
.padding(.horizontal, 5) | |||||
.padding(.horizontal, 16) | |||||
.padding(.vertical, 16) | |||||
.font(.custom(appearance.rawValue, size: 17, relativeTo: .body)) | .font(.custom(appearance.rawValue, size: 17, relativeTo: .body)) | ||||
} | } | ||||
if post.appearance == "sans" { | if post.appearance == "sans" { | ||||