Improve offline experience (#255)

This commit is contained in:
Angelo Stavrow 2023-10-23 17:15:41 -04:00 committed by GitHub
parent 5efdde96cc
commit 3f424b399a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 135 deletions

View File

@ -17,6 +17,14 @@ final class WriteFreelyModel: ObservableObject {
@Published var hasError: Bool = false
var currentError: Error? {
didSet {
if let localizedErrorDescription = currentError?.localizedDescription,
localizedErrorDescription == "The operation couldnt 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
print("⚠️ currentError -> didSet \(currentError?.localizedDescription ?? "nil")")
print(" > hasError was: \(self.hasError)")
@ -66,6 +74,10 @@ final class WriteFreelyModel: ObservableObject {
self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey)
self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey)
self.account.restoreState()
// Set the appearance
self.preferences.updateAppearance(to: Appearance(rawValue: self.preferences.appearance) ?? .system)
if self.account.isLoggedIn {
guard let serverURL = URL(string: self.account.server) else {
self.currentError = AccountError.invalidServerURL

View File

@ -59,14 +59,7 @@ struct ContentView: View {
.withErrorHandling()
#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)
.onChange(of: model.hasError) { value in

View File

@ -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))
}
}

View File

@ -97,6 +97,7 @@ struct PostListView: View {
.padding(.vertical, 4)
.padding(.horizontal, 8)
} else {
if model.hasNetworkConnection {
Button(action: {
DispatchQueue.main.async {
model.fetchUserCollections()
@ -110,6 +111,12 @@ struct PostListView: View {
.accessibilityLabel(Text("Refresh Posts"))
.accessibilityHint(Text("Fetch changes from the server"))
.disabled(!model.account.isLoggedIn)
} else {
Image(systemName: "wifi.exclamationmark")
.padding(.vertical, 4)
.padding(.horizontal, 8)
.foregroundColor(.secondary)
}
}
}
.padding(.top, 8)

View File

@ -1,21 +1,25 @@
import SwiftUI
enum Appearance: Int {
case system = 0
case light = 1
case dark = 2
}
class PreferencesModel: ObservableObject {
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"
*/
@Published var selectedColorScheme: ColorScheme?
@Published var appearance: Int = 0
@Published var font: Int = 0 {
didSet {
defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey)
}
}
#if os(iOS)
@available(iOSApplicationExtension, unavailable)
func updateAppearance(to appearance: Appearance) {
#if os(iOS)
var window: UIWindow? {
guard let scene = UIApplication.shared.connectedScenes.first,
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
@ -26,42 +30,25 @@ class PreferencesModel: ObservableObject {
}
#endif
@available(iOSApplicationExtension, unavailable)
@Published var selectedColorScheme: ColorScheme?
@available(iOSApplicationExtension, unavailable)
@Published var appearance: Int = 0 {
didSet {
switch appearance {
case 1:
// selectedColorScheme = .light
case .light:
#if os(macOS)
NSApp.appearance = NSAppearance(named: .aqua)
#else
window?.overrideUserInterfaceStyle = .light
#endif
case 2:
// selectedColorScheme = .dark
case .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
}
defaults.set(appearance, forKey: WFDefaults.colorSchemeIntegerKey)
}
}
@Published var font: Int = 0 {
didSet {
defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey)
}
}
}

View File

@ -3,6 +3,28 @@ import SwiftUI
struct PreferencesView: View {
@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 {
VStack {
VStack {
@ -46,6 +68,10 @@ struct PreferencesView: View {
}
.padding(.bottom)
}
.onChange(of: preferences.appearance) { value in
preferences.updateAppearance(to: Appearance(rawValue: value) ?? .system)
UserDefaults.shared.set(value, forKey: WFDefaults.colorSchemeIntegerKey)
}
}
}

View File

@ -136,6 +136,8 @@
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 */; };
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 */; };
3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779389629EC0C880032D6C1 /* HelpCommands.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>"; };
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>"; };
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>"; };
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>"; };
@ -586,6 +589,7 @@
isa = PBXGroup;
children = (
17DF328224C87D3300BCE2E3 /* ContentView.swift */,
37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */,
);
path = Navigation;
sourceTree = "<group>";
@ -922,6 +926,7 @@
173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */,
17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */,
17C42E622507D8E600072984 /* PostStatus.swift in Sources */,
37095AE02AA4A0E700C9C5F8 /* NoSelectedPostView.swift in Sources */,
1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */,
1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */,
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */,
@ -975,6 +980,7 @@
17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */,
17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */,
172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */,
37113EF92A98C10A00B36B98 /* NoSelectedPostView.swift in Sources */,
1727526728099802003D0A6A /* ErrorConstants.swift in Sources */,
17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */,
17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */,
@ -1060,7 +1066,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 704;
CURRENT_PROJECT_VERSION = 706;
DEVELOPMENT_TEAM = TPPAB4YBA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
@ -1072,7 +1078,7 @@
"@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_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1091,7 +1097,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 704;
CURRENT_PROJECT_VERSION = 706;
DEVELOPMENT_TEAM = TPPAB4YBA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
@ -1103,7 +1109,7 @@
"@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_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1234,7 +1240,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 704;
CURRENT_PROJECT_VERSION = 706;
DEVELOPMENT_TEAM = TPPAB4YBA6;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;
@ -1243,7 +1249,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.16;
MARKETING_VERSION = 1.0.17;
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
PRODUCT_NAME = "WriteFreely-MultiPlatform";
SDKROOT = iphoneos;
@ -1260,7 +1266,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 704;
CURRENT_PROJECT_VERSION = 706;
DEVELOPMENT_TEAM = TPPAB4YBA6;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = iOS/Info.plist;
@ -1269,7 +1275,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.16;
MARKETING_VERSION = 1.0.17;
PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform";
PRODUCT_NAME = "WriteFreely-MultiPlatform";
SDKROOT = iphoneos;

View File

@ -50,7 +50,10 @@ struct PostEditorView: View {
PostEditorStatusToolbarView(post: post)
}
ToolbarItem(placement: .primaryAction) {
if model.isProcessingRequest {
if !model.hasNetworkConnection {
Image(systemName: "wifi.exclamationmark")
.foregroundColor(.secondary)
} else if model.isProcessingRequest {
ProgressView()
} else {
Menu(content: {

View File

@ -115,6 +115,7 @@ final class CustomTextView: NSView {
let scrollView = NSScrollView()
scrollView.drawsBackground = false
scrollView.borderType = .noBorder
scrollView.autohidesScrollers = true
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalRuler = false
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
.foregroundColor: NSColor.labelColor
]
textView.textContainer?.lineFragmentPadding = 16
textView.textContainerInset = NSSize(width: 0, height: 16)
return textView
}()

View File

@ -9,11 +9,17 @@ struct PostEditorView: View {
@State private var updatingFromServer: Bool = false
var body: some View {
VStack {
if !model.hasNetworkConnection {
Label("You are not connected to the internet", systemImage: "wifi.exclamationmark")
.font(.caption)
.foregroundColor(.secondary)
.padding(.top, 8)
}
PostTextEditingView(
post: post,
updatingFromServer: $updatingFromServer
)
.padding()
.background(Color(NSColor.controlBackgroundColor))
.onAppear(perform: {
model.editor.setInitialValues(for: post)
@ -69,6 +75,7 @@ struct PostEditorView: View {
}
})
}
}
}
struct PostEditorView_EmptyPostPreviews: PreviewProvider {

View File

@ -15,7 +15,8 @@ struct PostTextEditingView: View {
if combinedText.count == 0 {
Text("Write…")
.foregroundColor(Color(NSColor.placeholderTextColor))
.padding(.horizontal, 5)
.padding(.horizontal, 16)
.padding(.vertical, 16)
.font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
}
if post.appearance == "sans" {