From 260fa83cf91541cfc782c2ff206e14f3e6f927a6 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Thu, 10 Oct 2024 11:10:29 -0400 Subject: [PATCH 01/14] iosapp: prep for sending pairql events --- api/Package.swift | 2 + .../Resolvers/LogInterestingEvent.swift | 2 +- api/Sources/Api/PairQL/iOS/LogIOSEvent.swift | 25 ++++++++ api/Sources/Api/PairQL/iOS/iOSRoute.swift | 12 ++++ api/Sources/Api/Routes/PairQL.swift | 12 ++++ .../iOSPairResolvers/iOSResolverTests.swift | 35 +++++++++++ iosapp/ios-poc.xcodeproj/project.pbxproj | 28 +++++++-- .../xcshareddata/swiftpm/Package.resolved | 20 +++++- .../{ios_pocApp.swift => IOSAppEntry.swift} | 2 +- iosapp/lib-ios/Package.resolved | 20 +++++- iosapp/lib-ios/Package.swift | 3 +- iosapp/lib-ios/Sources/App/App+Install.swift | 61 +++++++++++++++++++ iosapp/lib-ios/Sources/App/ContentView.swift | 6 +- .../Sources/LiveApiClient/ApiRequest.swift | 2 +- pairql-iosapp/Package.resolved | 51 ++++++++++++++++ pairql-iosapp/Package.swift | 29 +++++++++ pairql-iosapp/Sources/IOSRoute/IOSRoute.swift | 15 +++++ .../Sources/IOSRoute/LogIOSEvent.swift | 33 ++++++++++ pairql-iosapp/project.json | 10 +++ .../Tests/MacAppRouteTests/RouterTests.swift | 4 +- pairql/Package.swift | 4 +- pairql/Sources/PairQL/Types.swift | 5 ++ x-expect/Package.swift | 4 +- x-kit/Package.swift | 4 +- 24 files changed, 364 insertions(+), 25 deletions(-) create mode 100644 api/Sources/Api/PairQL/iOS/LogIOSEvent.swift create mode 100644 api/Sources/Api/PairQL/iOS/iOSRoute.swift create mode 100644 api/Tests/ApiTests/iOSPairResolvers/iOSResolverTests.swift rename iosapp/ios-poc/{ios_pocApp.swift => IOSAppEntry.swift} (93%) create mode 100644 pairql-iosapp/Package.resolved create mode 100644 pairql-iosapp/Package.swift create mode 100644 pairql-iosapp/Sources/IOSRoute/IOSRoute.swift create mode 100644 pairql-iosapp/Sources/IOSRoute/LogIOSEvent.swift create mode 100644 pairql-iosapp/project.json diff --git a/api/Package.swift b/api/Package.swift index 0e8ad970..27f8e85e 100644 --- a/api/Package.swift +++ b/api/Package.swift @@ -18,6 +18,7 @@ let package = Package( .package(path: "../gertie"), .package(path: "../pairql"), .package(path: "../pairql-macapp"), + .package(path: "../pairql-iosapp"), .package(path: "../ts-interop"), .package(path: "../x-aws"), .package(path: "../x-sendgrid"), @@ -40,6 +41,7 @@ let package = Package( .product(name: "Gertie", package: "gertie"), .product(name: "PairQL", package: "pairql"), .product(name: "MacAppRoute", package: "pairql-macapp"), + .product(name: "IOSRoute", package: "pairql-iosapp"), .product(name: "TaggedTime", package: "swift-tagged"), .product(name: "VaporRouting", package: "vapor-routing"), .product(name: "Dependencies", package: "swift-dependencies"), diff --git a/api/Sources/Api/PairQL/MacApp/Resolvers/LogInterestingEvent.swift b/api/Sources/Api/PairQL/MacApp/Resolvers/LogInterestingEvent.swift index 86ae080f..ede49378 100644 --- a/api/Sources/Api/PairQL/MacApp/Resolvers/LogInterestingEvent.swift +++ b/api/Sources/Api/PairQL/MacApp/Resolvers/LogInterestingEvent.swift @@ -84,7 +84,7 @@ private func errorLoc(_ detail: String) -> String { } } -private func githubSearch(_ eventId: String) -> String { +func githubSearch(_ eventId: String) -> String { Slack.link( to: "https://github.com/search?q=repo%3Agertrude-app%2Fswift%20\(eventId)&type=code", withText: eventId diff --git a/api/Sources/Api/PairQL/iOS/LogIOSEvent.swift b/api/Sources/Api/PairQL/iOS/LogIOSEvent.swift new file mode 100644 index 00000000..10c1c26a --- /dev/null +++ b/api/Sources/Api/PairQL/iOS/LogIOSEvent.swift @@ -0,0 +1,25 @@ +import IOSRoute + +extension LogIOSEvent: Resolver { + static func resolve(with input: Input, in context: Context) async throws -> Output { + let detail = "\(input.detail ?? ""), " + [ + "device: `\(input.deviceType)`", + "iOS: `\(input.iOSVersion)`", + "vendorId: `\(input.vendorId?.lowercased ?? "(nil)")`", + ].joined(separator: ", ") + + try await context.db.create(InterestingEvent( + eventId: input.eventId, + kind: input.kind, + context: "ios", + detail: detail + )) + + if context.env.mode == .prod { + await with(dependency: \.slack) + .sysLog("iOS app event: \(githubSearch(input.eventId)) \(detail)") + } + + return .success + } +} diff --git a/api/Sources/Api/PairQL/iOS/iOSRoute.swift b/api/Sources/Api/PairQL/iOS/iOSRoute.swift new file mode 100644 index 00000000..ff696100 --- /dev/null +++ b/api/Sources/Api/PairQL/iOS/iOSRoute.swift @@ -0,0 +1,12 @@ +import IOSRoute +import Vapor + +extension IOSRoute: RouteResponder { + static func respond(to route: Self, in context: Context) async throws -> Response { + switch route { + case .logIOSEvent(let input): + let output = try await LogIOSEvent.resolve(with: input, in: context) + return try await self.respond(with: output) + } + } +} diff --git a/api/Sources/Api/Routes/PairQL.swift b/api/Sources/Api/Routes/PairQL.swift index c9699e6f..471b5293 100644 --- a/api/Sources/Api/Routes/PairQL.swift +++ b/api/Sources/Api/Routes/PairQL.swift @@ -1,4 +1,5 @@ import Dependencies +import IOSRoute import MacAppRoute import URLRouting import Vapor @@ -17,6 +18,7 @@ enum PairQLRoute: Equatable, RouteResponder { case dashboard(DashboardRoute) case macApp(MacAppRoute) case superAdmin(SuperAdminRoute) + case iOS(IOSRoute) nonisolated(unsafe) static let router = OneOf { Route(.case(PairQLRoute.macApp)) { @@ -24,6 +26,11 @@ enum PairQLRoute: Equatable, RouteResponder { Path { "macos-app" } MacAppRoute.router } + Route(.case(PairQLRoute.iOS)) { + Method("POST") + Path { "ios-app" } + IOSRoute.router + } Route(.case(PairQLRoute.dashboard)) { Method("POST") Path { "dashboard" } @@ -44,6 +51,8 @@ enum PairQLRoute: Equatable, RouteResponder { return try await DashboardRoute.respond(to: dashboardRoute, in: context) case .superAdmin(let superAdminRoute): return try await SuperAdminRoute.respond(to: superAdminRoute, in: context) + case .iOS(let iosAppRoute): + return try await IOSRoute.respond(to: iosAppRoute, in: context) } } @@ -107,6 +116,9 @@ private func logOperation(_ route: PairQLRoute, _ request: Request) { case .superAdmin: request.logger .notice("PairQL request: \("SuperAdmin".cyan) \(operation.yellow)") + case .iOS: + request.logger + .notice("PairQL request: \("iOS".blue) \(operation.yellow)") } } diff --git a/api/Tests/ApiTests/iOSPairResolvers/iOSResolverTests.swift b/api/Tests/ApiTests/iOSPairResolvers/iOSResolverTests.swift new file mode 100644 index 00000000..7651cfd4 --- /dev/null +++ b/api/Tests/ApiTests/iOSPairResolvers/iOSResolverTests.swift @@ -0,0 +1,35 @@ +import DuetSQL +import IOSRoute +import XCTest +import XExpect + +@testable import Api + +final class iOSResolverTests: ApiTestCase { + func testLogIOSEvent() async throws { + let eventId = UUID().uuidString + let vendorId = UUID() + _ = try await LogIOSEvent.resolve( + with: .init( + eventId: eventId, + kind: "event", + deviceType: "iPhone", + iOSVersion: "18.0.1", + vendorId: vendorId, + detail: "first launch" + ), + in: .mock + ) + + let retrieved = try await InterestingEvent.query() + .where(.eventId == eventId) + .first(in: self.db) + + expect(retrieved.kind).toEqual("event") + expect(retrieved.context).toEqual("ios") + expect(retrieved.detail!).toContain("iPhone") + expect(retrieved.detail!).toContain("18.0.1") + expect(retrieved.detail!).toContain(vendorId.lowercased) + expect(retrieved.detail!).toContain("first launch") + } +} diff --git a/iosapp/ios-poc.xcodeproj/project.pbxproj b/iosapp/ios-poc.xcodeproj/project.pbxproj index 8818ff2d..74b3f601 100644 --- a/iosapp/ios-poc.xcodeproj/project.pbxproj +++ b/iosapp/ios-poc.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 1FE271D2FDBE4C6122B68A8A /* BgGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A47408B05AA4E9E2C4042A /* BgGradient.swift */; }; 580A5B546430D0B85CDE67A4 /* FeatureLI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFBB6E5EDFE1B2D7B40D17A6 /* FeatureLI.swift */; }; 7F7E54B62CB576B20012844E /* Filter in Frameworks */ = {isa = PBXBuildFile; productRef = 7F7E54B52CB576B20012844E /* Filter */; }; - 7FA7B8472AEB238700363B53 /* ios_pocApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA7B8462AEB238700363B53 /* ios_pocApp.swift */; }; + 7FA7B8472AEB238700363B53 /* IOSAppEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */; }; 7FA7B84B2AEB238800363B53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7FA7B84A2AEB238800363B53 /* Assets.xcassets */; }; 7FA7B84E2AEB238800363B53 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7FA7B84D2AEB238800363B53 /* Preview Assets.xcassets */; }; 7FA7B85B2AEB243400363B53 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA7B85A2AEB243400363B53 /* NetworkExtension.framework */; }; @@ -63,7 +63,7 @@ 5FF145756F19546462ADE7D9 /* PrimaryButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; 7F4A14502CB5843500FD6ABE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 7FA7B8432AEB238700363B53 /* ios-poc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-poc.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7FA7B8462AEB238700363B53 /* ios_pocApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ios_pocApp.swift; sourceTree = ""; }; + 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSAppEntry.swift; sourceTree = ""; }; 7FA7B84A2AEB238800363B53 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7FA7B84D2AEB238800363B53 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 7FA7B8582AEB243400363B53 /* filter.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = filter.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -164,7 +164,7 @@ children = ( 7F4A14502CB5843500FD6ABE /* Info.plist */, 7FA7B8682AEB271800363B53 /* ios-poc.entitlements */, - 7FA7B8462AEB238700363B53 /* ios_pocApp.swift */, + 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */, 7FA7B84A2AEB238800363B53 /* Assets.xcassets */, 7FA7B84C2AEB238800363B53 /* Preview Content */, ); @@ -365,12 +365,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7FA7B8472AEB238700363B53 /* ios_pocApp.swift in Sources */, 1FE271D2FDBE4C6122B68A8A /* BgGradient.swift in Sources */, 580A5B546430D0B85CDE67A4 /* FeatureLI.swift in Sources */, 03E917BFA4D8CBC845611955 /* Colors.swift in Sources */, CECCA8D914450391BB017E01 /* PrimaryButton.swift in Sources */, F7AEF6187A4EA032CA3A38F7 /* PreReqs.swift in Sources */, + 7FA7B8472AEB238700363B53 /* IOSAppEntry.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -456,7 +456,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -510,7 +510,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -551,6 +551,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -589,6 +590,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -617,6 +619,9 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.filter"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -644,6 +649,9 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.filter"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -675,6 +683,10 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.controller"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -707,6 +719,10 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.controller"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b41a3b70..17990c01 100644 --- a/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "294cc5392bc6679434856a5c441a94b895b5c278e5f25fa185fd58083dcee459", + "originHash" : "e63dc6b23ee4c31b02d892ff2ffcb166c0419168b7833fd893d2bf7072b738dd", "pins" : [ { "identity" : "combine-schedulers", @@ -91,6 +91,15 @@ "version" : "2.2.1" } }, + { + "identity" : "swift-parsing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-parsing", + "state" : { + "revision" : "a0e7d73f462c1c38c59dc40a3969ac40cea42950", + "version" : "0.13.0" + } + }, { "identity" : "swift-perception", "kind" : "remoteSourceControl", @@ -109,6 +118,15 @@ "version" : "600.0.1" } }, + { + "identity" : "swift-url-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gertrude-app/swift-url-routing", + "state" : { + "branch" : "1cf1ca6", + "revision" : "1cf1ca67f4a4e442a599473e320049a85cd31588" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/iosapp/ios-poc/ios_pocApp.swift b/iosapp/ios-poc/IOSAppEntry.swift similarity index 93% rename from iosapp/ios-poc/ios_pocApp.swift rename to iosapp/ios-poc/IOSAppEntry.swift index dbc10fbb..9d232150 100644 --- a/iosapp/ios-poc/ios_pocApp.swift +++ b/iosapp/ios-poc/IOSAppEntry.swift @@ -3,7 +3,7 @@ import ComposableArchitecture import SwiftUI @main -struct ios_pocApp: App { +struct IOSAppEntry: App { let store: StoreOf init() { diff --git a/iosapp/lib-ios/Package.resolved b/iosapp/lib-ios/Package.resolved index ab0da880..277a056c 100644 --- a/iosapp/lib-ios/Package.resolved +++ b/iosapp/lib-ios/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7cd15ad1fa8aaf83ad148fa4bb78ed2b0b9608033ad6e39929da31d436a1ee24", + "originHash" : "f3a15af06196a2299b9cc4d8b5cea622813d35446bd8878e8b16ad6870196668", "pins" : [ { "identity" : "combine-schedulers", @@ -91,6 +91,15 @@ "version" : "2.2.1" } }, + { + "identity" : "swift-parsing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-parsing", + "state" : { + "revision" : "a0e7d73f462c1c38c59dc40a3969ac40cea42950", + "version" : "0.13.0" + } + }, { "identity" : "swift-perception", "kind" : "remoteSourceControl", @@ -109,6 +118,15 @@ "version" : "600.0.1" } }, + { + "identity" : "swift-url-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gertrude-app/swift-url-routing", + "state" : { + "branch" : "1cf1ca6", + "revision" : "1cf1ca67f4a4e442a599473e320049a85cd31588" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/iosapp/lib-ios/Package.swift b/iosapp/lib-ios/Package.swift index a3556daa..7a61b62c 100644 --- a/iosapp/lib-ios/Package.swift +++ b/iosapp/lib-ios/Package.swift @@ -1,5 +1,4 @@ // swift-tools-version: 5.10 - import PackageDescription let package = Package( @@ -14,12 +13,14 @@ let package = Package( url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.0.0" ), + .package(path: "../../pairql-iosapp"), ], targets: [ .target( name: "App", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "IOSRoute", package: "pairql-iosapp"), ] ), .target( diff --git a/iosapp/lib-ios/Sources/App/App+Install.swift b/iosapp/lib-ios/Sources/App/App+Install.swift index 64b6e1b0..8059e10c 100644 --- a/iosapp/lib-ios/Sources/App/App+Install.swift +++ b/iosapp/lib-ios/Sources/App/App+Install.swift @@ -1,7 +1,68 @@ import FamilyControls +import IOSRoute import NetworkExtension import os.log +#if os(iOS) + import UIKit +#endif + +func logEvent(id: String, detail: String?) async { + let payload = LogIOSEvent.Input( + eventId: id, + kind: "ios", + deviceType: Device.current.type, + iOSVersion: Device.current.iOSVersion, + vendorId: Device.current.vendorId, + detail: detail + ) + do { + let router = IOSRoute.router.baseURL(.gertrudeApi) + var request = try router.request(for: .logIOSEvent(payload)) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + request.httpMethod = "POST" + _ = try await URLSession.shared.data(for: request) + } catch { + os_log("[G•] error logging event: %{public}s", String(reflecting: error)) + } +} + +func foo() { + let date = Date() + UserDefaults.standard.set(date, forKey: "savedDate") +} + +extension String { + static var gertrudeApi: String { + #if DEBUG + // just run-api-ip + return "http://192.168.10.227:8080/pairql/ios-app" + #else + return "https://api.gertrude.app/pairql/ios-app" + #endif + } +} + +struct Device { + var type: String + var iOSVersion: String + var vendorId: UUID? +} + +extension Device { + static var current: Device { + #if os(iOS) + Device( + type: UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone", + iOSVersion: UIDevice.current.systemVersion, + vendorId: UIDevice.current.identifierForVendor + ) + #else + Device(type: "iPhone", iOSVersion: "18.0.1", vendorId: nil) + #endif + } +} + // @see https://developer.apple.com/documentation/familycontrols/familycontrolserror public enum AuthFailureReason: Error, Equatable { // The device isn't signed into a valid iCloud account (also? .individual?) diff --git a/iosapp/lib-ios/Sources/App/ContentView.swift b/iosapp/lib-ios/Sources/App/ContentView.swift index 934330f1..afec0b31 100644 --- a/iosapp/lib-ios/Sources/App/ContentView.swift +++ b/iosapp/lib-ios/Sources/App/ContentView.swift @@ -59,11 +59,7 @@ public struct ContentView: View { public extension View { var deviceType: String { - #if os(iOS) - UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone" - #else - "iPhone" - #endif + Device.current.type } } diff --git a/macapp/App/Sources/LiveApiClient/ApiRequest.swift b/macapp/App/Sources/LiveApiClient/ApiRequest.swift index 01696754..c0f77c00 100644 --- a/macapp/App/Sources/LiveApiClient/ApiRequest.swift +++ b/macapp/App/Sources/LiveApiClient/ApiRequest.swift @@ -48,7 +48,7 @@ private func request( private func data(for request: URLRequest) async throws -> (Data, URLResponse) { return try await withCheckedThrowingContinuation { continuation in URLSession.shared.dataTask(with: request) { data, response, err in - if let err = err { + if let err { continuation.resume(throwing: err) return } diff --git a/pairql-iosapp/Package.resolved b/pairql-iosapp/Package.resolved new file mode 100644 index 00000000..32d3d315 --- /dev/null +++ b/pairql-iosapp/Package.resolved @@ -0,0 +1,51 @@ +{ + "originHash" : "fb4d4e1c156d45d90e7177ad4bf164f1e6c8876ef1a802f1937b89e3923fecb1", + "pins" : [ + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" + } + }, + { + "identity" : "swift-parsing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-parsing", + "state" : { + "revision" : "a0e7d73f462c1c38c59dc40a3969ac40cea42950", + "version" : "0.13.0" + } + }, + { + "identity" : "swift-url-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gertrude-app/swift-url-routing", + "state" : { + "branch" : "1cf1ca6", + "revision" : "1cf1ca67f4a4e442a599473e320049a85cd31588" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "96beb108a57f24c8476ae1f309239270772b2940", + "version" : "1.2.5" + } + } + ], + "version" : 3 +} diff --git a/pairql-iosapp/Package.swift b/pairql-iosapp/Package.swift new file mode 100644 index 00000000..ec43e998 --- /dev/null +++ b/pairql-iosapp/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.10 +import PackageDescription + +let package = Package( + name: "IOSRoute", + platforms: [.macOS(.v10_15), .iOS(.v17)], + products: [ + .library(name: "IOSRoute", targets: ["IOSRoute"]), + ], + dependencies: [ + // fork avoids swift-syntax transitive dep via case-paths + .package(url: "https://github.com/gertrude-app/swift-url-routing", revision: "1cf1ca6"), + .package(path: "../pairql"), + ], + targets: [ + .target( + name: "IOSRoute", + dependencies: [ + .product(name: "URLRouting", package: "swift-url-routing"), + .product(name: "PairQL", package: "pairql"), + ], + swiftSettings: [.unsafeFlags([ + "-Xfrontend", "-warn-concurrency", + "-Xfrontend", "-enable-actor-data-race-checks", + "-Xfrontend", "-warnings-as-errors", + ])] + ), + ] +) diff --git a/pairql-iosapp/Sources/IOSRoute/IOSRoute.swift b/pairql-iosapp/Sources/IOSRoute/IOSRoute.swift new file mode 100644 index 00000000..4cb16ef3 --- /dev/null +++ b/pairql-iosapp/Sources/IOSRoute/IOSRoute.swift @@ -0,0 +1,15 @@ +import PairQL + +public enum IOSRoute: PairRoute { + case logIOSEvent(LogIOSEvent.Input) +} + +public extension IOSRoute { + nonisolated(unsafe) static let router: AnyParserPrinter = OneOf { + Route(.case(Self.logIOSEvent)) { + Operation(LogIOSEvent.self) + Body(.json(LogIOSEvent.Input.self)) + } + } + .eraseToAnyParserPrinter() +} diff --git a/pairql-iosapp/Sources/IOSRoute/LogIOSEvent.swift b/pairql-iosapp/Sources/IOSRoute/LogIOSEvent.swift new file mode 100644 index 00000000..36796694 --- /dev/null +++ b/pairql-iosapp/Sources/IOSRoute/LogIOSEvent.swift @@ -0,0 +1,33 @@ +import Foundation +import PairQL + +public struct LogIOSEvent: Pair { + public static let auth: ClientAuth = .none + + public struct Input: PairInput { + public var eventId: String + public var kind: String + public var deviceType: String // "iPhone" | "iPad" + public var iOSVersion: String // "18.0.1" + public var vendorId: UUID? + public var detail: String? + + public init( + eventId: String, + kind: String, + deviceType: String, + iOSVersion: String, + vendorId: UUID? = nil, + detail: String? = nil + ) { + self.eventId = eventId + self.kind = kind + self.deviceType = deviceType + self.iOSVersion = iOSVersion + self.vendorId = vendorId + self.detail = detail + } + } + + public typealias Output = Infallible +} diff --git a/pairql-iosapp/project.json b/pairql-iosapp/project.json new file mode 100644 index 00000000..69327bbc --- /dev/null +++ b/pairql-iosapp/project.json @@ -0,0 +1,10 @@ +{ + "root": "pairql-iosapp", + "projectType": "library", + "implicitDependencies": ["pairql"], + "targets": { + "build": { + "command": "cd pairql-iosapp && swift build" + } + } +} diff --git a/pairql-macapp/Tests/MacAppRouteTests/RouterTests.swift b/pairql-macapp/Tests/MacAppRouteTests/RouterTests.swift index 4db2f9ab..45ca7af1 100644 --- a/pairql-macapp/Tests/MacAppRouteTests/RouterTests.swift +++ b/pairql-macapp/Tests/MacAppRouteTests/RouterTests.swift @@ -22,7 +22,7 @@ final class RouterTests: XCTestCase { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(input) - let matched = try router.match(request: request) + let matched = try self.router.match(request: request) let expected = MacAppRoute.userAuthed(self.token, .createSignedScreenshotUpload(input)) expect(matched).toEqual(expected) } @@ -35,7 +35,7 @@ final class RouterTests: XCTestCase { expect(missingHeader).toEqual(nil) request.addValue(self.token.uuidString, forHTTPHeaderField: "X-UserToken") - let matched = try router.match(request: request) + let matched = try self.router.match(request: request) expect(matched).toEqual(route) } } diff --git a/pairql/Package.swift b/pairql/Package.swift index d3ac2d5d..82f6df17 100644 --- a/pairql/Package.swift +++ b/pairql/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.10 import PackageDescription let package = Package( name: "PairQL", - platforms: [.macOS(.v10_15)], + platforms: [.macOS(.v10_15), .iOS(.v17)], products: [ .library(name: "PairQL", targets: ["PairQL"]), ], diff --git a/pairql/Sources/PairQL/Types.swift b/pairql/Sources/PairQL/Types.swift index c3ea3ed3..0fc20ba0 100644 --- a/pairql/Sources/PairQL/Types.swift +++ b/pairql/Sources/PairQL/Types.swift @@ -81,6 +81,11 @@ public struct SuccessOutput: PairOutput { public static var failure: Self { .init(false) } } +public struct Infallible: PairOutput { + private init() {} + public static var success: Self { .init() } +} + extension String: PairOutput {} extension String: PairInput {} extension UUID: PairInput {} diff --git a/x-expect/Package.swift b/x-expect/Package.swift index 0600dbf9..7b720902 100644 --- a/x-expect/Package.swift +++ b/x-expect/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.10 import PackageDescription let package = Package( name: "XExpect", - platforms: [.macOS(.v10_15)], + platforms: [.macOS(.v10_15), .iOS(.v17)], products: [ .library(name: "XExpect", targets: ["XExpect"]), ], diff --git a/x-kit/Package.swift b/x-kit/Package.swift index ad148e79..6fb50835 100644 --- a/x-kit/Package.swift +++ b/x-kit/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.10 import PackageDescription let package = Package( name: "XKit", - platforms: [.macOS(.v10_15)], + platforms: [.macOS(.v10_15), .iOS(.v17)], products: [ .library(name: "XCore", targets: ["XCore"]), .library(name: "XBase64", targets: ["XBase64"]), From 9d1d4b2f358b215e8ead09344a28e5695857656f Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Thu, 10 Oct 2024 12:40:10 -0400 Subject: [PATCH 02/14] iosapp: log app launch first time only --- iosapp/lib-ios/Package.swift | 2 +- iosapp/lib-ios/Sources/App/ApiClient.swift | 45 +++++ iosapp/lib-ios/Sources/App/App+Install.swift | 191 ------------------ iosapp/lib-ios/Sources/App/App+Types.swift | 81 ++++++++ iosapp/lib-ios/Sources/App/App.swift | 30 ++- .../lib-ios/Sources/App/StorageClient.swift | 31 +++ iosapp/lib-ios/Sources/App/SystemClient.swift | 116 +++++++++++ iosapp/lib-ios/Tests/AppTests/AppTests.swift | 55 ++++- 8 files changed, 353 insertions(+), 198 deletions(-) create mode 100644 iosapp/lib-ios/Sources/App/ApiClient.swift delete mode 100644 iosapp/lib-ios/Sources/App/App+Install.swift create mode 100644 iosapp/lib-ios/Sources/App/App+Types.swift create mode 100644 iosapp/lib-ios/Sources/App/StorageClient.swift create mode 100644 iosapp/lib-ios/Sources/App/SystemClient.swift diff --git a/iosapp/lib-ios/Package.swift b/iosapp/lib-ios/Package.swift index 7a61b62c..6777009c 100644 --- a/iosapp/lib-ios/Package.swift +++ b/iosapp/lib-ios/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "App", - platforms: [.macOS(.v13), .iOS(.v17)], + platforms: [.macOS(.v14), .iOS(.v17)], products: [ .library(name: "App", targets: ["App"]), .library(name: "Filter", targets: ["Filter"]), diff --git a/iosapp/lib-ios/Sources/App/ApiClient.swift b/iosapp/lib-ios/Sources/App/ApiClient.swift new file mode 100644 index 00000000..811ff9e6 --- /dev/null +++ b/iosapp/lib-ios/Sources/App/ApiClient.swift @@ -0,0 +1,45 @@ +import Dependencies +import DependenciesMacros +import Foundation +import IOSRoute +import os.log + +@DependencyClient +struct ApiClient: Sendable { + var logEvent: @Sendable (_ id: String, _ detail: String?) async -> Void +} + +extension ApiClient: TestDependencyKey { + public static let testValue = ApiClient() +} + +extension ApiClient: DependencyKey { + public static var liveValue: ApiClient { + ApiClient { id, detail in + let payload = LogIOSEvent.Input( + eventId: id, + kind: "ios", + deviceType: Device.current.type, + iOSVersion: Device.current.iOSVersion, + vendorId: Device.current.vendorId, + detail: detail + ) + do { + let router = IOSRoute.router.baseURL(.gertrudeApi) + var request = try router.request(for: .logIOSEvent(payload)) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + request.httpMethod = "POST" + _ = try await URLSession.shared.data(for: request) + } catch { + os_log("[G•] error logging event: %{public}s", String(reflecting: error)) + } + } + } +} + +extension DependencyValues { + var api: ApiClient { + get { self[ApiClient.self] } + set { self[ApiClient.self] = newValue } + } +} diff --git a/iosapp/lib-ios/Sources/App/App+Install.swift b/iosapp/lib-ios/Sources/App/App+Install.swift deleted file mode 100644 index 8059e10c..00000000 --- a/iosapp/lib-ios/Sources/App/App+Install.swift +++ /dev/null @@ -1,191 +0,0 @@ -import FamilyControls -import IOSRoute -import NetworkExtension -import os.log - -#if os(iOS) - import UIKit -#endif - -func logEvent(id: String, detail: String?) async { - let payload = LogIOSEvent.Input( - eventId: id, - kind: "ios", - deviceType: Device.current.type, - iOSVersion: Device.current.iOSVersion, - vendorId: Device.current.vendorId, - detail: detail - ) - do { - let router = IOSRoute.router.baseURL(.gertrudeApi) - var request = try router.request(for: .logIOSEvent(payload)) - request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData - request.httpMethod = "POST" - _ = try await URLSession.shared.data(for: request) - } catch { - os_log("[G•] error logging event: %{public}s", String(reflecting: error)) - } -} - -func foo() { - let date = Date() - UserDefaults.standard.set(date, forKey: "savedDate") -} - -extension String { - static var gertrudeApi: String { - #if DEBUG - // just run-api-ip - return "http://192.168.10.227:8080/pairql/ios-app" - #else - return "https://api.gertrude.app/pairql/ios-app" - #endif - } -} - -struct Device { - var type: String - var iOSVersion: String - var vendorId: UUID? -} - -extension Device { - static var current: Device { - #if os(iOS) - Device( - type: UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone", - iOSVersion: UIDevice.current.systemVersion, - vendorId: UIDevice.current.identifierForVendor - ) - #else - Device(type: "iPhone", iOSVersion: "18.0.1", vendorId: nil) - #endif - } -} - -// @see https://developer.apple.com/documentation/familycontrols/familycontrolserror -public enum AuthFailureReason: Error, Equatable { - // The device isn't signed into a valid iCloud account (also? .individual?) - case invalidAccountType - /// Another authorized app already provides parental controls - case authorizationConflict - case unexpected(Unexpected) - case other(String) - /// Device must be connected to the network in order to enroll with parental controls - case networkError - /// The device must have a passcode set in order for an individual to enroll with parental controls - case passcodeRequired - /// The parent or guardian cancelled a request for authorization - case authorizationCanceled - - public enum Unexpected: Equatable { - /// The method's arguments are invalid - case invalidArgument - /// The system failed to set up the Family Control famework - case unavailable - /// A restriction prevents your app from using Family Controls on this device - case restricted - } -} - -public enum FilterInstallError: Error, Equatable { - case configurationInvalid - case configurationDisabled - /// another process modified the filter configuration - /// since the last time the app loaded the configuration - case configurationStale - /// removing the configuration isn't allowed - case configurationCannotBeRemoved - case configurationPermissionDenied - case configurationInternalError - case unexpected(String) -} - -// TODO: extract into @Dependency -func requestAuthorization() async -> Result { - // TODO: figure out SPM things... - #if os(iOS) - do { - #if DEBUG - try await AuthorizationCenter.shared.requestAuthorization(for: .individual) - #else - try await AuthorizationCenter.shared.requestAuthorization(for: .child) - #endif - } catch let familyError as FamilyControlsError { - switch familyError { - case .invalidAccountType: - return .failure(.invalidAccountType) - case .authorizationConflict: - return .failure(.authorizationConflict) - case .authorizationCanceled: - return .failure(.authorizationCanceled) - case .networkError: - return .failure(.networkError) - case .authenticationMethodUnavailable: - return .failure(.passcodeRequired) - case .restricted: - return .failure(.unexpected(.restricted)) - case .unavailable: - return .failure(.unexpected(.unavailable)) - case .invalidArgument: - return .failure(.unexpected(.invalidArgument)) - @unknown default: - return .failure(.other(String(reflecting: familyError))) - } - } catch { - return .failure(.other(String(reflecting: error))) - } - #endif - return .success(()) -} - -func saveConfiguration() async -> Result { - // not sure this is necessary, but doesn't seem to hurt and might ensure clean slate - try? await NEFilterManager.shared().removeFromPreferences() - - if NEFilterManager.shared().providerConfiguration == nil { - let newConfiguration = NEFilterProviderConfiguration() - newConfiguration.username = "IOSPoc" - newConfiguration.organization = "GertrudeSkunk" - #if os(iOS) - newConfiguration.filterBrowsers = true - #endif - newConfiguration.filterSockets = true - NEFilterManager.shared().providerConfiguration = newConfiguration - } - NEFilterManager.shared().isEnabled = true - do { - try await NEFilterManager.shared().saveToPreferences() - return .success(()) - } catch { - switch NEFilterManagerError(rawValue: (error as NSError).code) { - case .some(.configurationInvalid): - return .failure(.configurationInvalid) - case .some(.configurationDisabled): - return .failure(.configurationDisabled) - case .some(.configurationStale): - return .failure(.configurationStale) - case .some(.configurationCannotBeRemoved): - return .failure(.configurationCannotBeRemoved) - case .some(.configurationPermissionDenied): - return .failure(.configurationPermissionDenied) - case .some(.configurationInternalError): - return .failure(.configurationInternalError) - case .none: - return .failure(.unexpected(String(reflecting: error))) - @unknown default: - return .failure(.unexpected(String(reflecting: error))) - } - } -} - -func isRunning() async -> Bool { - do { - try await NEFilterManager.shared().loadFromPreferences() - return NEFilterManager.shared().isEnabled - } catch { - print("Error loading preferences: \(error)") - os_log("[G•] error loading preferences (isRunning()): %{public}s", String(reflecting: error)) - return false - } -} diff --git a/iosapp/lib-ios/Sources/App/App+Types.swift b/iosapp/lib-ios/Sources/App/App+Types.swift new file mode 100644 index 00000000..1595d484 --- /dev/null +++ b/iosapp/lib-ios/Sources/App/App+Types.swift @@ -0,0 +1,81 @@ +import FamilyControls +import IOSRoute +import NetworkExtension +import os.log + +#if os(iOS) + import UIKit +#endif + +struct Device { + var type: String + var iOSVersion: String + var vendorId: UUID? +} + +extension Device { + static var current: Device { + #if os(iOS) + Device( + type: UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone", + iOSVersion: UIDevice.current.systemVersion, + vendorId: UIDevice.current.identifierForVendor + ) + #else + Device(type: "iPhone", iOSVersion: "18.0.1", vendorId: nil) + #endif + } +} + +// @see https://developer.apple.com/documentation/familycontrols/familycontrolserror +public enum AuthFailureReason: Error, Equatable { + // The device isn't signed into a valid iCloud account (also? .individual?) + case invalidAccountType + /// Another authorized app already provides parental controls + case authorizationConflict + case unexpected(Unexpected) + case other(String) + /// Device must be connected to the network in order to enroll with parental controls + case networkError + /// The device must have a passcode set in order for an individual to enroll with parental controls + case passcodeRequired + /// The parent or guardian cancelled a request for authorization + case authorizationCanceled + + public enum Unexpected: Equatable { + /// The method's arguments are invalid + case invalidArgument + /// The system failed to set up the Family Control famework + case unavailable + /// A restriction prevents your app from using Family Controls on this device + case restricted + } +} + +public enum FilterInstallError: Error, Equatable { + case configurationInvalid + case configurationDisabled + /// another process modified the filter configuration + /// since the last time the app loaded the configuration + case configurationStale + /// removing the configuration isn't allowed + case configurationCannotBeRemoved + case configurationPermissionDenied + case configurationInternalError + case unexpected(String) +} + +extension String { + static var gertrudeApi: String { + #if DEBUG + // just run-api-ip + return "http://192.168.10.227:8080/pairql/ios-app" + #else + return "https://api.gertrude.app/pairql/ios-app" + #endif + } + + static var launchDateStorageKey: String { + "firstLaunchDate" + } +} diff --git a/iosapp/lib-ios/Sources/App/App.swift b/iosapp/lib-ios/Sources/App/App.swift index a0a8c74a..2dfa5292 100644 --- a/iosapp/lib-ios/Sources/App/App.swift +++ b/iosapp/lib-ios/Sources/App/App.swift @@ -1,16 +1,27 @@ import ComposableArchitecture +import Foundation @Reducer public struct AppReducer { @ObservableState public struct State: Equatable { public var appState: AppState + public var firstLaunch: Date? public init(appState: AppState = .launching) { self.appState = appState } } + @ObservationIgnored + @Dependency(\.api) var api + @ObservationIgnored + @Dependency(\.system) var system + @ObservationIgnored + @Dependency(\.storage) var storage + @ObservationIgnored + @Dependency(\.date.now) var now + // TODO: figure out why i can't use a root store enum public enum AppState: Equatable { case launching @@ -37,6 +48,7 @@ public struct AppReducer { case installFilterTapped case postInstallOkTapped case setRunning(Bool) + case setFirstLaunch(Date) } public var body: some Reducer { @@ -45,13 +57,25 @@ public struct AppReducer { case .appLaunched: return .run { send in - await send(.setRunning(await isRunning())) + await send(.setRunning(await self.system.filterRunning())) + if let firstLaunch = self.storage.object(forKey: .launchDateStorageKey) as? Date { + await send(.setFirstLaunch(firstLaunch)) + } else { + let now = self.now + self.storage.set(now, forKey: .launchDateStorageKey) + await send(.setFirstLaunch(now)) + await self.api.logEvent("dcd721aa", "first launch") + } } case .setRunning(true): state.appState = .running return .none + case .setFirstLaunch(let date): + state.firstLaunch = date + return .none + case .setRunning(false): state.appState = .welcome return .none @@ -63,7 +87,7 @@ public struct AppReducer { case .startAuthorizationTapped: state.appState = .authorizing return .run { send in - switch await requestAuthorization() { + switch await self.system.requestAuthorization() { case .success: await send(.authorizationSucceeded) case .failure(let reason): @@ -85,7 +109,7 @@ public struct AppReducer { case .installFilterTapped: return .run { send in - switch await saveConfiguration() { + switch await self.system.installFilter() { case .success: await send(.installSucceeded) case .failure(let error): diff --git a/iosapp/lib-ios/Sources/App/StorageClient.swift b/iosapp/lib-ios/Sources/App/StorageClient.swift new file mode 100644 index 00000000..74bc66d2 --- /dev/null +++ b/iosapp/lib-ios/Sources/App/StorageClient.swift @@ -0,0 +1,31 @@ +import Dependencies +import DependenciesMacros +import Foundation + +@DependencyClient +struct StorageClient: Sendable { + var object: @Sendable (_ forKey: String) -> Any? + var set: @Sendable (Any?, _ forKey: String) -> Void +} + +extension StorageClient: DependencyKey { + public static let liveValue = StorageClient( + object: { + key in UserDefaults.standard.object(forKey: key) + }, + set: { value, key in + UserDefaults.standard.set(value, forKey: key) + } + ) +} + +extension StorageClient: TestDependencyKey { + public static let testValue = StorageClient() +} + +extension DependencyValues { + var storage: StorageClient { + get { self[StorageClient.self] } + set { self[StorageClient.self] = newValue } + } +} diff --git a/iosapp/lib-ios/Sources/App/SystemClient.swift b/iosapp/lib-ios/Sources/App/SystemClient.swift new file mode 100644 index 00000000..cf470bd6 --- /dev/null +++ b/iosapp/lib-ios/Sources/App/SystemClient.swift @@ -0,0 +1,116 @@ +import Dependencies +import FamilyControls +import NetworkExtension +import os.log + +struct SystemClient: Sendable { + var requestAuthorization: @Sendable () async -> Result + var installFilter: @Sendable () async -> Result + var filterRunning: @Sendable () async -> Bool +} + +extension SystemClient: DependencyKey { + public static let liveValue = SystemClient( + requestAuthorization: { + #if os(iOS) + do { + #if DEBUG + try await AuthorizationCenter.shared.requestAuthorization(for: .individual) + #else + try await AuthorizationCenter.shared.requestAuthorization(for: .child) + #endif + } catch let familyError as FamilyControlsError { + switch familyError { + case .invalidAccountType: + return .failure(.invalidAccountType) + case .authorizationConflict: + return .failure(.authorizationConflict) + case .authorizationCanceled: + return .failure(.authorizationCanceled) + case .networkError: + return .failure(.networkError) + case .authenticationMethodUnavailable: + return .failure(.passcodeRequired) + case .restricted: + return .failure(.unexpected(.restricted)) + case .unavailable: + return .failure(.unexpected(.unavailable)) + case .invalidArgument: + return .failure(.unexpected(.invalidArgument)) + @unknown default: + return .failure(.other(String(reflecting: familyError))) + } + } catch { + return .failure(.other(String(reflecting: error))) + } + #endif + return .success(()) + }, + installFilter: { + // not sure this is necessary, but doesn't seem to hurt and might ensure clean slate + try? await NEFilterManager.shared().removeFromPreferences() + + if NEFilterManager.shared().providerConfiguration == nil { + let newConfiguration = NEFilterProviderConfiguration() + newConfiguration.username = "Gertrude" + newConfiguration.organization = "Gertrude" + #if os(iOS) + newConfiguration.filterBrowsers = true + #endif + newConfiguration.filterSockets = true + NEFilterManager.shared().providerConfiguration = newConfiguration + } + NEFilterManager.shared().isEnabled = true + do { + try await NEFilterManager.shared().saveToPreferences() + return .success(()) + } catch { + switch NEFilterManagerError(rawValue: (error as NSError).code) { + case .some(.configurationInvalid): + return .failure(.configurationInvalid) + case .some(.configurationDisabled): + return .failure(.configurationDisabled) + case .some(.configurationStale): + return .failure(.configurationStale) + case .some(.configurationCannotBeRemoved): + return .failure(.configurationCannotBeRemoved) + case .some(.configurationPermissionDenied): + return .failure(.configurationPermissionDenied) + case .some(.configurationInternalError): + return .failure(.configurationInternalError) + case .none: + return .failure(.unexpected(String(reflecting: error))) + @unknown default: + return .failure(.unexpected(String(reflecting: error))) + } + } + }, + filterRunning: { + do { + try await NEFilterManager.shared().loadFromPreferences() + return NEFilterManager.shared().isEnabled + } catch { + os_log( + "[G•] error loading preferences: %{public}s", + String(reflecting: error) + ) + return false + } + } + ) +} + +extension SystemClient: TestDependencyKey { + public static let testValue = SystemClient( + requestAuthorization: { .success(()) }, + installFilter: { .success(()) }, + filterRunning: { false } + ) +} + +extension DependencyValues { + var system: SystemClient { + get { self[SystemClient.self] } + set { self[SystemClient.self] = newValue } + } +} diff --git a/iosapp/lib-ios/Tests/AppTests/AppTests.swift b/iosapp/lib-ios/Tests/AppTests/AppTests.swift index ab2d035a..f7672ed1 100644 --- a/iosapp/lib-ios/Tests/AppTests/AppTests.swift +++ b/iosapp/lib-ios/Tests/AppTests/AppTests.swift @@ -1,8 +1,57 @@ -import App +import ComposableArchitecture import XCTest +@testable import App + final class AppTests: XCTestCase { - func testPlaceholder() throws { - XCTAssertTrue(true) + func testAppSendsFirstLaunchEventWhenNoLaunchDatePresent() async throws { + let logDetails = LockIsolated<[String]>([]) + let storedDates = LockIsolated<[Date]>([]) + let store = await TestStore(initialState: AppReducer.State()) { + AppReducer() + } withDependencies: { + $0.date = .constant(.reference) + $0.api.logEvent = { @Sendable id, detail in + logDetails.withValue { $0.append(detail ?? "") } + } + $0.storage.object = { @Sendable key in nil } + $0.storage.set = { @Sendable value, key in + storedDates.withValue { $0.append(value as! Date) } + } + } + + await store.send(.appLaunched) + + await store.receive(.setRunning(false)) { + $0.appState = .welcome + } + await store.receive(.setFirstLaunch(.reference)) { + $0.firstLaunch = .reference + } + + XCTAssertEqual(storedDates.value, [.reference]) + XCTAssertEqual(logDetails.value, ["first launch"]) + } + + func testNoApiEventWhenFirstLaunchPresent() async throws { + let store = await TestStore(initialState: AppReducer.State()) { + AppReducer() + } withDependencies: { + $0.storage.object = { @Sendable _ in Date.epoch } + } + + await store.send(.appLaunched) + + await store.receive(.setRunning(false)) { + $0.appState = .welcome + } + await store.receive(.setFirstLaunch(.epoch)) { + $0.firstLaunch = .epoch + } } } + +public extension Date { + static let epoch = Date(timeIntervalSince1970: 0) + static let reference = Date(timeIntervalSinceReferenceDate: 0) +} From e77a360220e6e43eb83a4082d9b8d124297e9894 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Thu, 10 Oct 2024 12:43:04 -0400 Subject: [PATCH 03/14] iosapp: log auth/install results --- iosapp/lib-ios/Sources/App/App.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iosapp/lib-ios/Sources/App/App.swift b/iosapp/lib-ios/Sources/App/App.swift index 2dfa5292..b8196f5e 100644 --- a/iosapp/lib-ios/Sources/App/App.swift +++ b/iosapp/lib-ios/Sources/App/App.swift @@ -90,8 +90,10 @@ public struct AppReducer { switch await self.system.requestAuthorization() { case .success: await send(.authorizationSucceeded) + await self.api.logEvent("d317c73c", "authorization succeeded") case .failure(let reason): await send(.authorizationFailed(reason)) + await self.api.logEvent("d9dfd021", "authorization failed: \(reason)") } } @@ -112,8 +114,10 @@ public struct AppReducer { switch await self.system.installFilter() { case .success: await send(.installSucceeded) + await self.api.logEvent("101c91ea", "filter install success") case .failure(let error): await send(.installFailed(error)) + await self.api.logEvent("739c08c6", "filter install failed: \(error)") } } From 43dcb9883b1691fbe260fcdb6ce4b059f75bc6d9 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Thu, 10 Oct 2024 13:44:36 -0400 Subject: [PATCH 04/14] iosapp: clean up system for auth/install retry --- iosapp/lib-ios/Sources/App/App.swift | 4 +++- iosapp/lib-ios/Sources/App/SystemClient.swift | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/iosapp/lib-ios/Sources/App/App.swift b/iosapp/lib-ios/Sources/App/App.swift index b8196f5e..70495dd2 100644 --- a/iosapp/lib-ios/Sources/App/App.swift +++ b/iosapp/lib-ios/Sources/App/App.swift @@ -93,6 +93,7 @@ public struct AppReducer { await self.api.logEvent("d317c73c", "authorization succeeded") case .failure(let reason): await send(.authorizationFailed(reason)) + await self.system.cleanupForRetry() await self.api.logEvent("d9dfd021", "authorization failed: \(reason)") } } @@ -117,6 +118,7 @@ public struct AppReducer { await self.api.logEvent("101c91ea", "filter install success") case .failure(let error): await send(.installFailed(error)) + await self.system.cleanupForRetry() await self.api.logEvent("739c08c6", "filter install failed: \(error)") } } @@ -126,7 +128,7 @@ public struct AppReducer { return .none case .installFailedTryAgainTapped: - // TODO: clean up for retry + state.appState = .welcome return .none case .installSucceeded: diff --git a/iosapp/lib-ios/Sources/App/SystemClient.swift b/iosapp/lib-ios/Sources/App/SystemClient.swift index cf470bd6..26f4cfbb 100644 --- a/iosapp/lib-ios/Sources/App/SystemClient.swift +++ b/iosapp/lib-ios/Sources/App/SystemClient.swift @@ -7,6 +7,7 @@ struct SystemClient: Sendable { var requestAuthorization: @Sendable () async -> Result var installFilter: @Sendable () async -> Result var filterRunning: @Sendable () async -> Bool + var cleanupForRetry: @Sendable () async -> Void } extension SystemClient: DependencyKey { @@ -96,6 +97,13 @@ extension SystemClient: DependencyKey { ) return false } + }, + cleanupForRetry: { + NEFilterManager.shared().providerConfiguration = nil + try? await NEFilterManager.shared().removeFromPreferences() + #if os(iOS) + AuthorizationCenter.shared.revokeAuthorization { _ in } + #endif } ) } @@ -104,7 +112,8 @@ extension SystemClient: TestDependencyKey { public static let testValue = SystemClient( requestAuthorization: { .success(()) }, installFilter: { .success(()) }, - filterRunning: { false } + filterRunning: { false }, + cleanupForRetry: {} ) } From 76d05a9b2dd3535c6a420b7ff316602ec54997f1 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Tue, 15 Oct 2024 15:51:40 -0400 Subject: [PATCH 05/14] iosapp: restore dev team for signing --- iosapp/ios-poc.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iosapp/ios-poc.xcodeproj/project.pbxproj b/iosapp/ios-poc.xcodeproj/project.pbxproj index 74b3f601..976cf633 100644 --- a/iosapp/ios-poc.xcodeproj/project.pbxproj +++ b/iosapp/ios-poc.xcodeproj/project.pbxproj @@ -531,7 +531,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ios-poc/Info.plist"; @@ -570,7 +570,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ios-poc/Info.plist"; From fd7d1f47c9f937f5be36f7bcd68fe5c26a4dfedd Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Tue, 15 Oct 2024 15:51:58 -0400 Subject: [PATCH 06/14] iosapp: workaround platform issues temporarily --- iosapp/lib-ios/Sources/App/Views/BgGradient.swift | 7 ++++--- iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/iosapp/lib-ios/Sources/App/Views/BgGradient.swift b/iosapp/lib-ios/Sources/App/Views/BgGradient.swift index 6d14788b..348b9d19 100644 --- a/iosapp/lib-ios/Sources/App/Views/BgGradient.swift +++ b/iosapp/lib-ios/Sources/App/Views/BgGradient.swift @@ -5,7 +5,8 @@ struct BgGradient: View { @State var timer: Timer? var body: some View { - if #available(iOS 18.0, *) { + // if #available(iOS 18.0, *) { + #if false MeshGradient( width: 3, height: 4, @@ -27,9 +28,9 @@ struct BgGradient: View { }.onDisappear { self.stopAnimation() } - } else { + #else Rectangle().fill(Gradient(colors: [.white, violet300])) - } + #endif } private func startAnimation() { diff --git a/iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift b/iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift index d6ca65fc..a83391e4 100644 --- a/iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift +++ b/iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift @@ -8,8 +8,10 @@ struct PrimaryButton: View { var body: some View { Button { - let impact = UIImpactFeedbackGenerator(style: .medium) - impact.impactOccurred() + #if os(iOS) + let impact = UIImpactFeedbackGenerator(style: .medium) + impact.impactOccurred() + #endif } label: { ZStack { VStack { From 327bbe586547502e9b40014965d57e8e04c48923 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Tue, 15 Oct 2024 16:15:51 -0400 Subject: [PATCH 07/14] iosapp: change bundle id for live release --- iosapp/controller/controller.entitlements | 8 ++++---- iosapp/filter/filter.entitlements | 8 ++++---- iosapp/ios-poc.xcodeproj/project.pbxproj | 20 ++++++++++---------- iosapp/ios-poc/ios-poc.entitlements | 20 ++++++++++---------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/iosapp/controller/controller.entitlements b/iosapp/controller/controller.entitlements index cbc0897a..462251b3 100644 --- a/iosapp/controller/controller.entitlements +++ b/iosapp/controller/controller.entitlements @@ -6,9 +6,9 @@ content-filter-provider - com.apple.security.application-groups - - group.com.gertrude-skunk.ios-poc - + com.apple.security.application-groups + + group.com.netrivet.gertrude-ios.app + diff --git a/iosapp/filter/filter.entitlements b/iosapp/filter/filter.entitlements index cbc0897a..462251b3 100644 --- a/iosapp/filter/filter.entitlements +++ b/iosapp/filter/filter.entitlements @@ -6,9 +6,9 @@ content-filter-provider - com.apple.security.application-groups - - group.com.gertrude-skunk.ios-poc - + com.apple.security.application-groups + + group.com.netrivet.gertrude-ios.app + diff --git a/iosapp/ios-poc.xcodeproj/project.pbxproj b/iosapp/ios-poc.xcodeproj/project.pbxproj index 976cf633..668e2791 100644 --- a/iosapp/ios-poc.xcodeproj/project.pbxproj +++ b/iosapp/ios-poc.xcodeproj/project.pbxproj @@ -529,7 +529,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "ios-poc/ios-poc.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; @@ -546,8 +546,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc"; + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -568,7 +568,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = "ios-poc/ios-poc.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; @@ -585,8 +585,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc"; + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -616,7 +616,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.filter"; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app.filter"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -646,7 +646,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.filter"; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app.filter"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -680,7 +680,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.controller"; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app.controller"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -716,7 +716,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.gertrude-skunk.ios-poc.controller"; + PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app.controller"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/iosapp/ios-poc/ios-poc.entitlements b/iosapp/ios-poc/ios-poc.entitlements index 0f42515e..31b7058b 100644 --- a/iosapp/ios-poc/ios-poc.entitlements +++ b/iosapp/ios-poc/ios-poc.entitlements @@ -2,15 +2,15 @@ - com.apple.developer.family-controls - - com.apple.developer.networking.networkextension - - content-filter-provider - - com.apple.security.application-groups - - group.com.gertrude-skunk.ios-poc - + com.apple.developer.family-controls + + com.apple.developer.networking.networkextension + + content-filter-provider + + com.apple.security.application-groups + + group.com.netrivet.gertrude-ios.app + From 5e4e0d79dd4447a49f0383a81ceee6fcd773d6df Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Tue, 15 Oct 2024 16:21:42 -0400 Subject: [PATCH 08/14] iosapp: increase a few small font sizes --- iosapp/lib-ios/Sources/App/Views/AuthFailed.swift | 1 - iosapp/lib-ios/Sources/App/Views/InstallFail.swift | 1 - iosapp/lib-ios/Sources/App/Views/Running.swift | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/iosapp/lib-ios/Sources/App/Views/AuthFailed.swift b/iosapp/lib-ios/Sources/App/Views/AuthFailed.swift index 38cd4da7..49089d37 100644 --- a/iosapp/lib-ios/Sources/App/Views/AuthFailed.swift +++ b/iosapp/lib-ios/Sources/App/Views/AuthFailed.swift @@ -42,7 +42,6 @@ struct AuthFailed: View { } } .multilineTextAlignment(.center) - .font(.footnote) Button { self.onTryAgain() diff --git a/iosapp/lib-ios/Sources/App/Views/InstallFail.swift b/iosapp/lib-ios/Sources/App/Views/InstallFail.swift index 7d173135..880a0892 100644 --- a/iosapp/lib-ios/Sources/App/Views/InstallFail.swift +++ b/iosapp/lib-ios/Sources/App/Views/InstallFail.swift @@ -26,7 +26,6 @@ struct InstallFail: View { Text("Unexpected error: \(underlying)") } } - .font(.footnote) .foregroundColor(.red) .padding(.bottom, 20) .multilineTextAlignment(.center) diff --git a/iosapp/lib-ios/Sources/App/Views/Running.swift b/iosapp/lib-ios/Sources/App/Views/Running.swift index bae6be17..81a7319e 100644 --- a/iosapp/lib-ios/Sources/App/Views/Running.swift +++ b/iosapp/lib-ios/Sources/App/Views/Running.swift @@ -4,11 +4,10 @@ struct Running: View { var body: some View { VStack(spacing: 20) { Text("Gertrude is blocking GIFs and image searches.") - .font(.system(size: 20, weight: .semibold)) + .font(.system(size: 24, weight: .semibold)) .multilineTextAlignment(.center) Text("You can quit this app now—it will keep blocking even when not running.") - .font(.footnote) .multilineTextAlignment(.center) Spacer() From 39e31ebaf7e6be319ce5701792c325f39896d465 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 11:04:00 -0400 Subject: [PATCH 09/14] iosapp: rename for release, xcode 16b3 updates --- .../project.pbxproj | 79 +++++++------- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/swiftpm/Package.resolved | 8 +- .../xcschemes/Gertrude-iOS.xcscheme} | 25 ++--- .../xcschemes/controller.xcscheme | 96 ++++++++++++++++++ .../xcshareddata/xcschemes/filter.xcscheme | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/icon-large.png | Bin .../Assets.xcassets/Contents.json | 0 .../GertrudeIcon.imageset/Contents.json | 0 .../GertrudeIcon.imageset/gertrude-icon1x.png | Bin .../GertrudeIcon.imageset/gertrude-icon2x.png | Bin .../GertrudeIcon.imageset/gertrude-icon3x.png | Bin iosapp/{ios-poc => app}/IOSAppEntry.swift | 2 +- iosapp/{ios-poc => app}/Info.plist | 0 .../Preview Assets.xcassets/Contents.json | 0 .../app.entitlements} | 0 iosapp/filter/FilterDataProvider.swift | 2 +- iosapp/lib-ios/Package.resolved | 8 +- iosapp/lib-ios/Package.swift | 18 ++-- .../{Filter => LibFilter}/Filter.swift | 0 .../Sources/{App => LibIOS}/ApiClient.swift | 0 .../Sources/{App => LibIOS}/App+Types.swift | 0 .../lib-ios/Sources/{App => LibIOS}/App.swift | 0 .../Sources/{App => LibIOS}/ContentView.swift | 0 .../Sources/{App => LibIOS}/Lib/Colors.swift | 0 .../{App => LibIOS}/StorageClient.swift | 0 .../{App => LibIOS}/SystemClient.swift | 0 .../{App => LibIOS}/Views/AuthFailed.swift | 0 .../{App => LibIOS}/Views/Authorized.swift | 0 .../{App => LibIOS}/Views/BgGradient.swift | 7 +- .../{App => LibIOS}/Views/FeatureLI.swift | 0 .../{App => LibIOS}/Views/InstallFail.swift | 0 .../{App => LibIOS}/Views/LoadingScreen.swift | 0 .../{App => LibIOS}/Views/PostInstall.swift | 0 .../{App => LibIOS}/Views/PreReqs.swift | 0 .../{App => LibIOS}/Views/PrimaryButton.swift | 0 .../{App => LibIOS}/Views/Running.swift | 6 +- .../{App => LibIOS}/Views/Welcome.swift | 0 .../FilterTests.swift | 0 .../{AppTests => LibIOSTests}/AppTests.swift | 2 +- 43 files changed, 173 insertions(+), 80 deletions(-) rename iosapp/{ios-poc.xcodeproj => Gertrude-iOS.xcodeproj}/project.pbxproj (93%) rename iosapp/{ios-poc.xcodeproj => Gertrude-iOS.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename iosapp/{ios-poc.xcodeproj => Gertrude-iOS.xcodeproj}/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename iosapp/{ios-poc.xcodeproj => Gertrude-iOS.xcodeproj}/project.xcworkspace/xcshareddata/swiftpm/Package.resolved (95%) rename iosapp/{ios-poc.xcodeproj/xcshareddata/xcschemes/ios-poc.xcscheme => Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/Gertrude-iOS.xcscheme} (78%) create mode 100644 iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/controller.xcscheme rename iosapp/{ios-poc.xcodeproj => Gertrude-iOS.xcodeproj}/xcshareddata/xcschemes/filter.xcscheme (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/AppIcon.appiconset/icon-large.png (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/Contents.json (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/GertrudeIcon.imageset/Contents.json (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon1x.png (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon2x.png (100%) rename iosapp/{ios-poc => app}/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon3x.png (100%) rename iosapp/{ios-poc => app}/IOSAppEntry.swift (96%) rename iosapp/{ios-poc => app}/Info.plist (100%) rename iosapp/{ios-poc => app}/Preview Content/Preview Assets.xcassets/Contents.json (100%) rename iosapp/{ios-poc/ios-poc.entitlements => app/app.entitlements} (100%) rename iosapp/lib-ios/Sources/{Filter => LibFilter}/Filter.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/ApiClient.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/App+Types.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/App.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/ContentView.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Lib/Colors.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/StorageClient.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/SystemClient.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/AuthFailed.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/Authorized.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/BgGradient.swift (94%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/FeatureLI.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/InstallFail.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/LoadingScreen.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/PostInstall.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/PreReqs.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/PrimaryButton.swift (100%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/Running.swift (81%) rename iosapp/lib-ios/Sources/{App => LibIOS}/Views/Welcome.swift (100%) rename iosapp/lib-ios/Tests/{FilterTests => LibFilterTests}/FilterTests.swift (100%) rename iosapp/lib-ios/Tests/{AppTests => LibIOSTests}/AppTests.swift (98%) diff --git a/iosapp/ios-poc.xcodeproj/project.pbxproj b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj similarity index 93% rename from iosapp/ios-poc.xcodeproj/project.pbxproj rename to iosapp/Gertrude-iOS.xcodeproj/project.pbxproj index 668e2791..4dd77f18 100644 --- a/iosapp/ios-poc.xcodeproj/project.pbxproj +++ b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 03E917BFA4D8CBC845611955 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3943E5533FFA53791343F445 /* Colors.swift */; }; 1FE271D2FDBE4C6122B68A8A /* BgGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A47408B05AA4E9E2C4042A /* BgGradient.swift */; }; 580A5B546430D0B85CDE67A4 /* FeatureLI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFBB6E5EDFE1B2D7B40D17A6 /* FeatureLI.swift */; }; - 7F7E54B62CB576B20012844E /* Filter in Frameworks */ = {isa = PBXBuildFile; productRef = 7F7E54B52CB576B20012844E /* Filter */; }; 7FA7B8472AEB238700363B53 /* IOSAppEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */; }; 7FA7B84B2AEB238800363B53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7FA7B84A2AEB238800363B53 /* Assets.xcassets */; }; 7FA7B84E2AEB238800363B53 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7FA7B84D2AEB238800363B53 /* Preview Assets.xcassets */; }; @@ -21,7 +20,8 @@ 7FE743D62C8F431500607876 /* FilterControlProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FE743D52C8F431500607876 /* FilterControlProvider.swift */; }; 7FE743DB2C8F431500607876 /* controller.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7FE743D22C8F431500607876 /* controller.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7FE743DF2C8F4ED700607876 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA7B85A2AEB243400363B53 /* NetworkExtension.framework */; }; - 7FFB54912CB477C200EBFA3B /* App in Frameworks */ = {isa = PBXBuildFile; productRef = 7FFB54902CB477C200EBFA3B /* App */; }; + 7FEB782C2CC0058500A76A98 /* LibFilter in Frameworks */ = {isa = PBXBuildFile; productRef = 7FEB782B2CC0058500A76A98 /* LibFilter */; }; + 7FEC01B72CBF0DCA00B152B8 /* LibIOS in Frameworks */ = {isa = PBXBuildFile; productRef = 7FEC01B62CBF0DCA00B152B8 /* LibIOS */; }; CECCA8D914450391BB017E01 /* PrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF145756F19546462ADE7D9 /* PrimaryButton.swift */; }; F7AEF6187A4EA032CA3A38F7 /* PreReqs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D8E4736A2DBCF7F38363B3 /* PreReqs.swift */; }; /* End PBXBuildFile section */ @@ -62,7 +62,7 @@ 3943E5533FFA53791343F445 /* Colors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 5FF145756F19546462ADE7D9 /* PrimaryButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; 7F4A14502CB5843500FD6ABE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 7FA7B8432AEB238700363B53 /* ios-poc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-poc.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FA7B8432AEB238700363B53 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSAppEntry.swift; sourceTree = ""; }; 7FA7B84A2AEB238800363B53 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7FA7B84D2AEB238800363B53 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -71,7 +71,7 @@ 7FA7B85D2AEB243400363B53 /* FilterDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterDataProvider.swift; sourceTree = ""; }; 7FA7B85F2AEB243400363B53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7FA7B8602AEB243400363B53 /* filter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = filter.entitlements; sourceTree = ""; }; - 7FA7B8682AEB271800363B53 /* ios-poc.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ios-poc.entitlements"; sourceTree = ""; }; + 7FA7B8682AEB271800363B53 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = ""; }; 7FE743D22C8F431500607876 /* controller.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = controller.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7FE743D52C8F431500607876 /* FilterControlProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterControlProvider.swift; sourceTree = ""; }; 7FE743D72C8F431500607876 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -87,7 +87,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7FFB54912CB477C200EBFA3B /* App in Frameworks */, + 7FEC01B72CBF0DCA00B152B8 /* LibIOS in Frameworks */, 7FE743DF2C8F4ED700607876 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -96,7 +96,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7F7E54B62CB576B20012844E /* Filter in Frameworks */, + 7FEB782C2CC0058500A76A98 /* LibFilter in Frameworks */, 7FA7B85B2AEB243400363B53 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -120,19 +120,19 @@ path = Lib; sourceTree = ""; }; - 5C9A54EF758AD899FB94954C /* App */ = { + 5C9A54EF758AD899FB94954C /* LibIOS */ = { isa = PBXGroup; children = ( 9DAD2F4ECC8A0B77558A95F5 /* Views */, 545C6415C3C80BA9198F9BF5 /* Lib */, ); - path = App; + path = LibIOS; sourceTree = ""; }; 6F6AFDF4611779783100CDFA /* Sources */ = { isa = PBXGroup; children = ( - 5C9A54EF758AD899FB94954C /* App */, + 5C9A54EF758AD899FB94954C /* LibIOS */, ); path = Sources; sourceTree = ""; @@ -141,7 +141,7 @@ isa = PBXGroup; children = ( 7FFB548E2CB4768600EBFA3B /* lib-ios */, - 7FA7B8452AEB238700363B53 /* ios-poc */, + 7FA7B8452AEB238700363B53 /* app */, 7FA7B85C2AEB243400363B53 /* filter */, 7FE743D42C8F431500607876 /* controller */, 7FA7B8592AEB243400363B53 /* Frameworks */, @@ -152,23 +152,23 @@ 7FA7B8442AEB238700363B53 /* Products */ = { isa = PBXGroup; children = ( - 7FA7B8432AEB238700363B53 /* ios-poc.app */, + 7FA7B8432AEB238700363B53 /* app.app */, 7FA7B8582AEB243400363B53 /* filter.appex */, 7FE743D22C8F431500607876 /* controller.appex */, ); name = Products; sourceTree = ""; }; - 7FA7B8452AEB238700363B53 /* ios-poc */ = { + 7FA7B8452AEB238700363B53 /* app */ = { isa = PBXGroup; children = ( 7F4A14502CB5843500FD6ABE /* Info.plist */, - 7FA7B8682AEB271800363B53 /* ios-poc.entitlements */, + 7FA7B8682AEB271800363B53 /* app.entitlements */, 7FA7B8462AEB238700363B53 /* IOSAppEntry.swift */, 7FA7B84A2AEB238800363B53 /* Assets.xcassets */, 7FA7B84C2AEB238800363B53 /* Preview Content */, ); - path = "ios-poc"; + path = app; sourceTree = ""; }; 7FA7B84C2AEB238800363B53 /* Preview Content */ = { @@ -230,9 +230,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 7FA7B8422AEB238700363B53 /* ios-poc */ = { + 7FA7B8422AEB238700363B53 /* app */ = { isa = PBXNativeTarget; - buildConfigurationList = 7FA7B8512AEB238800363B53 /* Build configuration list for PBXNativeTarget "ios-poc" */; + buildConfigurationList = 7FA7B8512AEB238800363B53 /* Build configuration list for PBXNativeTarget "app" */; buildPhases = ( 7FA7B83F2AEB238700363B53 /* Sources */, 7FA7B8402AEB238700363B53 /* Frameworks */, @@ -245,12 +245,12 @@ 7FA7B8622AEB243400363B53 /* PBXTargetDependency */, 7FE743DA2C8F431500607876 /* PBXTargetDependency */, ); - name = "ios-poc"; + name = app; packageProductDependencies = ( - 7FFB54902CB477C200EBFA3B /* App */, + 7FEC01B62CBF0DCA00B152B8 /* LibIOS */, ); - productName = "ios-poc"; - productReference = 7FA7B8432AEB238700363B53 /* ios-poc.app */; + productName = app; + productReference = 7FA7B8432AEB238700363B53 /* app.app */; productType = "com.apple.product-type.application"; }; 7FA7B8572AEB243400363B53 /* filter */ = { @@ -267,7 +267,7 @@ ); name = filter; packageProductDependencies = ( - 7F7E54B52CB576B20012844E /* Filter */, + 7FEB782B2CC0058500A76A98 /* LibFilter */, ); productName = filter; productReference = 7FA7B8582AEB243400363B53 /* filter.appex */; @@ -311,7 +311,7 @@ }; }; }; - buildConfigurationList = 7FA7B83E2AEB238700363B53 /* Build configuration list for PBXProject "ios-poc" */; + buildConfigurationList = 7FA7B83E2AEB238700363B53 /* Build configuration list for PBXProject "Gertrude-iOS" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -327,7 +327,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 7FA7B8422AEB238700363B53 /* ios-poc */, + 7FA7B8422AEB238700363B53 /* app */, 7FA7B8572AEB243400363B53 /* filter */, 7FE743D12C8F431500607876 /* controller */, ); @@ -527,14 +527,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CODE_SIGN_ENTITLEMENTS = "ios-poc/ios-poc.entitlements"; + CODE_SIGN_ENTITLEMENTS = app/app.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_ASSET_PATHS = "\"app/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "ios-poc/Info.plist"; + INFOPLIST_FILE = app/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Gertrude; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -546,7 +546,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -566,14 +566,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CODE_SIGN_ENTITLEMENTS = "ios-poc/ios-poc.entitlements"; + CODE_SIGN_ENTITLEMENTS = app/app.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"ios-poc/Preview Content\""; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_ASSET_PATHS = "\"app/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "ios-poc/Info.plist"; + INFOPLIST_FILE = app/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Gertrude; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -585,7 +585,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -732,7 +732,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 7FA7B83E2AEB238700363B53 /* Build configuration list for PBXProject "ios-poc" */ = { + 7FA7B83E2AEB238700363B53 /* Build configuration list for PBXProject "Gertrude-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 7FA7B84F2AEB238800363B53 /* Debug */, @@ -741,7 +741,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7FA7B8512AEB238800363B53 /* Build configuration list for PBXNativeTarget "ios-poc" */ = { + 7FA7B8512AEB238800363B53 /* Build configuration list for PBXNativeTarget "app" */ = { isa = XCConfigurationList; buildConfigurations = ( 7FA7B8522AEB238800363B53 /* Debug */, @@ -778,13 +778,14 @@ /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 7F7E54B52CB576B20012844E /* Filter */ = { + 7FEB782B2CC0058500A76A98 /* LibFilter */ = { isa = XCSwiftPackageProductDependency; - productName = Filter; + package = 7FFB548F2CB477C200EBFA3B /* XCLocalSwiftPackageReference "lib-ios" */; + productName = LibFilter; }; - 7FFB54902CB477C200EBFA3B /* App */ = { + 7FEC01B62CBF0DCA00B152B8 /* LibIOS */ = { isa = XCSwiftPackageProductDependency; - productName = App; + productName = LibIOS; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/iosapp/ios-poc.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from iosapp/ios-poc.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 95% rename from iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 17990c01..9efc9624 100644 --- a/iosapp/ios-poc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iosapp/Gertrude-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "8013f1a72af8ccb2b1735d7aed831a8dc07c6fd0", - "version" : "1.15.0" + "revision" : "fc5cbeec88114ff987f6c3cad3a7f3a3713fdb56", + "version" : "1.15.1" } }, { @@ -87,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "d1bdbd8a5d1d1dfd2e4bb1f5e2f6facb631404d4", - "version" : "2.2.1" + "revision" : "16a27ab7ae0abfefbbcba73581b3e2380b47a579", + "version" : "2.2.2" } }, { diff --git a/iosapp/ios-poc.xcodeproj/xcshareddata/xcschemes/ios-poc.xcscheme b/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/Gertrude-iOS.xcscheme similarity index 78% rename from iosapp/ios-poc.xcodeproj/xcshareddata/xcschemes/ios-poc.xcscheme rename to iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/Gertrude-iOS.xcscheme index 1e636f07..fb4ac19f 100644 --- a/iosapp/ios-poc.xcodeproj/xcshareddata/xcschemes/ios-poc.xcscheme +++ b/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/Gertrude-iOS.xcscheme @@ -16,9 +16,9 @@ + BuildableName = "app.app" + BlueprintName = "app" + ReferencedContainer = "container:Gertrude-iOS.xcodeproj"> @@ -45,18 +45,11 @@ + BuildableName = "app.app" + BlueprintName = "app" + ReferencedContainer = "container:Gertrude-iOS.xcodeproj"> - - - - + BuildableName = "app.app" + BlueprintName = "app" + ReferencedContainer = "container:Gertrude-iOS.xcodeproj"> diff --git a/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/controller.xcscheme b/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/controller.xcscheme new file mode 100644 index 00000000..3b83c499 --- /dev/null +++ b/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/controller.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iosapp/ios-poc.xcodeproj/xcshareddata/xcschemes/filter.xcscheme b/iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/filter.xcscheme similarity index 100% rename from iosapp/ios-poc.xcodeproj/xcshareddata/xcschemes/filter.xcscheme rename to iosapp/Gertrude-iOS.xcodeproj/xcshareddata/xcschemes/filter.xcscheme diff --git a/iosapp/ios-poc/Assets.xcassets/AccentColor.colorset/Contents.json b/iosapp/app/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/AccentColor.colorset/Contents.json rename to iosapp/app/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/iosapp/ios-poc/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosapp/app/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/AppIcon.appiconset/Contents.json rename to iosapp/app/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/iosapp/ios-poc/Assets.xcassets/AppIcon.appiconset/icon-large.png b/iosapp/app/Assets.xcassets/AppIcon.appiconset/icon-large.png similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/AppIcon.appiconset/icon-large.png rename to iosapp/app/Assets.xcassets/AppIcon.appiconset/icon-large.png diff --git a/iosapp/ios-poc/Assets.xcassets/Contents.json b/iosapp/app/Assets.xcassets/Contents.json similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/Contents.json rename to iosapp/app/Assets.xcassets/Contents.json diff --git a/iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/Contents.json b/iosapp/app/Assets.xcassets/GertrudeIcon.imageset/Contents.json similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/Contents.json rename to iosapp/app/Assets.xcassets/GertrudeIcon.imageset/Contents.json diff --git a/iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon1x.png b/iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon1x.png similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon1x.png rename to iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon1x.png diff --git a/iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon2x.png b/iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon2x.png similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon2x.png rename to iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon2x.png diff --git a/iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon3x.png b/iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon3x.png similarity index 100% rename from iosapp/ios-poc/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon3x.png rename to iosapp/app/Assets.xcassets/GertrudeIcon.imageset/gertrude-icon3x.png diff --git a/iosapp/ios-poc/IOSAppEntry.swift b/iosapp/app/IOSAppEntry.swift similarity index 96% rename from iosapp/ios-poc/IOSAppEntry.swift rename to iosapp/app/IOSAppEntry.swift index 9d232150..a6df5fa2 100644 --- a/iosapp/ios-poc/IOSAppEntry.swift +++ b/iosapp/app/IOSAppEntry.swift @@ -1,5 +1,5 @@ -import App import ComposableArchitecture +import LibIOS import SwiftUI @main diff --git a/iosapp/ios-poc/Info.plist b/iosapp/app/Info.plist similarity index 100% rename from iosapp/ios-poc/Info.plist rename to iosapp/app/Info.plist diff --git a/iosapp/ios-poc/Preview Content/Preview Assets.xcassets/Contents.json b/iosapp/app/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from iosapp/ios-poc/Preview Content/Preview Assets.xcassets/Contents.json rename to iosapp/app/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/iosapp/ios-poc/ios-poc.entitlements b/iosapp/app/app.entitlements similarity index 100% rename from iosapp/ios-poc/ios-poc.entitlements rename to iosapp/app/app.entitlements diff --git a/iosapp/filter/FilterDataProvider.swift b/iosapp/filter/FilterDataProvider.swift index 658bc450..5305f93e 100644 --- a/iosapp/filter/FilterDataProvider.swift +++ b/iosapp/filter/FilterDataProvider.swift @@ -1,4 +1,4 @@ -import Filter +import LibFilter import NetworkExtension import os.log diff --git a/iosapp/lib-ios/Package.resolved b/iosapp/lib-ios/Package.resolved index 277a056c..fdb4be1d 100644 --- a/iosapp/lib-ios/Package.resolved +++ b/iosapp/lib-ios/Package.resolved @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "8013f1a72af8ccb2b1735d7aed831a8dc07c6fd0", - "version" : "1.15.0" + "revision" : "fc5cbeec88114ff987f6c3cad3a7f3a3713fdb56", + "version" : "1.15.1" } }, { @@ -87,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "d1bdbd8a5d1d1dfd2e4bb1f5e2f6facb631404d4", - "version" : "2.2.1" + "revision" : "16a27ab7ae0abfefbbcba73581b3e2380b47a579", + "version" : "2.2.2" } }, { diff --git a/iosapp/lib-ios/Package.swift b/iosapp/lib-ios/Package.swift index 6777009c..6c127208 100644 --- a/iosapp/lib-ios/Package.swift +++ b/iosapp/lib-ios/Package.swift @@ -2,11 +2,11 @@ import PackageDescription let package = Package( - name: "App", + name: "LibIOS", platforms: [.macOS(.v14), .iOS(.v17)], products: [ - .library(name: "App", targets: ["App"]), - .library(name: "Filter", targets: ["Filter"]), + .library(name: "LibIOS", targets: ["LibIOS"]), + .library(name: "LibFilter", targets: ["LibFilter"]), ], dependencies: [ .package( @@ -17,23 +17,23 @@ let package = Package( ], targets: [ .target( - name: "App", + name: "LibIOS", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "IOSRoute", package: "pairql-iosapp"), ] ), .target( - name: "Filter", + name: "LibFilter", dependencies: [] ), .testTarget( - name: "AppTests", - dependencies: ["App"] + name: "LibIOSTests", + dependencies: ["LibIOS"] ), .testTarget( - name: "FilterTests", - dependencies: ["Filter"] + name: "LibFilterTests", + dependencies: ["LibFilter"] ), ] ) diff --git a/iosapp/lib-ios/Sources/Filter/Filter.swift b/iosapp/lib-ios/Sources/LibFilter/Filter.swift similarity index 100% rename from iosapp/lib-ios/Sources/Filter/Filter.swift rename to iosapp/lib-ios/Sources/LibFilter/Filter.swift diff --git a/iosapp/lib-ios/Sources/App/ApiClient.swift b/iosapp/lib-ios/Sources/LibIOS/ApiClient.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/ApiClient.swift rename to iosapp/lib-ios/Sources/LibIOS/ApiClient.swift diff --git a/iosapp/lib-ios/Sources/App/App+Types.swift b/iosapp/lib-ios/Sources/LibIOS/App+Types.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/App+Types.swift rename to iosapp/lib-ios/Sources/LibIOS/App+Types.swift diff --git a/iosapp/lib-ios/Sources/App/App.swift b/iosapp/lib-ios/Sources/LibIOS/App.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/App.swift rename to iosapp/lib-ios/Sources/LibIOS/App.swift diff --git a/iosapp/lib-ios/Sources/App/ContentView.swift b/iosapp/lib-ios/Sources/LibIOS/ContentView.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/ContentView.swift rename to iosapp/lib-ios/Sources/LibIOS/ContentView.swift diff --git a/iosapp/lib-ios/Sources/App/Lib/Colors.swift b/iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Lib/Colors.swift rename to iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift diff --git a/iosapp/lib-ios/Sources/App/StorageClient.swift b/iosapp/lib-ios/Sources/LibIOS/StorageClient.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/StorageClient.swift rename to iosapp/lib-ios/Sources/LibIOS/StorageClient.swift diff --git a/iosapp/lib-ios/Sources/App/SystemClient.swift b/iosapp/lib-ios/Sources/LibIOS/SystemClient.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/SystemClient.swift rename to iosapp/lib-ios/Sources/LibIOS/SystemClient.swift diff --git a/iosapp/lib-ios/Sources/App/Views/AuthFailed.swift b/iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/AuthFailed.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift diff --git a/iosapp/lib-ios/Sources/App/Views/Authorized.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/Authorized.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift diff --git a/iosapp/lib-ios/Sources/App/Views/BgGradient.swift b/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift similarity index 94% rename from iosapp/lib-ios/Sources/App/Views/BgGradient.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift index 348b9d19..6d14788b 100644 --- a/iosapp/lib-ios/Sources/App/Views/BgGradient.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift @@ -5,8 +5,7 @@ struct BgGradient: View { @State var timer: Timer? var body: some View { - // if #available(iOS 18.0, *) { - #if false + if #available(iOS 18.0, *) { MeshGradient( width: 3, height: 4, @@ -28,9 +27,9 @@ struct BgGradient: View { }.onDisappear { self.stopAnimation() } - #else + } else { Rectangle().fill(Gradient(colors: [.white, violet300])) - #endif + } } private func startAnimation() { diff --git a/iosapp/lib-ios/Sources/App/Views/FeatureLI.swift b/iosapp/lib-ios/Sources/LibIOS/Views/FeatureLI.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/FeatureLI.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/FeatureLI.swift diff --git a/iosapp/lib-ios/Sources/App/Views/InstallFail.swift b/iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/InstallFail.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift diff --git a/iosapp/lib-ios/Sources/App/Views/LoadingScreen.swift b/iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/LoadingScreen.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift diff --git a/iosapp/lib-ios/Sources/App/Views/PostInstall.swift b/iosapp/lib-ios/Sources/LibIOS/Views/PostInstall.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/PostInstall.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/PostInstall.swift diff --git a/iosapp/lib-ios/Sources/App/Views/PreReqs.swift b/iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/PreReqs.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift diff --git a/iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift b/iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/PrimaryButton.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift diff --git a/iosapp/lib-ios/Sources/App/Views/Running.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift similarity index 81% rename from iosapp/lib-ios/Sources/App/Views/Running.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/Running.swift index 81a7319e..1f623444 100644 --- a/iosapp/lib-ios/Sources/App/Views/Running.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift @@ -2,7 +2,11 @@ import SwiftUI struct Running: View { var body: some View { - VStack(spacing: 20) { + VStack(spacing: 24) { + Image("GertrudeIcon") + .clipShape(RoundedRectangle(cornerRadius: 20)) + .shadow(radius: 5) + Text("Gertrude is blocking GIFs and image searches.") .font(.system(size: 24, weight: .semibold)) .multilineTextAlignment(.center) diff --git a/iosapp/lib-ios/Sources/App/Views/Welcome.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Welcome.swift similarity index 100% rename from iosapp/lib-ios/Sources/App/Views/Welcome.swift rename to iosapp/lib-ios/Sources/LibIOS/Views/Welcome.swift diff --git a/iosapp/lib-ios/Tests/FilterTests/FilterTests.swift b/iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift similarity index 100% rename from iosapp/lib-ios/Tests/FilterTests/FilterTests.swift rename to iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift diff --git a/iosapp/lib-ios/Tests/AppTests/AppTests.swift b/iosapp/lib-ios/Tests/LibIOSTests/AppTests.swift similarity index 98% rename from iosapp/lib-ios/Tests/AppTests/AppTests.swift rename to iosapp/lib-ios/Tests/LibIOSTests/AppTests.swift index f7672ed1..65e74580 100644 --- a/iosapp/lib-ios/Tests/AppTests/AppTests.swift +++ b/iosapp/lib-ios/Tests/LibIOSTests/AppTests.swift @@ -1,7 +1,7 @@ import ComposableArchitecture import XCTest -@testable import App +@testable import LibIOS final class AppTests: XCTestCase { func testAppSendsFirstLaunchEventWhenNoLaunchDatePresent() async throws { From da10dc7e6c9b05c0c1005b4cf1d662337d71419d Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 11:14:19 -0400 Subject: [PATCH 10/14] iosapp: move custom colors into extension --- .../lib-ios/Sources/LibIOS/Lib/Colors.swift | 38 +++++++------- .../Sources/LibIOS/Views/AuthFailed.swift | 4 +- .../Sources/LibIOS/Views/Authorized.swift | 4 +- .../Sources/LibIOS/Views/BgGradient.swift | 50 +++++++++++-------- .../Sources/LibIOS/Views/InstallFail.swift | 4 +- .../Sources/LibIOS/Views/LoadingScreen.swift | 6 +-- .../Sources/LibIOS/Views/PreReqs.swift | 2 +- .../Sources/LibIOS/Views/PrimaryButton.swift | 8 +-- .../Sources/LibIOS/Views/Running.swift | 2 +- 9 files changed, 62 insertions(+), 56 deletions(-) diff --git a/iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift b/iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift index ec1b8f47..1ed77ef8 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Lib/Colors.swift @@ -1,26 +1,26 @@ import SwiftUI -let violet100 = Color(hex: "#ede9fe")! -let violet200 = Color(hex: "#ddd6fe")! -let violet300 = Color(hex: "#c4b5fd")! -let violet400 = Color(hex: "#a78bfa")! -let violet500 = Color(hex: "#8b5cf6")! -let violet600 = Color(hex: "#7c3aed")! -let violet700 = Color(hex: "#6d28d9")! -let violet800 = Color(hex: "#5b21b6")! -let violet900 = Color(hex: "#4c1d95")! +public extension Color { + static let violet100 = Color(hex: "#ede9fe")! + static let violet200 = Color(hex: "#ddd6fe")! + static let violet300 = Color(hex: "#c4b5fd")! + static let violet400 = Color(hex: "#a78bfa")! + static let violet500 = Color(hex: "#8b5cf6")! + static let violet600 = Color(hex: "#7c3aed")! + static let violet700 = Color(hex: "#6d28d9")! + static let violet800 = Color(hex: "#5b21b6")! + static let violet900 = Color(hex: "#4c1d95")! -let fuschia100 = Color(hex: "#fae8ff")! -let fuchsia200 = Color(hex: "#f5d0fe")! -let fuchsia300 = Color(hex: "#f0abfc")! -let fuchsia400 = Color(hex: "#e879f9")! -let fuchsia500 = Color(hex: "#d946ef")! -let fuchsia600 = Color(hex: "#c026d3")! -let fuchsia700 = Color(hex: "#a21caf")! -let fuchsia800 = Color(hex: "#86198f")! -let fuchsia900 = Color(hex: "#701a75")! + static let fuschia100 = Color(hex: "#fae8ff")! + static let fuchsia200 = Color(hex: "#f5d0fe")! + static let fuchsia300 = Color(hex: "#f0abfc")! + static let fuchsia400 = Color(hex: "#e879f9")! + static let fuchsia500 = Color(hex: "#d946ef")! + static let fuchsia600 = Color(hex: "#c026d3")! + static let fuchsia700 = Color(hex: "#a21caf")! + static let fuchsia800 = Color(hex: "#86198f")! + static let fuchsia900 = Color(hex: "#701a75")! -public extension Color { init?(hex: String) { let r, g, b: Double diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift b/iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift index 49089d37..21a06042 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/AuthFailed.swift @@ -51,9 +51,9 @@ struct AuthFailed: View { Spacer() } .padding(.vertical, 12) - .background(violet100) + .background(Color.violet100) .cornerRadius(8) - .foregroundColor(violet700) + .foregroundColor(.violet700) .font(.system(size: 16, weight: .semibold)) } .padding(20) diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift index b3728e78..6a71d69a 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/Authorized.swift @@ -17,9 +17,9 @@ struct Authorized: View { Spacer() } .padding(.vertical, 12) - .background(violet100) + .background(Color.violet100) .cornerRadius(8) - .foregroundColor(violet700) + .foregroundColor(.violet700) .font(.system(size: 16, weight: .semibold)) } .padding(20) diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift b/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift index 6d14788b..ec471776 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/BgGradient.swift @@ -6,29 +6,35 @@ struct BgGradient: View { var body: some View { if #available(iOS 18.0, *) { - MeshGradient( - width: 3, - height: 4, - points: [ - [0, 0], [0.5, 0], [1, 0], - [0, 0.33], [Float(cos(self.t) / 2.5 + 0.5), Float(sin(2 * self.t) / 6 + 0.33)], [1, 0.5], - [0, 0.66], [Float(-cos(self.t) / 2.5 + 0.5), Float(-sin(self.t) / 4 + 0.66)], [1, 0.5], - [0, 1], [0.5, 1], [1, 1], - ], - colors: [ - fuchsia300, violet300, .white, - .white, .white, fuchsia300, - violet100, fuchsia300, violet300, - fuchsia300, violet300, violet100, - ], - smoothsColors: true - ).onAppear { - self.startAnimation() - }.onDisappear { - self.stopAnimation() - } + #if os(iOS) + MeshGradient( + width: 3, + height: 4, + points: [ + [0, 0], [0.5, 0], [1, 0], + [0, 0.33], [Float(cos(self.t) / 2.5 + 0.5), Float(sin(2 * self.t) / 6 + 0.33)], + [1, 0.5], + [0, 0.66], [Float(-cos(self.t) / 2.5 + 0.5), Float(-sin(self.t) / 4 + 0.66)], [1, 0.5], + [0, 1], [0.5, 1], [1, 1], + ], + colors: [ + .fuchsia300, .violet300, .white, + .white, .white, .fuchsia300, + .violet100, .fuchsia300, .violet300, + .fuchsia300, .violet300, .violet100, + ], + smoothsColors: true + ).onAppear { + self.startAnimation() + }.onDisappear { + self.stopAnimation() + } + #else + // keep nvim LSP happy + Rectangle().fill(Gradient(colors: [.white, .violet300])) + #endif } else { - Rectangle().fill(Gradient(colors: [.white, violet300])) + Rectangle().fill(Gradient(colors: [.white, .violet300])) } } diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift b/iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift index 880a0892..364dad27 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/InstallFail.swift @@ -38,9 +38,9 @@ struct InstallFail: View { Spacer() } .padding(.vertical, 12) - .background(violet100) + .background(Color.violet100) .cornerRadius(8) - .foregroundColor(violet700) + .foregroundColor(.violet700) .font(.system(size: 16, weight: .semibold)) } .padding(20) diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift b/iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift index e3f63710..0be663df 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/LoadingScreen.swift @@ -10,15 +10,15 @@ public struct LoadingScreen: View { Rectangle() .frame(width: -sin(self.t) * 5 + 15, height: sin(self.t) * 40 + 60) .cornerRadius(20) - .foregroundColor(violet500.opacity(0.8)) + .foregroundColor(.violet500.opacity(0.8)) Rectangle() .frame(width: -sin(self.t + 0.4) * 5 + 15, height: sin(self.t + 0.4) * 40 + 60) .cornerRadius(20) - .foregroundColor(violet500.opacity(0.8)) + .foregroundColor(.violet500.opacity(0.8)) Rectangle() .frame(width: -sin(self.t + 0.8) * 5 + 15, height: sin(self.t + 0.8) * 40 + 60) .cornerRadius(20) - .foregroundColor(violet500.opacity(0.8)) + .foregroundColor(.violet500.opacity(0.8)) } .onAppear { self.startAnimation() diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift b/iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift index d9ea380a..3180fff8 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/PreReqs.swift @@ -23,7 +23,7 @@ struct PreReqs: View { HStack { Image(systemName: "checkmark.circle") .font(.system(size: 12, weight: .semibold)) - .foregroundColor(violet500) + .foregroundColor(.violet500) Text(requirement) .font(.footnote) } diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift b/iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift index a83391e4..a8a1b0bd 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/PrimaryButton.swift @@ -16,7 +16,7 @@ struct PrimaryButton: View { ZStack { VStack { Rectangle() - .fill(Gradient(colors: [violet400, violet500])) + .fill(Gradient(colors: [.violet400, .violet500])) Spacer() Spacer() HStack { @@ -25,9 +25,9 @@ struct PrimaryButton: View { Spacer() Spacer() Rectangle() - .fill(Gradient(colors: [violet500, violet700])) + .fill(Gradient(colors: [.violet500, .violet700])) } - .background(violet500) + .background(Color.violet500) .cornerRadius(20) VStack { Spacer() @@ -41,7 +41,7 @@ struct PrimaryButton: View { } Spacer() } - .background(Gradient(colors: [violet500, violet600])) + .background(Gradient(colors: [.violet500, .violet600])) .foregroundColor(.white) .cornerRadius(20) .padding(.vertical, 2.5) diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift index 1f623444..c0be106a 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift @@ -6,7 +6,7 @@ struct Running: View { Image("GertrudeIcon") .clipShape(RoundedRectangle(cornerRadius: 20)) .shadow(radius: 5) - + Text("Gertrude is blocking GIFs and image searches.") .font(.system(size: 24, weight: .semibold)) .multilineTextAlignment(.center) From c176baab688e422019ef6d29f049d5417f2660f0 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 11:19:23 -0400 Subject: [PATCH 11/14] iosapp: tweak icon in running screen --- iosapp/lib-ios/Sources/LibIOS/Views/Running.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift index c0be106a..726f4d40 100644 --- a/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift +++ b/iosapp/lib-ios/Sources/LibIOS/Views/Running.swift @@ -5,7 +5,8 @@ struct Running: View { VStack(spacing: 24) { Image("GertrudeIcon") .clipShape(RoundedRectangle(cornerRadius: 20)) - .shadow(radius: 5) + .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.12), radius: 4) + .padding(.bottom, 12) Text("Gertrude is blocking GIFs and image searches.") .font(.system(size: 24, weight: .semibold)) From 58903491b014f45d2fa10bcd82393e4520e29aba Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 12:03:17 -0400 Subject: [PATCH 12/14] iosapp: add a few more blocks --- iosapp/lib-ios/Sources/LibFilter/Filter.swift | 8 ++++++++ iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/iosapp/lib-ios/Sources/LibFilter/Filter.swift b/iosapp/lib-ios/Sources/LibFilter/Filter.swift index fa448142..2db93cdd 100644 --- a/iosapp/lib-ios/Sources/LibFilter/Filter.swift +++ b/iosapp/lib-ios/Sources/LibFilter/Filter.swift @@ -3,6 +3,8 @@ public func decideFlow(hostname: String?, url: String?, sourceId: String?) -> Bo return false } else if sourceId?.contains("com.apple.Spotlight") == true { return false + } else if sourceId?.contains(".com.apple.photoanalysisd") == true { + return false } if url?.contains("tenor.co") == true { @@ -18,6 +20,12 @@ public func decideFlow(hostname: String?, url: String?, sourceId: String?) -> Bo return false } else if target.contains("media.fosu2-1.fna.whatsapp.net") { return false + } else if sourceId?.contains(".com.apple.MobileSMS") == true { + if target.contains("amp-api-edge.apps.apple.com") { + return false + } else if target.contains("is1-ssl.mzstatic.com") { + return false + } } } return true diff --git a/iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift b/iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift index 47af8f0d..b2ead413 100644 --- a/iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift +++ b/iosapp/lib-ios/Tests/LibFilterTests/FilterTests.swift @@ -1,4 +1,4 @@ -import Filter +import LibFilter import XCTest final class FilterTests: XCTestCase { @@ -14,6 +14,11 @@ final class FilterTests: XCTestCase { (host: "giphy.com", url: nil, src: "com.widget", allow: false), (host: "media0.giphy.com", url: nil, src: "com.widget", allow: false), (host: "media.fosu2-1.fna.whatsapp.net", url: nil, src: "", allow: false), + // block these only from Messages app, they allow searching/viewing app store content + (host: "amp-api-edge.apps.apple.com", url: nil, src: ".com.apple.MobileSMS", allow: false), + (host: "amp-api-edge.apps.apple.com", url: nil, src: "com.widget", allow: true), + (host: "is1-ssl.mzstatic.com", url: nil, src: ".com.apple.MobileSMS", allow: false), + (host: "is1-ssl.mzstatic.com", url: nil, src: "com.widget", allow: true), ] for (host, url, src, expected) in cases { From f941f40af9b7a501a64727d9d367bed3f4b34492 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 12:09:35 -0400 Subject: [PATCH 13/14] iosapp: set build numbers for initial release --- iosapp/Gertrude-iOS.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj index 4dd77f18..bf9e6ae1 100644 --- a/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj +++ b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj @@ -529,7 +529,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = app/app.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"app/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; @@ -546,7 +546,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -568,7 +568,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = app/app.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"app/Preview Content\""; DEVELOPMENT_TEAM = WFN83LM943; ENABLE_PREVIEWS = YES; @@ -585,7 +585,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.netrivet.gertrude-ios.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; From a81adab420ed37a2c55b7b647021ae26a6d7aa25 Mon Sep 17 00:00:00 2001 From: Jared Henderson Date: Wed, 16 Oct 2024 13:42:31 -0400 Subject: [PATCH 14/14] iosapp: lock to portraid mode only --- iosapp/Gertrude-iOS.xcodeproj/project.pbxproj | 4 ++-- iosapp/app/Info.plist | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj index bf9e6ae1..b68b9e0e 100644 --- a/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj +++ b/iosapp/Gertrude-iOS.xcodeproj/project.pbxproj @@ -539,8 +539,8 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchImage; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -578,8 +578,8 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchImage; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/iosapp/app/Info.plist b/iosapp/app/Info.plist index fa221f2d..8146926c 100644 --- a/iosapp/app/Info.plist +++ b/iosapp/app/Info.plist @@ -2,12 +2,12 @@ - UILaunchScreen - + UILaunchScreen + UIColorName white UIImageName GertrudeIcon - +