Skip to content

Commit

Permalink
Prep Mac app for release, fix bugs in iOS app (#258)
Browse files Browse the repository at this point in the history
* Update Sparkle to latest version

* Bump minimum macOS target

For launch, I propose we support the current version of macOS (14.x) and one version earlier (13.x).

* Add WFNavigation wrapper to use NavigationSplitView in macOS

* Replace NavigationView with WFNavigation in ContentView

* Fix deprecation warnings on locale

* Update docs for updating the Mac app

* Fix for being sent back to post list on app reactivate

* Bump build version

* Remove debugging statements

* Bump Sparkle version to address security fix
  • Loading branch information
AngeloStavrow authored Jul 28, 2024
1 parent 664eb44 commit 53ab32b
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 86 deletions.
13 changes: 10 additions & 3 deletions Shared/Extensions/WriteFreelyModel+API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,16 @@ extension WriteFreelyModel {
}

if post.language == nil {
if let languageCode = Locale.current.languageCode {
post.language = languageCode
post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
if #available(iOS 16, macOS 13, *) {
if let languageCode = Locale.current.language.languageCode?.identifier {
post.language = languageCode
post.rtl = Locale.Language(identifier: languageCode).characterDirection == .rightToLeft
}
} else {
if let languageCode = Locale.current.languageCode {
post.language = languageCode
post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
}
}
}

Expand Down
121 changes: 73 additions & 48 deletions Shared/Navigation/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,86 @@ struct ContentView: View {
@EnvironmentObject var errorHandling: ErrorHandling

var body: some View {
NavigationView {
#if os(macOS)
CollectionListView()
.withErrorHandling()
.toolbar {
Button(
action: {
NSApp.keyWindow?.contentViewController?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil
#if os(macOS)
WFNavigation(
collectionList: {
CollectionListView()
.withErrorHandling()
.toolbar {
if #available(macOS 13, *) {
EmptyView()
} else {
Button(
action: {
NSApp.keyWindow?.contentViewController?.tryToPerform(
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil
)
},
label: { Image(systemName: "sidebar.left") }
)
},
label: { Image(systemName: "sidebar.left") }
)
.help("Toggle the sidebar's visibility.")
Spacer()
Button(action: {
withAnimation {
// Un-set the currently selected post
self.model.selectedPost = nil
.help("Toggle the sidebar's visibility.")
}
// Create the new-post managed object
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
withAnimation {
DispatchQueue.main.async {
// Load the new post in the editor
self.model.selectedPost = managedPost
Spacer()
Button(action: {
withAnimation {
// Un-set the currently selected post
self.model.selectedPost = nil
}
// Create the new-post managed object
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
withAnimation {
DispatchQueue.main.async {
// Load the new post in the editor
self.model.selectedPost = managedPost
}
}
}, label: { Image(systemName: "square.and.pencil") })
.help("Create a new local draft.")
}
.frame(width: 200)
},
postList: {
ZStack {
PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts)
.withErrorHandling()
.frame(width: 300)
if model.isProcessingRequest {
ZStack {
Color(NSColor.controlBackgroundColor).opacity(0.75)
ProgressView()
}
}, label: { Image(systemName: "square.and.pencil") })
.help("Create a new local draft.")
}
.frame(width: 200)
#else
CollectionListView()
.withErrorHandling()
#endif

#if os(macOS)
ZStack {
PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts)
.withErrorHandling()
.frame(width: 300)
if model.isProcessingRequest {
ZStack {
Color(NSColor.controlBackgroundColor).opacity(0.75)
ProgressView()
}
}
},
postDetail: {
NoSelectedPostView(isConnected: $model.hasNetworkConnection)
}
)
.environmentObject(model)
.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
}
#else
PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts)
.withErrorHandling()
#endif

NoSelectedPostView(isConnected: $model.hasNetworkConnection)
}
#else
WFNavigation(
collectionList: {
CollectionListView()
.withErrorHandling()
},
postList: {
PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts)
.withErrorHandling()
},
postDetail: {
NoSelectedPostView(isConnected: $model.hasNetworkConnection)
}
)
.environmentObject(model)
.onChange(of: model.hasError) { value in
if value {
Expand All @@ -72,6 +96,7 @@ struct ContentView: View {
model.hasError = false
}
}
#endif
}
}

Expand Down
37 changes: 37 additions & 0 deletions Shared/Navigation/WFNavigation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import SwiftUI

struct WFNavigation<CollectionList, PostList, PostDetail>: View
where CollectionList: View, PostList: View, PostDetail: View {

private var collectionList: CollectionList
private var postList: PostList
private var postDetail: PostDetail

init(
@ViewBuilder collectionList: () -> CollectionList,
@ViewBuilder postList: () -> PostList,
@ViewBuilder postDetail: () -> PostDetail
) {
self.collectionList = collectionList()
self.postList = postList()
self.postDetail = postDetail()
}

var body: some View {
#if os(macOS)
NavigationSplitView {
collectionList
} content: {
postList
} detail: {
postDetail
}
#else
NavigationView {
collectionList
postList
postDetail
}
#endif
}
}
13 changes: 10 additions & 3 deletions Shared/PostEditor/PostEditorModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@ struct PostEditorModel {
default:
managedPost.appearance = "serif"
}
if let languageCode = Locale.current.languageCode {
managedPost.language = languageCode
managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
if #available(iOS 16, macOS 13, *) {
if let languageCode = Locale.current.language.languageCode?.identifier {
managedPost.language = languageCode
managedPost.rtl = Locale.Language(identifier: languageCode).characterDirection == .rightToLeft
}
} else {
if let languageCode = Locale.current.languageCode {
managedPost.language = languageCode
managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
}
}
return managedPost
}
Expand Down
14 changes: 7 additions & 7 deletions Shared/PostList/PostListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,18 @@ struct PostListView: View {
.frame(height: frameHeight)
.background(Color(UIColor.systemGray5))
.overlay(Divider(), alignment: .top)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
// We use this to invalidate and refresh the view, so that new posts created outside of the app (e.g.,
// in the action extension) show up.
withAnimation {
self.filteredListViewId += 1
}
}
}
.ignoresSafeArea(.all, edges: .bottom)
.onAppear {
// Set the selected collection and whether or not we want to show all posts
model.selectedCollection = selectedCollection
model.showAllPosts = showAllPosts

// We use this to invalidate and refresh the view, so that new posts created outside of the app (e.g.,
// in the action extension) show up.
withAnimation {
self.filteredListViewId += 1
}
}
.onChange(of: model.hasError) { value in
if value {
Expand Down
29 changes: 11 additions & 18 deletions Technotes/MacSoftwareUpdater.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

To make updating the Mac app easy, we're using the [Sparkle framework][1].

This is added to the project via the Swift Package Manager (SPM), but at the time of writing, tagged versions of Sparkle do not yet support
SPM — the dependency can only be added from a branch or commit. To avoid any surprises arising from updates to the project's `master`
branch, we're using [WriteFreely's fork of Sparkle][2]. Updates to the forked repository from upstream should be considered dangerous and
tested thoroughly before merging into `main`.

WriteFreely for Mac uses the v1.x branch of Sparkle, and is therefore not a sandboxed app.

## Troubleshooting

### If Xcode throws an error when you try to build the project
Expand All @@ -22,21 +15,22 @@ You should then be able to build and run the Mac target.

### If you can't run `generate_keys` because "Apple cannot check it for malicious software"

There may be a code signing issue with Sparkle. Right-click on `generate_keys` in the Finder and choose Open ([reference][3]).
If you run into a code signing issue with Sparkle, right-click on `generate_keys` in the Finder and choose Open ([reference][2]).

## Deploying Updates

To [publish an update to the app][5], you'll need the **Sparkle-for-Swift-Package-Manager.zip** [archive][4] — specifically, you'll need the
`generate_appcast` tool. Download and de-compress the archive.
To [publish an update to the app][4], you'll need the **Sparkle-for-Swift-Package-Manager.zip** [archive][3] — 
specifically, you'll need the `generate_appcast` tool. Download and de-compress the archive.

You will need some credentials and signing certificates to proceed with this process; speak to the project maintainer if you're responsible for
creating the update, and confirm you have:
You will need some credentials and signing certificates to proceed with this process; speak to the project maintainer if
you're responsible for creating the update, and confirm you have:

- the app's Developer ID Application certificate (check your Mac's system Keychain)
- the Sparkle EdDSA signing key (again, check your Mac's system Keychain)

Sign and notarize the app archive, then click on **Export Notarized App** in Xcode's Organizer window. Open the Terminal and navigate to
where you de-compressed the Sparkle-for-Swift-Package-Manager archive, then create a zip file that preserves symlinks:
Sign and notarize the app archive, then click on **Export Notarized App** in Xcode's Organizer window. Open the Terminal
and navigate to where you de-compressed the Sparkle-for-Swift-Package-Manager archive, then create a zip file that
preserves symlinks:

```bash
% ditto -c -k --sequesterRsrc --keepParent <source_path_to_app> <zip_destination>
Expand All @@ -60,7 +54,6 @@ and they'll be made available to users.

<!--references-->
[1]: https://sparkle-project.org
[2]: https://github.com/writefreely/Sparkle
[3]: https://github.com/sparkle-project/Sparkle/issues/1701#issuecomment-752249920
[4]: https://github.com/sparkle-project/Sparkle/releases/tag/1.24.0
[5]: https://sparkle-project.org/documentation/publishing/
[2]: https://github.com/sparkle-project/Sparkle/issues/1701#issuecomment-752249920
[3]: https://github.com/sparkle-project/Sparkle/releases/tag/1.24.0
[4]: https://sparkle-project.org/documentation/publishing/
Loading

0 comments on commit 53ab32b

Please sign in to comment.