diff --git a/.swiftlint.yml b/.swiftlint.yml index 780afe7..e590428 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,2 +1,5 @@ disabled_rules: - identifier_name + - identifier_name + # Swift 5.3 + - multiple_closures_with_trailing_closure + - no_space_in_method_call diff --git a/Development Assets/DevelopmentModels.swift b/Development Assets/DevelopmentModels.swift new file mode 100644 index 0000000..6b521fb --- /dev/null +++ b/Development Assets/DevelopmentModels.swift @@ -0,0 +1,69 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import Combine + +// swiftlint:disable force_try +private let decoder = MastodonDecoder() +private var cancellables = Set() +private let devIdentityID = "DEVELOPMENT_IDENTITY_ID" + +extension Secrets { + static let development: Secrets = { + let secrets = Secrets(keychain: FakeKeychain()) + + try! secrets.set("DEVELOPMENT_CLIENT_ID", forItem: .clientID, forIdentityID: devIdentityID) + try! secrets.set("DEVELOPMENT_CLIENT_SECRET", forItem: .clientSecret, forIdentityID: devIdentityID) + try! secrets.set("DEVELOPMENT_ACCESS_TOKEN", forItem: .accessToken, forIdentityID: devIdentityID) + + return secrets + }() +} + +extension MastodonClient { + static let development = MastodonClient(configuration: .stubbing) +} + +extension Account { + static let development = try! decoder.decode(Account.self, from: Data(officialAccountJSON.utf8)) +} + +extension Instance { + static let development = try! decoder.decode(Instance.self, from: Data(officialInstanceJSON.utf8)) +} + +extension IdentityDatabase { + static var development: IdentityDatabase = { + let db = try! IdentityDatabase(inMemory: true) + + db.createIdentity(id: devIdentityID, url: URL(string: "https://mastodon.social")!) + .receive(on: ImmediateScheduler.shared) + .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + .store(in: &cancellables) + + db.updateAccount(.development, forIdentityID: devIdentityID) + .receive(on: ImmediateScheduler.shared) + .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + .store(in: &cancellables) + + db.updateInstance(.development, forIdentityID: devIdentityID) + .receive(on: ImmediateScheduler.shared) + .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + .store(in: &cancellables) + + return db + }() +} + +extension Identity { + static let development = try! IdentityDatabase.development.identity(id: devIdentityID)! +} + +extension SceneViewModel { + static let development = SceneViewModel( + networkClient: .development, + identityDatabase: .development, + secrets: .development) +} + +// swiftlint:enable force_try diff --git a/Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift b/Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift new file mode 100644 index 0000000..09c4939 --- /dev/null +++ b/Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift @@ -0,0 +1,50 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +// swiftlint:disable line_length +let officialAccountJSON = #""" +{ + "id": "13179", + "username": "Mastodon", + "acct": "Mastodon", + "display_name": "Mastodon", + "locked": false, + "bot": false, + "discoverable": false, + "group": false, + "created_at": "2016-11-23T04:32:45.703Z", + "note": "

Official account of the Mastodon project. News, releases, announcements. All in micro-blog form! Toot toot!

", + "url": "https://mastodon.social/@Mastodon", + "avatar": "https://files.mastodon.social/accounts/avatars/000/013/179/original/27bc451c7713091b.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/013/179/original/27bc451c7713091b.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/013/179/original/4835294a8ed4c5a2.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/013/179/original/4835294a8ed4c5a2.png", + "followers_count": 531199, + "following_count": 10, + "statuses_count": 166, + "last_status_at": "2020-07-26", + "emojis": [], + "fields": [ + { + "name": "Homepage", + "value": "https://joinmastodon.org", + "verified_at": "2018-10-31T04:11:00.076+00:00" + }, + { + "name": "Patreon", + "value": "https://patreon.com/mastodon", + "verified_at": null + } + ] +} +"""# + +extension AccountEndpoint: Stubbing { + func dataString(url: URL) -> String? { + switch self { + case .verifyCredentials: return officialAccountJSON + } + } +} +// swiftlint:enable line_length diff --git a/Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift b/Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift new file mode 100644 index 0000000..98ff2ec --- /dev/null +++ b/Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift @@ -0,0 +1,74 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +// swiftlint:disable line_length +let officialInstanceJSON = #""" +{ + "uri": "mastodon.social", + "title": "Mastodon", + "short_description": "Server run by the main developers of the project \"\ud83d\udc18\" It is not focused on any particular niche interest - everyone is welcome as long as you follow our code of conduct!", + "description": "Server run by the main developers of the project \"\ud83d\udc18\" It is not focused on any particular niche interest - everyone is welcome as long as you follow our code of conduct!", + "email": "staff@mastodon.social", + "version": "3.2.0", + "urls": { + "streaming_api": "wss://mastodon.social" + }, + "stats": { + "user_count": 528991, + "status_count": 24588857, + "domain_count": 14857 + }, + "thumbnail": "https://files.mastodon.social/site_uploads/files/000/000/001/original/vlcsnap-2018-08-27-16h43m11s127.png", + "languages": [ + "en" + ], + "registrations": false, + "approval_required": false, + "invites_enabled": true, + "contact_account": { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "discoverable": true, + "group": false, + "created_at": "2016-03-16T14:34:26.392Z", + "note": "

Developer of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "followers_count": 425409, + "following_count": 440, + "statuses_count": 66170, + "last_status_at": "2020-07-31", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + }, + { + "name": "Homepage", + "value": "https://zeonfederated.com", + "verified_at": "2019-07-15T18:29:57.191+00:00" + } + ] + } +} +"""# + +extension InstanceEndpoint: Stubbing { + + func dataString(url: URL) -> String? { + switch self { + case .instance: return officialInstanceJSON + } + } +} +// swiftlint:enable line_length diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 5208354..6270a7c 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -11,6 +11,16 @@ D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D047FA8524C3E21000AF17C5 /* MetatextApp.swift */; }; D047FAB224C3E21200AF17C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D047FA8724C3E21200AF17C5 /* Assets.xcassets */; }; D047FAB324C3E21200AF17C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D047FA8724C3E21200AF17C5 /* Assets.xcassets */; }; + D04FD73324D48F37007D572D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73224D48F37007D572D /* SettingsView.swift */; }; + D04FD73424D48F37007D572D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73224D48F37007D572D /* SettingsView.swift */; }; + D04FD73624D49506007D572D /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73524D49506007D572D /* SettingsViewModel.swift */; }; + D04FD73724D49506007D572D /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73524D49506007D572D /* SettingsViewModel.swift */; }; + D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */; }; + D04FD73A24D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */; }; + D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */; }; + D04FD73D24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */; }; + D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD74124D4AA34007D572D /* DevelopmentModels.swift */; }; + D04FD74324D4AA34007D572D /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD74124D4AA34007D572D /* DevelopmentModels.swift */; }; D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; }; D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; }; D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; }; @@ -42,6 +52,8 @@ D0666A7D24C7745A00F3F04B /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = D0666A7C24C7745A00F3F04B /* GRDB */; }; D06B491F24D3F7FE00642749 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D06B491E24D3F7FE00642749 /* Localizable.strings */; }; D06B492024D3FB8000642749 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D06B491E24D3F7FE00642749 /* Localizable.strings */; }; + D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; }; + D06B492524D4612400642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492424D4612400642749 /* KingfisherSwiftUI */; }; D074577724D29006004758DB /* StubbingWebAuthenticationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577624D29006004758DB /* StubbingWebAuthenticationSession.swift */; }; D074577824D29006004758DB /* StubbingWebAuthenticationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577624D29006004758DB /* StubbingWebAuthenticationSession.swift */; }; D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; }; @@ -131,6 +143,11 @@ D047FA9424C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; }; D047FA9624C3E21200AF17C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D047FA9724C3E21200AF17C5 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; + D04FD73224D48F37007D572D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + D04FD73524D49506007D572D /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountEndpoint+Stubbing.swift"; sourceTree = ""; }; + D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InstanceEndpoint+Stubbing.swift"; sourceTree = ""; }; + D04FD74124D4AA34007D572D /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = ""; }; D065F53A24D3B33A00741304 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; D065F53D24D3D20300741304 /* InstanceEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceEndpoint.swift; sourceTree = ""; }; D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -189,6 +206,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */, D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */, D0DC175F24D016EA00A75C65 /* Alamofire in Frameworks */, ); @@ -198,6 +216,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D06B492524D4612400642749 /* KingfisherSwiftUI in Frameworks */, D0DC176124D0171800A75C65 /* Alamofire in Frameworks */, D0666A7D24C7745A00F3F04B /* GRDB in Frameworks */, ); @@ -321,6 +340,7 @@ D0DB6EF324C5228A00D965FE /* AddIdentityView.swift */, D0BEC93A24C96FD500E864C4 /* ContentView.swift */, D0BEC94924CA231200E864C4 /* TimelineView.swift */, + D04FD73224D48F37007D572D /* SettingsView.swift */, ); path = Views; sourceTree = ""; @@ -343,6 +363,7 @@ children = ( D0DB6F0824C65AC000D965FE /* AddIdentityViewModel.swift */, D0BEC93724C9632800E864C4 /* SceneViewModel.swift */, + D04FD73524D49506007D572D /* SettingsViewModel.swift */, D0BEC94624CA22C400E864C4 /* TimelineViewModel.swift */, ); path = "View Models"; @@ -363,7 +384,9 @@ isa = PBXGroup; children = ( D0DC175424D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift */, + D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */, D0DC174924CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift */, + D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */, D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */, ); path = "Mastodon API Stubs"; @@ -380,6 +403,7 @@ D0ED1BB224CE3A1600B4899C /* Development Assets */ = { isa = PBXGroup; children = ( + D04FD74124D4AA34007D572D /* DevelopmentModels.swift */, D0DC177624D0CF2600A75C65 /* FakeKeychain.swift */, D0DC175724D0130800A75C65 /* HTTPStubs.swift */, D0DC174824CFF13700A75C65 /* Mastodon API Stubs */, @@ -426,6 +450,7 @@ packageProductDependencies = ( D0666A4824C6C1A300F3F04B /* GRDB */, D0DC175E24D016EA00A75C65 /* Alamofire */, + D06B492224D4611300642749 /* KingfisherSwiftUI */, ); productName = "Metatext (iOS)"; productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */; @@ -448,6 +473,7 @@ packageProductDependencies = ( D0666A7C24C7745A00F3F04B /* GRDB */, D0DC176024D0171800A75C65 /* Alamofire */, + D06B492424D4612400642749 /* KingfisherSwiftUI */, ); productName = "Metatext (macOS)"; productReference = D047FA9424C3E21200AF17C5 /* Metatext.app */; @@ -510,6 +536,7 @@ D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */, D0DC175D24D016EA00A75C65 /* XCRemoteSwiftPackageReference "Alamofire" */, D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */, + D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = D047FA8D24C3E21200AF17C5 /* Products */; projectDirPath = ""; @@ -592,6 +619,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */, D0DB6F0924C65AC000D965FE /* AddIdentityViewModel.swift in Sources */, D0ED1BD724CF94B200B4899C /* Application.swift in Sources */, D047FAAE24C3E21200AF17C5 /* MetatextApp.swift in Sources */, @@ -601,6 +629,7 @@ D0ED1BE324CFA84400B4899C /* MastodonError.swift in Sources */, D0666A6324C6DC6C00F3F04B /* AppAuthorization.swift in Sources */, D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */, + D04FD73324D48F37007D572D /* SettingsView.swift in Sources */, D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */, D0666A5A24C6C64100F3F04B /* MastodonEncoder.swift in Sources */, D0666A5124C6C3BC00F3F04B /* Account.swift in Sources */, @@ -618,6 +647,8 @@ D0666A4224C6BB7B00F3F04B /* IdentityDatabase.swift in Sources */, D0BEC94A24CA231200E864C4 /* TimelineView.swift in Sources */, D0BEC93B24C96FD500E864C4 /* ContentView.swift in Sources */, + D04FD73624D49506007D572D /* SettingsViewModel.swift in Sources */, + D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */, D0DC175824D0130800A75C65 /* HTTPStubs.swift in Sources */, D0DC177724D0CF2600A75C65 /* FakeKeychain.swift in Sources */, D0C963FB24CC359D003BD330 /* AlertItem.swift in Sources */, @@ -634,6 +665,7 @@ D0BEC95124CA2B7E00E864C4 /* TabNavigation.swift in Sources */, D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */, D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */, + D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */, D0DC175B24D0154F00A75C65 /* MastodonAPI.swift in Sources */, D0ED1BD124CF779B00B4899C /* MastodonTarget.swift in Sources */, D065F53E24D3D20300741304 /* InstanceEndpoint.swift in Sources */, @@ -646,6 +678,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D04FD73A24D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */, D0DB6F0A24C65AC000D965FE /* AddIdentityViewModel.swift in Sources */, D0ED1BD824CF94B200B4899C /* Application.swift in Sources */, D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */, @@ -655,6 +688,7 @@ D0ED1BE424CFA84400B4899C /* MastodonError.swift in Sources */, D0666A6424C6DC6C00F3F04B /* AppAuthorization.swift in Sources */, D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */, + D04FD73424D48F37007D572D /* SettingsView.swift in Sources */, D0DC174B24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */, D0666A5B24C6C64100F3F04B /* MastodonEncoder.swift in Sources */, D0666A5224C6C3BC00F3F04B /* Account.swift in Sources */, @@ -672,6 +706,8 @@ D0666A4324C6BB7B00F3F04B /* IdentityDatabase.swift in Sources */, D0BEC94B24CA231200E864C4 /* TimelineView.swift in Sources */, D0BEC93C24C96FD500E864C4 /* ContentView.swift in Sources */, + D04FD73724D49506007D572D /* SettingsViewModel.swift in Sources */, + D04FD74324D4AA34007D572D /* DevelopmentModels.swift in Sources */, D0DC175924D0130800A75C65 /* HTTPStubs.swift in Sources */, D0DC177824D0CF2600A75C65 /* FakeKeychain.swift in Sources */, D0C963FC24CC359D003BD330 /* AlertItem.swift in Sources */, @@ -688,6 +724,7 @@ D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */, D0ED1BC524CED54D00B4899C /* HTTPTarget.swift in Sources */, D0C963FF24CC3812003BD330 /* Publisher+Extensions.swift in Sources */, + D04FD73D24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */, D0DC175C24D0154F00A75C65 /* MastodonAPI.swift in Sources */, D0ED1BD224CF779B00B4899C /* MastodonTarget.swift in Sources */, D065F53F24D3D20300741304 /* InstanceEndpoint.swift in Sources */, @@ -832,7 +869,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_ASSET_PATHS = "Development\\ Assets"; + DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_TEAM = 82HL67AXQ2; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; @@ -855,7 +892,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_ASSET_PATHS = "Development\\ Assets"; + DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_TEAM = 82HL67AXQ2; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; @@ -881,7 +918,7 @@ CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_ASSET_PATHS = "Development\\ Assets"; + DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_TEAM = 82HL67AXQ2; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -906,7 +943,7 @@ CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_ASSET_PATHS = "Development\\ Assets"; + DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_TEAM = 82HL67AXQ2; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -1029,6 +1066,14 @@ minimumVersion = "5.0.0-beta.10"; }; }; + D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.14.1; + }; + }; D0DC175D24D016EA00A75C65 /* XCRemoteSwiftPackageReference "Alamofire" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire"; @@ -1055,6 +1100,16 @@ package = D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */; productName = GRDB; }; + D06B492224D4611300642749 /* KingfisherSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = KingfisherSwiftUI; + }; + D06B492424D4612400642749 /* KingfisherSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = KingfisherSwiftUI; + }; D0DC175E24D016EA00A75C65 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; package = D0DC175D24D016EA00A75C65 /* XCRemoteSwiftPackageReference "Alamofire" */; diff --git a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2be41dc..c91bb53 100644 --- a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "revision": "ededd8668abd5a3c4c43cc9ebcfd611082b47f65", "version": "5.0.0-beta.10" } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher", + "state": { + "branch": null, + "revision": "1339ebea9498ef6c3fc75cc195d7163d7c7167f9", + "version": "5.14.1" + } } ] }, diff --git a/Shared/Localizations/Localizable.strings b/Shared/Localizations/Localizable.strings index 244d6d9..fbf90c8 100644 --- a/Shared/Localizations/Localizable.strings +++ b/Shared/Localizations/Localizable.strings @@ -3,3 +3,4 @@ "go" = "Go"; "add-identity.instance-url" = "Instance URL"; "oauth.error.code-not-found" = "OAuth error: code not found"; +"settings" = "Settings"; diff --git a/Shared/View Models/SceneViewModel.swift b/Shared/View Models/SceneViewModel.swift index 129ae89..510f63a 100644 --- a/Shared/View Models/SceneViewModel.swift +++ b/Shared/View Models/SceneViewModel.swift @@ -20,6 +20,7 @@ class SceneViewModel: ObservableObject { } @Published var alertItem: AlertItem? + @Published var presentingSettings = false var selectedTopLevelNavigation: TopLevelNavigation? = .timelines private let networkClient: MastodonClient diff --git a/Shared/View Models/SettingsViewModel.swift b/Shared/View Models/SettingsViewModel.swift new file mode 100644 index 0000000..495e615 --- /dev/null +++ b/Shared/View Models/SettingsViewModel.swift @@ -0,0 +1,11 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +class SettingsViewModel: ObservableObject { + let identity: Identity + + init(identity: Identity) { + self.identity = identity + } +} diff --git a/Shared/Views/AddIdentityView.swift b/Shared/Views/AddIdentityView.swift index a3d0240..fda4138 100644 --- a/Shared/Views/AddIdentityView.swift +++ b/Shared/Views/AddIdentityView.swift @@ -36,13 +36,15 @@ extension AddIdentityView { } } +#if DEBUG struct AddAccountView_Previews: PreviewProvider { static var previews: some View { AddIdentityView(viewModel: AddIdentityViewModel( - networkClient: MastodonClient(configuration: .stubbing), + networkClient: MastodonClient.development, // swiftlint:disable force_try identityDatabase: try! IdentityDatabase(inMemory: true), // swiftlint:enable force_try secrets: Secrets(keychain: FakeKeychain()))) } } +#endif diff --git a/Shared/Views/ContentView.swift b/Shared/Views/ContentView.swift index 4dfa7b2..4ffd906 100644 --- a/Shared/Views/ContentView.swift +++ b/Shared/Views/ContentView.swift @@ -7,8 +7,8 @@ struct ContentView: View { @Environment(\.scenePhase) private var scenePhase var body: some View { - if sceneViewModel.identity != nil { - mainNavigation + if let identity = sceneViewModel.identity { + mainNavigation(identity: identity) .onChange(of: scenePhase) { if case .active = $0 { sceneViewModel.refreshIdentity() @@ -22,11 +22,11 @@ struct ContentView: View { } private extension ContentView { - private var mainNavigation: some View { + private func mainNavigation(identity: Identity) -> some View { #if os(macOS) return SidebarNavigation().frame(minWidth: 900, maxWidth: .infinity, minHeight: 500, maxHeight: .infinity) #else - return TabNavigation() + return TabNavigation(identity: identity) #endif } @@ -35,8 +35,11 @@ private extension ContentView { } } -//struct ContentView_Previews: PreviewProvider { -// static var previews: some View { -// ContentView() -// } -//} +#if DEBUG +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .environmentObject(SceneViewModel.development) + } +} +#endif diff --git a/Shared/Views/SettingsView.swift b/Shared/Views/SettingsView.swift new file mode 100644 index 0000000..a3670fe --- /dev/null +++ b/Shared/Views/SettingsView.swift @@ -0,0 +1,33 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import SwiftUI + +struct SettingsView: View { + @StateObject var viewModel: SettingsViewModel + @EnvironmentObject var sceneViewModel: SceneViewModel + + var body: some View { + NavigationView { + Form { + Text(viewModel.identity.handle) + } + .navigationBarTitle(Text("settings"), displayMode: .inline) + .navigationBarItems( + leading: Button { + sceneViewModel.presentingSettings.toggle() + } label: { + Image(systemName: "xmark.circle.fill").imageScale(.large) + }) + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} + +#if DEBUG +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView(viewModel: SettingsViewModel(identity: .development)) + .environmentObject(SceneViewModel.development) + } +} +#endif diff --git a/Shared/Views/TimelineView.swift b/Shared/Views/TimelineView.swift index c5e76a2..c310456 100644 --- a/Shared/Views/TimelineView.swift +++ b/Shared/Views/TimelineView.swift @@ -8,8 +8,10 @@ struct TimelineView: View { } } +#if DEBUG struct TimelineView_Previews: PreviewProvider { static var previews: some View { TimelineView() } } +#endif diff --git a/iOS/TabNavigation.swift b/iOS/TabNavigation.swift index 2e6eda3..27089ab 100644 --- a/iOS/TabNavigation.swift +++ b/iOS/TabNavigation.swift @@ -1,8 +1,12 @@ // Copyright © 2020 Metabolist. All rights reserved. import SwiftUI +import KingfisherSwiftUI +import struct Kingfisher.DownsamplingImageProcessor +import struct Kingfisher.RoundCornerImageProcessor struct TabNavigation: View { + let identity: Identity @EnvironmentObject var sceneViewModel: SceneViewModel var body: some View { @@ -11,6 +15,7 @@ struct TabNavigation: View { NavigationView { view(topLevelNavigation: topLevelNavigation) } + .navigationViewStyle(StackNavigationViewStyle()) .tabItem { Label(topLevelNavigation.title, systemImage: topLevelNavigation.systemImageName) .accessibility(label: Text(topLevelNavigation.title)) @@ -18,6 +23,10 @@ struct TabNavigation: View { .tag(topLevelNavigation) } } + .sheet(isPresented: $sceneViewModel.presentingSettings) { + SettingsView(viewModel: SettingsViewModel(identity: identity)) + .environmentObject(sceneViewModel) + } } } @@ -27,7 +36,24 @@ private extension TabNavigation { switch topLevelNavigation { case .timelines: TimelineView() - .navigationBarTitle(sceneViewModel.identity?.handle ?? "", displayMode: .inline) + .navigationBarTitle(identity.handle, displayMode: .inline) + .navigationBarItems( + leading: Button { + sceneViewModel.presentingSettings.toggle() + } label: { + KFImage(identity.account?.avatar + ?? identity.instance?.thumbnail, + options: [ + .processor( + DownsamplingImageProcessor(size: CGSize(width: 28, height: 28)) + .append(another: RoundCornerImageProcessor(radius: .widthFraction(0.5))) + ), + .scaleFactor(UIScreen.main.scale), + .cacheOriginalImage + ]) + .placeholder { Image(systemName: "gear") } + .renderingMode(.original) + }) default: Text(topLevelNavigation.title) } } @@ -36,8 +62,11 @@ private extension TabNavigation { // MARK: Preview +#if DEBUG struct TabNavigation_Previews: PreviewProvider { static var previews: some View { - TabNavigation() + TabNavigation(identity: .development) + .environmentObject(SceneViewModel.development) } } +#endif