diff --git a/ActionExtension-iOS/Action.js b/ActionExtension-iOS/Action.js
index 8225a62..92269e9 100644
--- a/ActionExtension-iOS/Action.js
+++ b/ActionExtension-iOS/Action.js
@@ -11,7 +11,8 @@ run: function(parameters) {
},
finalize: function(parameters) {
-
+ var customJavaScript = parameters["customJavaScript"];
+ eval(customJavaScript);
}
};
diff --git a/ActionExtension-iOS/ActionExtension-iOS.entitlements b/ActionExtension-iOS/ActionExtension-iOS.entitlements
new file mode 100644
index 0000000..a592bed
--- /dev/null
+++ b/ActionExtension-iOS/ActionExtension-iOS.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.abunchtell.writefreely
+
+
+
diff --git a/ActionExtension-iOS/ActionViewController.swift b/ActionExtension-iOS/ActionViewController.swift
index 5c508cc..c6b7cbe 100644
--- a/ActionExtension-iOS/ActionViewController.swift
+++ b/ActionExtension-iOS/ActionViewController.swift
@@ -1,30 +1,21 @@
-import UIKit
-import MobileCoreServices
-import UniformTypeIdentifiers
+import SwiftUI
class ActionViewController: UIViewController {
- @IBOutlet weak var imageView: UIImageView!
+ let moc = LocalStorageManager.standard.container.viewContext
+
+ override var prefersStatusBarHidden: Bool { true }
override func viewDidLoad() {
super.viewDidLoad()
- // Get the item[s] we're handling from the extension context.
- if let inputItem = extensionContext?.inputItems.first as? NSExtensionItem {
- if let itemProvider = inputItem.attachments?.first {
- itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String) { [weak self] dict, error in
- guard let itemDictionary = dict as? NSDictionary else { return }
- guard let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { return }
- print(javaScriptValues)
- }
- }
- }
- }
+ let contentView = ContentView()
+ .environment(\.extensionContext, extensionContext)
+ .environment(\.managedObjectContext, moc)
- @IBAction func done() {
- // Return any edited content to the host app.
- // This template doesn't do anything, so we just echo the passed in items.
- self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
+ view = UIHostingView(rootView: contentView)
+ view.isOpaque = true
+ view.backgroundColor = .systemBackground
}
}
diff --git a/ActionExtension-iOS/Base.lproj/MainInterface.storyboard b/ActionExtension-iOS/Base.lproj/MainInterface.storyboard
deleted file mode 100644
index b165d3a..0000000
--- a/ActionExtension-iOS/Base.lproj/MainInterface.storyboard
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ActionExtension-iOS/ContentView.swift b/ActionExtension-iOS/ContentView.swift
new file mode 100644
index 0000000..81df6f8
--- /dev/null
+++ b/ActionExtension-iOS/ContentView.swift
@@ -0,0 +1,198 @@
+import SwiftUI
+import MobileCoreServices
+import UniformTypeIdentifiers
+import WriteFreely
+
+enum WFActionExtensionError: Error {
+ case userCancelledRequest
+ case couldNotParseInputItems
+}
+
+struct ContentView: View {
+ @Environment(\.extensionContext) private var extensionContext: NSExtensionContext!
+ @Environment(\.managedObjectContext) private var managedObjectContext
+
+ @AppStorage(WFDefaults.defaultFontIntegerKey, store: UserDefaults.shared) var fontIndex: Int = 0
+
+ @FetchRequest(
+ entity: WFACollection.entity(),
+ sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)]
+ ) var collections: FetchedResults
+
+ @State private var draftTitle: String = ""
+ @State private var draftText: String = ""
+ @State private var isShowingAlert: Bool = false
+ @State private var selectedBlog: WFACollection?
+
+ private var draftsCollectionName: String {
+ guard UserDefaults.shared.string(forKey: WFDefaults.serverStringKey) == "https://write.as" else {
+ return "Drafts"
+ }
+ return "Anonymous"
+ }
+
+ private var controls: some View {
+ HStack {
+ Group {
+ Button(
+ action: { extensionContext.cancelRequest(withError: WFActionExtensionError.userCancelledRequest) },
+ label: { Image(systemName: "xmark.circle").imageScale(.large) }
+ )
+ .accessibilityLabel(Text("Cancel"))
+ Spacer()
+ Button(
+ action: {
+ savePostToCollection(collection: selectedBlog, title: draftTitle, body: draftText)
+ extensionContext.completeRequest(returningItems: nil, completionHandler: nil)
+ },
+ label: { Image(systemName: "square.and.arrow.down").imageScale(.large) }
+ )
+ .accessibilityLabel(Text("Create new draft"))
+ }
+ .padding()
+ }
+ }
+
+ var body: some View {
+ VStack {
+ controls
+ Form {
+ Section(header: Text("Title")) {
+ switch fontIndex {
+ case 1:
+ TextField("Draft Title", text: $draftTitle).font(.custom("OpenSans-Regular", size: 26))
+ case 2:
+ TextField("Draft Title", text: $draftTitle).font(.custom("Hack-Regular", size: 26))
+ default:
+ TextField("Draft Title", text: $draftTitle).font(.custom("Lora", size: 26))
+ }
+ }
+ Section(header: Text("Content")) {
+ switch fontIndex {
+ case 1:
+ TextEditor(text: $draftText).font(.custom("OpenSans-Regular", size: 17))
+ case 2:
+ TextEditor(text: $draftText).font(.custom("Hack-Regular", size: 17))
+ default:
+ TextEditor(text: $draftText).font(.custom("Lora", size: 17))
+ }
+ }
+ Section(header: Text("Save To")) {
+ Button(action: {
+ self.selectedBlog = nil
+ }, label: {
+ HStack {
+ Text(draftsCollectionName)
+ .foregroundColor(selectedBlog == nil ? .primary : .secondary)
+ Spacer()
+ if selectedBlog == nil {
+ Image(systemName: "checkmark")
+ }
+ }
+ })
+ ForEach(collections, id: \.self) { collection in
+ Button(action: {
+ self.selectedBlog = collection
+ }, label: {
+ HStack {
+ Text(collection.title)
+ .foregroundColor(selectedBlog == collection ? .primary : .secondary)
+ Spacer()
+ if selectedBlog == collection {
+ Image(systemName: "checkmark")
+ }
+ }
+ })
+ }
+ }
+ }
+ }
+ .alert(isPresented: $isShowingAlert, content: {
+ Alert(
+ title: Text("Something Went Wrong"),
+ message: Text("WriteFreely can't create a draft with the data received."),
+ dismissButton: .default(Text("OK"), action: {
+ extensionContext.cancelRequest(withError: WFActionExtensionError.couldNotParseInputItems)
+ }))
+ })
+ .onAppear {
+ do {
+ try getPageDataFromExtensionContext()
+ } catch {
+ self.isShowingAlert = true
+ }
+ }
+ }
+
+ private func savePostToCollection(collection: WFACollection?, title: String, body: String) {
+ let post = WFAPost(context: managedObjectContext)
+ post.createdDate = Date()
+ post.title = title
+ post.body = body
+ post.status = PostStatus.local.rawValue
+ post.collectionAlias = collection?.alias
+ switch fontIndex {
+ case 1:
+ post.appearance = "sans"
+ case 2:
+ post.appearance = "wrap"
+ default:
+ post.appearance = "serif"
+ }
+ if let languageCode = Locale.current.languageCode {
+ post.language = languageCode
+ post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
+ }
+ LocalStorageManager.standard.saveContext()
+ }
+
+ private func getPageDataFromExtensionContext() throws {
+ if let inputItem = extensionContext.inputItems.first as? NSExtensionItem {
+ if let itemProvider = inputItem.attachments?.first {
+
+ let typeIdentifier: String
+
+ if #available(iOS 15, *) {
+ typeIdentifier = UTType.propertyList.identifier
+ } else {
+ typeIdentifier = kUTTypePropertyList as String
+ }
+
+ itemProvider.loadItem(forTypeIdentifier: typeIdentifier) { (dict, error) in
+ if let error = error {
+ print("⚠️", error)
+ self.isShowingAlert = true
+ }
+
+ guard let itemDict = dict as? NSDictionary else {
+ return
+ }
+ guard let jsValues = itemDict[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else {
+ return
+ }
+
+ let pageTitle = jsValues["title"] as? String ?? ""
+ let pageURL = jsValues["URL"] as? String ?? ""
+ let pageSelectedText = jsValues["selection"] as? String ?? ""
+
+ if pageSelectedText.isEmpty {
+ // If there's no selected text, create a Markdown link to the webpage.
+ self.draftText = "[\(pageTitle)](\(pageURL))"
+ } else {
+ // If there is selected text, create a Markdown blockquote with the selection
+ // and add a Markdown link to the webpage.
+ self.draftText = """
+ > \(pageSelectedText)
+
+ Via: [\(pageTitle)](\(pageURL))
+ """
+ }
+ }
+ } else {
+ throw WFActionExtensionError.couldNotParseInputItems
+ }
+ } else {
+ throw WFActionExtensionError.couldNotParseInputItems
+ }
+ }
+}
diff --git a/ActionExtension-iOS/Info.plist b/ActionExtension-iOS/Info.plist
index e3ce5c0..9dcc0f9 100644
--- a/ActionExtension-iOS/Info.plist
+++ b/ActionExtension-iOS/Info.plist
@@ -2,6 +2,30 @@
+ UIAppFonts
+
+ LoraGX.ttf
+ OpenSans-Regular.ttf
+ Hack-Regular.ttf
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Create WriteFreely draft
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
NSExtension
NSExtensionAttributes
@@ -24,8 +48,8 @@
NSExtensionServiceTouchBarIconName
NSActionTemplate
- NSExtensionMainStoryboard
- MainInterface
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).ActionViewController
NSExtensionPointIdentifier
com.apple.ui-services
diff --git a/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@2x.png b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@2x.png
new file mode 100644
index 0000000..75f2008
Binary files /dev/null and b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@2x.png differ
diff --git a/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@3x.png b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@3x.png
new file mode 100644
index 0000000..1fd381e
Binary files /dev/null and b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/AppIconExtension@3x.png differ
diff --git a/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/Contents.json b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/Contents.json
new file mode 100644
index 0000000..4dac38e
--- /dev/null
+++ b/ActionExtension-iOS/Media.xcassets/AppIconExtension.appiconset/Contents.json
@@ -0,0 +1,100 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIconExtension@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIconExtension@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Shared/Account/AccountModel.swift b/Shared/Account/AccountModel.swift
index 0e6a6e8..94171ec 100644
--- a/Shared/Account/AccountModel.swift
+++ b/Shared/Account/AccountModel.swift
@@ -56,8 +56,6 @@ extension AccountError: LocalizedError {
struct AccountModel {
@AppStorage(WFDefaults.isLoggedIn, store: UserDefaults.shared) var isLoggedIn: Bool = false
private let defaults = UserDefaults.shared
- let usernameStringKey = "usernameStringKey"
- let serverStringKey = "serverStringKey"
var server: String = ""
var username: String = ""
@@ -68,19 +66,19 @@ struct AccountModel {
self.user = user
self.username = user.username ?? ""
self.isLoggedIn = true
- defaults.set(user.username, forKey: usernameStringKey)
- defaults.set(server, forKey: serverStringKey)
+ defaults.set(user.username, forKey: WFDefaults.usernameStringKey)
+ defaults.set(server, forKey: WFDefaults.serverStringKey)
}
mutating func logout() {
self.user = nil
self.isLoggedIn = false
- defaults.removeObject(forKey: usernameStringKey)
- defaults.removeObject(forKey: serverStringKey)
+ defaults.removeObject(forKey: WFDefaults.usernameStringKey)
+ defaults.removeObject(forKey: WFDefaults.serverStringKey)
}
mutating func restoreState() {
- server = defaults.string(forKey: serverStringKey) ?? ""
- username = defaults.string(forKey: usernameStringKey) ?? ""
+ server = defaults.string(forKey: WFDefaults.serverStringKey) ?? ""
+ username = defaults.string(forKey: WFDefaults.usernameStringKey) ?? ""
}
}
diff --git a/Shared/Extensions/UserDefaults+Extensions.swift b/Shared/Extensions/UserDefaults+Extensions.swift
index 47e554c..f40a824 100644
--- a/Shared/Extensions/UserDefaults+Extensions.swift
+++ b/Shared/Extensions/UserDefaults+Extensions.swift
@@ -5,6 +5,10 @@ enum WFDefaults {
static let showAllPostsFlag = "showAllPostsFlag"
static let selectedCollectionURL = "selectedCollectionURL"
static let lastDraftURL = "lastDraftURL"
+ static let colorSchemeIntegerKey = "colorSchemeIntegerKey"
+ static let defaultFontIntegerKey = "defaultFontIntegerKey"
+ static let usernameStringKey = "usernameStringKey"
+ static let serverStringKey = "serverStringKey"
#if os(macOS)
static let automaticallyChecksForUpdates = "automaticallyChecksForUpdates"
static let subscribeToBetaUpdates = "subscribeToBetaUpdates"
diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift
index 49247ea..ecb575f 100644
--- a/Shared/Models/WriteFreelyModel.swift
+++ b/Shared/Models/WriteFreelyModel.swift
@@ -43,8 +43,8 @@ final class WriteFreelyModel: ObservableObject {
init() {
DispatchQueue.main.async {
- self.preferences.appearance = self.defaults.integer(forKey: self.preferences.colorSchemeIntegerKey)
- self.preferences.font = self.defaults.integer(forKey: self.preferences.defaultFontIntegerKey)
+ self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey)
+ self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey)
self.account.restoreState()
if self.account.isLoggedIn {
guard let serverURL = URL(string: self.account.server) else {
diff --git a/Shared/Preferences/PreferencesModel.swift b/Shared/Preferences/PreferencesModel.swift
index d151d52..0fde8a6 100644
--- a/Shared/Preferences/PreferencesModel.swift
+++ b/Shared/Preferences/PreferencesModel.swift
@@ -2,8 +2,6 @@ import SwiftUI
class PreferencesModel: ObservableObject {
private let defaults = UserDefaults.shared
- let colorSchemeIntegerKey = "colorSchemeIntegerKey"
- let defaultFontIntegerKey = "defaultFontIntegerKey"
/* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now,
* because setting the .preferredColorScheme modifier on views in SwiftUI is
@@ -17,6 +15,7 @@ class PreferencesModel: ObservableObject {
*/
#if os(iOS)
+ @available(iOSApplicationExtension, unavailable)
var window: UIWindow? {
guard let scene = UIApplication.shared.connectedScenes.first,
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
@@ -27,7 +26,10 @@ class PreferencesModel: ObservableObject {
}
#endif
+ @available(iOSApplicationExtension, unavailable)
@Published var selectedColorScheme: ColorScheme?
+
+ @available(iOSApplicationExtension, unavailable)
@Published var appearance: Int = 0 {
didSet {
switch appearance {
@@ -54,12 +56,12 @@ class PreferencesModel: ObservableObject {
#endif
}
- defaults.set(appearance, forKey: colorSchemeIntegerKey)
+ defaults.set(appearance, forKey: WFDefaults.colorSchemeIntegerKey)
}
}
@Published var font: Int = 0 {
didSet {
- defaults.set(font, forKey: defaultFontIntegerKey)
+ defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey)
}
}
}
diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj
index 534b91c..6a36bf5 100644
--- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj
+++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj
@@ -30,9 +30,21 @@
172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */; platformFilter = maccatalyst; };
172E10042735B83E00061372 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 172E10032735B83E00061372 /* Media.xcassets */; };
172E10062735B83E00061372 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10052735B83E00061372 /* ActionViewController.swift */; };
- 172E10092735B83E00061372 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 172E10072735B83E00061372 /* MainInterface.storyboard */; };
172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
172E10132735BB6200061372 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = 172E10122735BB6200061372 /* Action.js */; };
+ 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10142735C2BD00061372 /* UIHostingView.swift */; };
+ 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */; };
+ 172E10192735C3DB00061372 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10182735C3DB00061372 /* ContentView.swift */; };
+ 172E101B2735C54400061372 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 172E101A2735C54400061372 /* WriteFreely */; };
+ 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; };
+ 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; };
+ 172E101E2735C62F00061372 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; };
+ 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; };
+ 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; };
+ 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; };
+ 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; };
+ 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; };
+ 172E10242735C72500061372 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; };
173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */; };
173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19E2254329CC00440F0F /* PostTextEditingView.swift */; };
17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17466625256C0D0600629997 /* MacEditorTextView.swift */; };
@@ -62,6 +74,10 @@
1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; };
17681E412519410E00D394AE /* UINavigationController+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */; };
1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; };
+ 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; };
+ 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; };
+ 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; };
+ 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; };
17A4FEDA25924AF70037E96B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17A4FED925924AF70037E96B /* Sparkle */; };
17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A4FEEC25927E730037E96B /* AppDelegate.swift */; };
17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */; };
@@ -170,9 +186,11 @@
172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
172E10032735B83E00061372 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; };
172E10052735B83E00061372 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; };
- 172E10082735B83E00061372 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
- 172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; usesTabs = 1; };
172E10122735BB6200061372 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = ""; };
+ 172E10142735C2BD00061372 /* UIHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHostingView.swift; sourceTree = ""; };
+ 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+Extensions.swift"; sourceTree = ""; };
+ 172E10182735C3DB00061372 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
173E19D0254318F600440F0F /* RemoteChangePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteChangePromptView.swift; sourceTree = ""; };
173E19E2254329CC00440F0F /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = ""; };
17466625256C0D0600629997 /* MacEditorTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacEditorTextView.swift; sourceTree = ""; };
@@ -192,6 +210,7 @@
1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
17681E402519410E00D394AE /* UINavigationController+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Appearance.swift"; sourceTree = ""; };
1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = ""; };
+ 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ActionExtension-iOS.entitlements"; sourceTree = ""; };
17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WriteFreely-MultiPlatform (iOS).entitlements"; sourceTree = ""; };
17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MacSoftwareUpdater.md; sourceTree = ""; };
17A4FEEC25927E730037E96B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
@@ -250,6 +269,7 @@
buildActionMask = 2147483647;
files = (
172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */,
+ 172E101B2735C54400061372 /* WriteFreely in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -308,9 +328,10 @@
172E10022735B83E00061372 /* ActionExtension-iOS */ = {
isa = PBXGroup;
children = (
+ 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */,
172E10032735B83E00061372 /* Media.xcassets */,
+ 172E10182735C3DB00061372 /* ContentView.swift */,
172E10052735B83E00061372 /* ActionViewController.swift */,
- 172E10072735B83E00061372 /* MainInterface.storyboard */,
172E100A2735B83E00061372 /* Info.plist */,
172E10122735BB6200061372 /* Action.js */,
);
@@ -367,6 +388,8 @@
children = (
1756AE8024CB844500FD7257 /* View+Keyboard.swift */,
17681E402519410E00D394AE /* UINavigationController+Appearance.swift */,
+ 172E10142735C2BD00061372 /* UIHostingView.swift */,
+ 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */,
);
path = Extensions;
sourceTree = "";
@@ -595,6 +618,9 @@
dependencies = (
);
name = "ActionExtension-iOS";
+ packageProductDependencies = (
+ 172E101A2735C54400061372 /* WriteFreely */,
+ );
productName = "ActionExtension-iOS";
productReference = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */;
productType = "com.apple.product-type.app-extension";
@@ -739,9 +765,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */,
172E10132735BB6200061372 /* Action.js in Resources */,
172E10042735B83E00061372 /* Media.xcassets in Resources */,
- 172E10092735B83E00061372 /* MainInterface.storyboard in Resources */,
+ 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */,
+ 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -834,6 +862,19 @@
buildActionMask = 2147483647;
files = (
172E10062735B83E00061372 /* ActionViewController.swift in Sources */,
+ 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */,
+ 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */,
+ 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */,
+ 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */,
+ 172E10242735C72500061372 /* PreferencesModel.swift in Sources */,
+ 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */,
+ 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */,
+ 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */,
+ 172E10192735C3DB00061372 /* ContentView.swift in Sources */,
+ 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */,
+ 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */,
+ 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */,
+ 172E101E2735C62F00061372 /* PostStatus.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -968,22 +1009,14 @@
};
/* End PBXTargetDependency section */
-/* Begin PBXVariantGroup section */
- 172E10072735B83E00061372 /* MainInterface.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 172E10082735B83E00061372 /* Base */,
- );
- name = MainInterface.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
/* Begin XCBuildConfiguration section */
172E100F2735B83E00061372 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "";
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = TPPAB4YBA6;
@@ -991,7 +1024,7 @@
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "ActionExtension-iOS";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1011,7 +1044,10 @@
172E10102735B83E00061372 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "";
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+ CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = TPPAB4YBA6;
@@ -1019,7 +1055,7 @@
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "ActionExtension-iOS";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1431,6 +1467,11 @@
package = 1714DD63260BAC14000C0DFF /* XCRemoteSwiftPackageReference "writefreely-swift" */;
productName = WriteFreely;
};
+ 172E101A2735C54400061372 /* WriteFreely */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 1714DD63260BAC14000C0DFF /* XCRemoteSwiftPackageReference "writefreely-swift" */;
+ productName = WriteFreely;
+ };
17A4FED925924AF70037E96B /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = 17A4FED825924AF70037E96B /* XCRemoteSwiftPackageReference "Sparkle" */;
diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
index 926b2fa..21aa893 100644
--- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
ActionExtension-iOS.xcscheme_^#shared#^_
orderHint
- 2
+ 1
WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_
@@ -17,7 +17,7 @@
WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_
orderHint
- 1
+ 2
diff --git a/iOS/Extensions/EnvironmentValues+Extensions.swift b/iOS/Extensions/EnvironmentValues+Extensions.swift
new file mode 100644
index 0000000..1357791
--- /dev/null
+++ b/iOS/Extensions/EnvironmentValues+Extensions.swift
@@ -0,0 +1,20 @@
+// Credit:
+// https://github.com/sindresorhus/Blear/blob/9ce7cd6ad8d6a88f8d0be12b1ef9152baeeacf96/Blear/Utilities.swift#L1052-L1064
+
+import SwiftUI
+
+extension EnvironmentValues {
+
+ private struct ExtensionContext: EnvironmentKey {
+ static var defaultValue: NSExtensionContext?
+ }
+
+ /// The `.extensionContext` of an app extension view controller.
+ var extensionContext: NSExtensionContext? {
+ get { self[ExtensionContext.self] }
+ set {
+ self[ExtensionContext.self] = newValue
+ }
+ }
+
+}
diff --git a/iOS/Extensions/UIHostingView.swift b/iOS/Extensions/UIHostingView.swift
new file mode 100644
index 0000000..4a960d1
--- /dev/null
+++ b/iOS/Extensions/UIHostingView.swift
@@ -0,0 +1,57 @@
+// Credit:
+// https://github.com/sindresorhus/Blear/blob/9ce7cd6ad8d6a88f8d0be12b1ef9152baeeacf96/Blear/Utilities.swift#L317-L368
+
+import SwiftUI
+
+final class UIHostingView: UIView {
+ private let rootViewHostingController: UIHostingController
+
+ var rootView: Content {
+ get { rootViewHostingController.rootView }
+ set {
+ rootViewHostingController.rootView = newValue
+ }
+ }
+
+ required init(rootView: Content) {
+ self.rootViewHostingController = UIHostingController(rootView: rootView)
+ super.init(frame: .zero)
+ rootViewHostingController.view.backgroundColor = .clear
+ addSubview(rootViewHostingController.view)
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ rootViewHostingController.view.frame = bounds
+ }
+
+ override func sizeToFit() {
+ guard let superview = superview else {
+ super.sizeToFit()
+ return
+ }
+
+ frame.size = rootViewHostingController.sizeThatFits(in: superview.frame.size)
+ }
+
+ override func sizeThatFits(_ size: CGSize) -> CGSize {
+ rootViewHostingController.sizeThatFits(in: size)
+ }
+
+ override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize {
+ rootViewHostingController.sizeThatFits(in: targetSize)
+ }
+
+ override func systemLayoutSizeFitting(
+ _ targetSize: CGSize,
+ withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
+ verticalFittingPriority: UILayoutPriority
+ ) -> CGSize {
+ rootViewHostingController.sizeThatFits(in: targetSize)
+ }
+}