diff --git a/Dialer/Dialer.xcodeproj/project.pbxproj b/Dialer/Dialer.xcodeproj/project.pbxproj index 5186613..78268ca 100644 --- a/Dialer/Dialer.xcodeproj/project.pbxproj +++ b/Dialer/Dialer.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -11,7 +11,6 @@ 8207FA9526776AB600412AE1 /* TransactionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8207FA9426776AB600412AE1 /* TransactionModel.swift */; }; 820C108A2766A31400A7CADE /* NumberField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820C10892766A31400A7CADE /* NumberField.swift */; }; 820C108E2766A52C00A7CADE /* ElectricityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820C108D2766A52C00A7CADE /* ElectricityView.swift */; }; - 8211528626E4EB1B00BA7BA2 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8211528526E4EB1B00BA7BA2 /* Color+Extension.swift */; }; 821BE13326FB19F200A3CA02 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821BE13226FB19F200A3CA02 /* AboutView.swift */; }; 821BE13526FB28CB00A3CA02 /* AppVersioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821BE13426FB28CB00A3CA02 /* AppVersioning.swift */; }; 821C0734264DD78F00DBD545 /* NewDialingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821C0733264DD78F00DBD545 /* NewDialingView.swift */; }; @@ -23,8 +22,7 @@ 8248440D26FDBB3200DB0643 /* SettingsOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8248440C26FDBB3200DB0643 /* SettingsOption.swift */; }; 825022E62673676B007ABB56 /* DialingsHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825022E52673676B007ABB56 /* DialingsHistoryView.swift */; }; 8255017A263B467A00CB1BC2 /* HistoryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82550179263B467A00CB1BC2 /* HistoryRow.swift */; }; - 8255E68E2693959800657EFB /* CongratulationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8255E68D2693959800657EFB /* CongratulationsView.swift */; }; - 8255E691269395EE00657EFB /* CongratsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8255E690269395EE00657EFB /* CongratsViewController.swift */; }; + 8255E68E2693959800657EFB /* ThanksYouView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8255E68D2693959800657EFB /* ThanksYouView.swift */; }; 825F996326720AE6009E4A7B /* TransferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F996226720AE6009E4A7B /* TransferView.swift */; }; 825F996626720B46009E4A7B /* ContactsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F996526720B46009E4A7B /* ContactsFetcher.swift */; }; 825F996A26720B7E009E4A7B /* ContactModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F996926720B7E009E4A7B /* ContactModel.swift */; }; @@ -147,7 +145,6 @@ 8207FA9426776AB600412AE1 /* TransactionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionModel.swift; sourceTree = ""; }; 820C10892766A31400A7CADE /* NumberField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberField.swift; sourceTree = ""; }; 820C108D2766A52C00A7CADE /* ElectricityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectricityView.swift; sourceTree = ""; }; - 8211528526E4EB1B00BA7BA2 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = ""; }; 821BE13226FB19F200A3CA02 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 821BE13426FB28CB00A3CA02 /* AppVersioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersioning.swift; sourceTree = ""; }; 821C0733264DD78F00DBD545 /* NewDialingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDialingView.swift; sourceTree = ""; }; @@ -160,8 +157,7 @@ 825022E52673676B007ABB56 /* DialingsHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialingsHistoryView.swift; sourceTree = ""; }; 82550129263B3BDF00CB1BC2 /* Dialer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Dialer.entitlements; sourceTree = ""; }; 82550179263B467A00CB1BC2 /* HistoryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryRow.swift; sourceTree = ""; }; - 8255E68D2693959800657EFB /* CongratulationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CongratulationsView.swift; sourceTree = ""; }; - 8255E690269395EE00657EFB /* CongratsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CongratsViewController.swift; sourceTree = ""; }; + 8255E68D2693959800657EFB /* ThanksYouView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThanksYouView.swift; sourceTree = ""; }; 825F996226720AE6009E4A7B /* TransferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferView.swift; sourceTree = ""; }; 825F996526720B46009E4A7B /* ContactsFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsFetcher.swift; sourceTree = ""; }; 825F996926720B7E009E4A7B /* ContactModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactModel.swift; sourceTree = ""; }; @@ -311,8 +307,7 @@ 8218DBA72694644600E3C025 /* SubViews */ = { isa = PBXGroup; children = ( - 8255E68D2693959800657EFB /* CongratulationsView.swift */, - 8255E690269395EE00657EFB /* CongratsViewController.swift */, + 8255E68D2693959800657EFB /* ThanksYouView.swift */, 82550179263B467A00CB1BC2 /* HistoryRow.swift */, CBAF13182785A54100D32E1F /* TappeableText.swift */, CB393A2A28121501009CA95E /* CopiedUSSDLabel.swift */, @@ -403,7 +398,6 @@ isa = PBXGroup; children = ( 82D7C1A6266D26E7003E0E0E /* Views+Extension.swift */, - 8211528526E4EB1B00BA7BA2 /* Color+Extension.swift */, CB393A2628120A59009CA95E /* String+Extension.swift */, CB393A2828120A73009CA95E /* Binding+Extension.swift */, CB92F70429AFEC98004BBE32 /* TimeInterval+Extensions.swift */, @@ -710,8 +704,9 @@ 82D41FAB25D1C1FE00551D22 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1230; + LastUpgradeCheck = 1500; TargetAttributes = { 82D41FB225D1C1FE00551D22 = { CreatedOnToolsVersion = 12.3; @@ -830,7 +825,6 @@ CB3B81EA29ABD2B0001FAFF6 /* CreateMerchantView.swift in Sources */, 821BE13326FB19F200A3CA02 /* AboutView.swift in Sources */, CBE3D8A7290C147300DF5C09 /* CodePin.swift in Sources */, - 8255E691269395EE00657EFB /* CongratsViewController.swift in Sources */, CB1099502A616EFA00E99555 /* ContactsViewModel.swift in Sources */, CBC8AE3228D87F0000FE55A0 /* DialerExternalLinks.swift in Sources */, CBAF13172785A34800D32E1F /* ElectricityMeter.swift in Sources */, @@ -853,11 +847,10 @@ CBA66BAC2A5EB2600082C8CE /* PaywallView.swift in Sources */, CB338CD829AF668300BEAD9A /* FirebaseTracker+Extensions.swift in Sources */, CB3B81E629ABD0D8001FAFF6 /* FirebaseManager.swift in Sources */, - 8255E68E2693959800657EFB /* CongratulationsView.swift in Sources */, + 8255E68E2693959800657EFB /* ThanksYouView.swift in Sources */, CB338CDC29AF6BDC00BEAD9A /* AppConfiguration.swift in Sources */, CB45380E28AB7CE300F3F24C /* QuickDialingView.swift in Sources */, 828D79D726E1029800439F9A /* SettingsView.swift in Sources */, - 8211528626E4EB1B00BA7BA2 /* Color+Extension.swift in Sources */, CB10994A2A6155B500E99555 /* ScanModels.swift in Sources */, CB92F70929AFF52D004BBE32 /* DateFormatter+Extensions.swift in Sources */, CBA429E029AB85F50041D578 /* SpeakerView.swift in Sources */, @@ -946,6 +939,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -980,6 +974,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1010,6 +1005,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1044,6 +1040,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1081,7 +1078,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.0.0; + MARKETING_VERSION = 5.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.abc.incs.cedricbahirwe.Dialit; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1108,7 +1105,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.0.0; + MARKETING_VERSION = 5.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.abc.incs.cedricbahirwe.Dialit; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1163,6 +1160,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1197,6 +1195,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1242,7 +1241,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.0.0; + MARKETING_VERSION = 5.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.abc.incs.cedricbahirwe.Dialit.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1295,6 +1294,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1329,6 +1329,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1368,7 +1369,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.0.0; + MARKETING_VERSION = 5.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.abc.incs.cedricbahirwe.Dialit.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1509,15 +1510,17 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 9.0.0; + kind = versionRange; + maximumVersion = 11.0.0; + minimumVersion = 10.0.0; }; }; CBA66BAD2A5EB3240082C8CE /* XCRemoteSwiftPackageReference "purchases-ios" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/RevenueCat/purchases-ios"; requirement = { - kind = upToNextMajorVersion; + kind = versionRange; + maximumVersion = 5.0.0; minimumVersion = 4.0.0; }; }; diff --git a/Dialer/Dialer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Dialer/Dialer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c4d9f1a..f668906 100644 --- a/Dialer/Dialer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Dialer/Dialer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,21 +1,12 @@ { "pins" : [ { - "identity" : "abseil-cpp-swiftpm", + "identity" : "abseil-cpp-binary", "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "location" : "https://github.com/google/abseil-cpp-binary.git", "state" : { - "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", - "version" : "0.20220203.2" - } - }, - { - "identity" : "boringssl-swiftpm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/boringssl-SwiftPM.git", - "state" : { - "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", - "version" : "0.9.1" + "revision" : "bfc0b6f81adc06ce5121eb23f628473638d67c5c", + "version" : "1.2022062300.0" } }, { @@ -23,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { - "revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050", - "version" : "9.6.0" + "revision" : "8a8ec57a272e0d31480fb0893dda0cf4f769b57e", + "version" : "10.15.0" } }, { @@ -32,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "c1cfde8067668027b23a42c29d11c246152fe046", - "version" : "9.6.0" + "revision" : "03b9beee1a61f62d32c521e172e192a1663a5e8b", + "version" : "10.13.0" } }, { @@ -55,12 +46,12 @@ } }, { - "identity" : "grpc-ios", + "identity" : "grpc-binary", "kind" : "remoteSourceControl", - "location" : "https://github.com/grpc/grpc-ios.git", + "location" : "https://github.com/google/grpc-binary.git", "state" : { - "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", - "version" : "1.44.3-grpc" + "revision" : "f1b366129d1125be7db83247e003fc333104b569", + "version" : "1.50.2" } }, { @@ -72,6 +63,15 @@ "version" : "2.3.0" } }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", diff --git a/Dialer/Dialer.xcodeproj/xcshareddata/xcschemes/Dialer-DEV.xcscheme b/Dialer/Dialer.xcodeproj/xcshareddata/xcschemes/Dialer-DEV.xcscheme index c9547b9..1c9cb34 100644 --- a/Dialer/Dialer.xcodeproj/xcshareddata/xcschemes/Dialer-DEV.xcscheme +++ b/Dialer/Dialer.xcodeproj/xcshareddata/xcschemes/Dialer-DEV.xcscheme @@ -1,6 +1,6 @@ Bool { - + DialerStorage.shared.saveOneTimeUniqueAppID() configureFirebase() configureRevenuCat() return true @@ -35,7 +35,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func configureRevenuCat() { - Purchases.logLevel = .debug +// Purchases.logLevel = .debug Purchases.configure( with: diff --git a/Dialer/Dialer/Extensions/Color+Extension.swift b/Dialer/Dialer/Extensions/Color+Extension.swift deleted file mode 100644 index a7f5bf6..0000000 --- a/Dialer/Dialer/Extensions/Color+Extension.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Color+Extension.swift -// Dialer -// -// Created by Cédric Bahirwe on 05/09/2021. -// - -import SwiftUI - -extension UIColor { - static let primaryBackground = UIColor(Color.primaryBackground) -} - -extension Color { - static var primaryBackground = Color("primaryBackground") - static var lightShadow = Color("lightShadow") - static var darkShadow = Color("darkShadow") - static let main = Color("main") - static let mainRed = Color("mainRed") -} diff --git a/Dialer/Dialer/Extensions/Views+Extension.swift b/Dialer/Dialer/Extensions/Views+Extension.swift index 3bc61e1..44a3973 100644 --- a/Dialer/Dialer/Extensions/Views+Extension.swift +++ b/Dialer/Dialer/Extensions/Views+Extension.swift @@ -10,7 +10,7 @@ import SwiftUI struct BiometricsAccessibility: ViewModifier { private let biometrics = BiometricsAuth.shared var onEvaluation: (Bool) -> Void - @AppStorage(UserDefaults.Keys.allowBiometrics) + @AppStorage(UserDefaultsKeys.allowBiometrics) private var allowBiometrics = false func body(content: Content) -> some View { @@ -30,7 +30,15 @@ struct BiometricsAccessibility: ViewModifier { } } +extension Bool { + static var isIOS16AndPlus: Bool { + guard #available(iOS 16.0.0, *) else { return false } + return true + } +} + extension View { + /// Tracking screen appearance and disappearance func trackAppearance(_ screen: ScreenName) -> some View { self diff --git a/Dialer/Dialer/Models/DeviceAccount.swift b/Dialer/Dialer/Models/DeviceAccount.swift index fbb33a8..0099765 100644 --- a/Dialer/Dialer/Models/DeviceAccount.swift +++ b/Dialer/Dialer/Models/DeviceAccount.swift @@ -8,8 +8,8 @@ import Foundation import FirebaseFirestoreSwift -struct DeviceAccount: Identifiable, Codable { - @DocumentID var id: String? +struct DeviceAccount: Codable { + @DocumentID private var id: String? var name: String let model: String @@ -24,6 +24,7 @@ struct DeviceAccount: Identifiable, Codable { func toDictionary() -> [String: Any] { var dictionary: [String: Any] = [:] + dictionary["id"] = id dictionary["name"] = name dictionary["model"] = model dictionary["system_version"] = systemVersion @@ -35,4 +36,31 @@ struct DeviceAccount: Identifiable, Codable { dictionary["last_visited_date"] = lastVisitedDate return dictionary } + + init(id: String? = nil, name: String, model: String, systemVersion: String, systemName: String, deviceHash: String, appVersion: String?, bundleVersion: String?, bundleId: String?, lastVisitedDate: String?) { + self.id = id + self.name = name + self.model = model + self.systemVersion = systemVersion + self.systemName = systemName + self.deviceHash = deviceHash + self.appVersion = appVersion + self.bundleVersion = bundleVersion + self.bundleId = bundleId + self.lastVisitedDate = lastVisitedDate + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.deviceHash = try container.decode(String.self, forKey: .deviceHash) + self._id = try container.decodeIfPresent(DocumentID.self, forKey: .id) ?? DocumentID(wrappedValue: deviceHash) + self.name = try container.decode(String.self, forKey: .name) + self.model = try container.decode(String.self, forKey: .model) + self.systemVersion = try container.decode(String.self, forKey: .systemVersion) + self.systemName = try container.decode(String.self, forKey: .systemName) + self.appVersion = try container.decodeIfPresent(String.self, forKey: .appVersion) + self.bundleVersion = try container.decodeIfPresent(String.self, forKey: .bundleVersion) + self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) + self.lastVisitedDate = try container.decodeIfPresent(String.self, forKey: .lastVisitedDate) + } } diff --git a/Dialer/Dialer/Models/Merchant.swift b/Dialer/Dialer/Models/Merchant.swift index e0544e3..0ef94e8 100644 --- a/Dialer/Dialer/Models/Merchant.swift +++ b/Dialer/Dialer/Models/Merchant.swift @@ -15,17 +15,17 @@ struct Merchant: Codable, Identifiable { let address: String? let code: String let ownerId: String? - var hashCode = UUID() - var createdDate: Date? = Date() + var hashCode: UUID + var createdDate: Date? - init(_ id: String? = nil, name: String, address: String?, code: String, ownerId: String, createdDate: Date = Date()) { + init(_ id: String? = nil, name: String, address: String?, code: String, ownerId: String) { self.id = id self.name = name self.address = address self.code = code self.ownerId = ownerId self.hashCode = UUID() - self.createdDate = createdDate + self.createdDate = Date() } } diff --git a/Dialer/Dialer/Services/FirebaseTracker+Extensions.swift b/Dialer/Dialer/Services/FirebaseTracker+Extensions.swift index b8d7182..061bf9a 100644 --- a/Dialer/Dialer/Services/FirebaseTracker+Extensions.swift +++ b/Dialer/Dialer/Services/FirebaseTracker+Extensions.swift @@ -41,7 +41,6 @@ extension FirebaseTracker: TrackerProtocol { .transType : transaction.type.rawValue, .transCode : transaction.fullCode, .transTime : Date.now.formatted(), - .devId: user.id as Any, .devHash: user.deviceHash ] diff --git a/Dialer/Dialer/Services/FirebaseTracker.swift b/Dialer/Dialer/Services/FirebaseTracker.swift index 0933626..8d8d5a4 100644 --- a/Dialer/Dialer/Services/FirebaseTracker.swift +++ b/Dialer/Dialer/Services/FirebaseTracker.swift @@ -87,7 +87,7 @@ class FirebaseTracker { let deviceModel = device.model let deviceSystemVersion = device.systemVersion let deviceSystemName = device.systemName - let deviceIdentifier = (device.identifierForVendor ?? .init()).uuidString + let deviceIdentifier = DialerStorage.shared.getOneTimeUniqueAppID()!.uuidString let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String let bundleID = Bundle.main.bundleIdentifier diff --git a/Dialer/Dialer/Services/Tracker.swift b/Dialer/Dialer/Services/Tracker.swift index b09b31f..f8255a7 100644 --- a/Dialer/Dialer/Services/Tracker.swift +++ b/Dialer/Dialer/Services/Tracker.swift @@ -73,7 +73,6 @@ enum EventParameterKey: String { case transTime = "tran_timestamp" // Device - case devId = "device_id" case devHash = "device_hash" case devVersion = "device_version" } diff --git a/Dialer/Dialer/Utilities/AppReviewing.swift b/Dialer/Dialer/Utilities/AppReviewing.swift index 5911285..69867c0 100644 --- a/Dialer/Dialer/Utilities/AppReviewing.swift +++ b/Dialer/Dialer/Utilities/AppReviewing.swift @@ -10,7 +10,7 @@ import StoreKit enum ReviewHandler { static func requestReview() { - var count = UserDefaults.standard.integer(forKey: UserDefaults.Keys.appStartUpsCountKey) + var count = UserDefaults.standard.integer(forKey: UserDefaultsKeys.appStartUpsCountKey) count += 1 UserDefaults.standard.set(count, forKey: UserDefaultsKeys.appStartUpsCountKey) @@ -21,7 +21,7 @@ enum ReviewHandler { let lastVersionPromptedForReview = UserDefaults.standard.string(forKey: UserDefaultsKeys.lastVersionPromptedForReviewKey) if count%4==0 && currentVersion != lastVersionPromptedForReview { - DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 1.0) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) { if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) UserDefaults.standard.set(currentVersion, forKey: UserDefaultsKeys.lastVersionPromptedForReviewKey) diff --git a/Dialer/Dialer/Utilities/FirebaseCRUD.swift b/Dialer/Dialer/Utilities/FirebaseCRUD.swift index 0d19334..280257d 100644 --- a/Dialer/Dialer/Utilities/FirebaseCRUD.swift +++ b/Dialer/Dialer/Utilities/FirebaseCRUD.swift @@ -63,6 +63,7 @@ extension FirebaseCRUD { } func getAllWithQuery(_ querySnapshot: QuerySnapshot) async -> [T] { + let result = querySnapshot.documents.compactMap { document -> T? in do { return try document.data(as: T.self) @@ -109,7 +110,7 @@ extension FirebaseCRUD { continuation.resume(returning: true) } catch { - Log.debug("Error updating Merchant: \(error)") + Log.debug("Error updating item: \(error)") continuation.resume(throwing: error) } } diff --git a/Dialer/Dialer/Utilities/FirebaseManager.swift b/Dialer/Dialer/Utilities/FirebaseManager.swift index 2bac20d..098a168 100644 --- a/Dialer/Dialer/Utilities/FirebaseManager.swift +++ b/Dialer/Dialer/Utilities/FirebaseManager.swift @@ -69,8 +69,7 @@ extension FirebaseManager: DeviceManagerProtocol { } func updateDevice(_ device: DeviceAccount) async throws -> Bool { - guard let deviceID = device.id else { return false } - return try await updateItemWithID(deviceID, content: device, in: .devices) + return try await updateItemWithID(device.deviceHash, content: device, in: .devices) } func deleteDevice(_ deviceID: String) async throws { diff --git a/Dialer/Dialer/Utilities/ForceUpdateManager.swift b/Dialer/Dialer/Utilities/ForceUpdateManager.swift index 50222e1..68819d4 100644 --- a/Dialer/Dialer/Utilities/ForceUpdateManager.swift +++ b/Dialer/Dialer/Utilities/ForceUpdateManager.swift @@ -7,8 +7,8 @@ import SwiftUI -enum AppUpdateType: Int { - case majorUpdates, minorUpdates, noUpdates +enum AppUpdateType { + case majorUpdate(_ version: String), minorUpdate(_ version: String), noUpdate } final class ForceUpdateManager: ObservableObject { @@ -38,26 +38,26 @@ final class ForceUpdateManager: ObservableObject { guard let storeAppVersion = RemoteConfigs.shared.string(for: .latestAppVersion) else { fatalError() } let update = getTypeOfUpdate(storeAppVersion: storeAppVersion) switch update { - case .noUpdates: + case .noUpdate: DialerStorage.shared.saveLastAskedDateToUpdate(nil) break - case .majorUpdates: + case .majorUpdate(let version): let actions = [ - UpdateAlert.Action("Download", action: openAppOnStore) + UpdateAlert.Action("Download v.\(version)", action: openAppOnStore) ] self.updateAlert = .init(title: "Please, Update your app!", message: "You haven't updated your app for a long time! Quickly download the latest version to take advantage of the new features. It's quick and easy !", buttons: actions) - case .minorUpdates: + case .minorUpdate(let version): let actions: [UpdateAlert.Action] = [ .init("Not Now", action: { DialerStorage.shared.saveLastAskedDateToUpdate(.now) }), - .init("Download", action: openAppOnStore) + .init("Download \(version)", action: openAppOnStore) ] - self.updateAlert = .init(title: "New Version available!", + self.updateAlert = .init(title: "New Version is available!", message: "A new version of the app is available. Download it as soon as possible to enjoy all the latest features!", buttons: actions) @@ -73,15 +73,15 @@ final class ForceUpdateManager: ObservableObject { let currentMajorVersion = currentVersionArray[0] let storeMajorVersion = storeVersionArray[0] if (currentMajorVersion < storeMajorVersion) { - return .majorUpdates + return .majorUpdate(storeAppVersion) } else if (currentMajorVersion == storeMajorVersion) && (currentVersionArray[1] < storeVersionArray[1]) { - return .minorUpdates + return .minorUpdate(storeAppVersion) } } DialerStorage.shared.saveLastAskedDateToUpdate(nil) - return .noUpdates + return .noUpdate } private func openAppOnStore() { diff --git a/Dialer/Dialer/Utilities/UserDefaults+Extension.swift b/Dialer/Dialer/Utilities/UserDefaults+Extension.swift index 1d52b4f..253c976 100644 --- a/Dialer/Dialer/Utilities/UserDefaults+Extension.swift +++ b/Dialer/Dialer/Utilities/UserDefaults+Extension.swift @@ -7,37 +7,34 @@ import Foundation -typealias UserDefaultsKeys = UserDefaults.Keys -extension UserDefaults { - - /// Storing the used UserDefaults keys for safety. - enum Keys { - static let recentCodes = "recentCodes" - static let pinCode = "pinCode" - static let lastSyncDate = "lastSyncDate" - - // Onboarding - static let showWelcomeView = "showWelcomeView" - - // Electricity - static let meterNumbers = "meterNumbers" - - // Biometrics - static let allowBiometrics = "allowBiometrics" - - // Review - static let appStartUpsCountKey = "appStartUpsCountKey" - static let lastVersionPromptedForReviewKey = "lastVersionPromptedForReviewKey" - - // Custom USSD codes - static let customUSSDCodes = "customUSSDCodes" - - // Last Date the app asked the user to update - static let lastAskedDateToUpdate = "lastAskedDateToUpdate" - - static let deviceAccount = "deviceAccount" - - // Daily local notification scheduled - static let dailyNotificationEnabled = "dailyNotificationEnabled" - } +enum UserDefaultsKeys { + static let appUniqueID = "appUniqueIdentifier" + static let recentCodes = "recentCodes" + static let pinCode = "pinCode" + static let lastSyncDate = "lastSyncDate" + + // Onboarding + static let showWelcomeView = "showWelcomeView" + + // Electricity + static let meterNumbers = "meterNumbers" + + // Biometrics + static let allowBiometrics = "allowBiometrics" + + // Review + static let appStartUpsCountKey = "appStartUpsCountKey" + static let lastVersionPromptedForReviewKey = "lastVersionPromptedForReviewKey" + + // Custom USSD codes + static let customUSSDCodes = "customUSSDCodes" + + // Last Date the app asked the user to update + static let lastAskedDateToUpdate = "lastAskedDateToUpdate" + + static let deviceAccount = "deviceAccount" + + // Daily local notification scheduled + static let dailyNotificationEnabled = "dailyNotificationEnabled" } + diff --git a/Dialer/Dialer/View Models/LocalStorage.swift b/Dialer/Dialer/View Models/LocalStorage.swift index 041d384..681dd75 100644 --- a/Dialer/Dialer/View Models/LocalStorage.swift +++ b/Dialer/Dialer/View Models/LocalStorage.swift @@ -13,13 +13,26 @@ final class DialerStorage { typealias ElectricityMeters = [ElectricityMeter] typealias USSDCodes = [USSDCode] - private let LocalKeys = UserDefaults.Keys.self + private let LocalKeys = UserDefaultsKeys.self private let userDefaults = UserDefaults.standard static let shared = DialerStorage() private init() { } + + func saveOneTimeUniqueAppID() { + guard getOneTimeUniqueAppID() == nil else { return } + userDefaults.setValue(UUID().uuidString, forKey: LocalKeys.appUniqueID) + } + + func getOneTimeUniqueAppID() -> UUID? { + guard let uniqueIDString = userDefaults.value(forKey: LocalKeys.appUniqueID) as? String, + let uniqueID = UUID(uuidString: uniqueIDString) else { + return nil + } + return uniqueID + } func saveCodePin(_ value: CodePin) throws { let data = try encodeData(value) @@ -135,31 +148,32 @@ private extension DialerStorage { return dictionary } - func encodeData(_ value: T) throws -> Data where T: Codable { - return try JSONEncoder().encode(value) - } - - func decodeData(key: String, as type: T.Type) -> T? { - guard let data = userDefaults.object(forKey: key) as? Data else { + func decodeDataWithFirebase(key: String, as type: T.Type) -> T? { + guard let dictionary = userDefaults.object(forKey: key) as? [String: Any] else { + userDefaults.removeObject(forKey: key) return nil } + do { - return try JSONDecoder().decode(type, from: data) + return try Firestore.Decoder().decode(type, from: dictionary) } catch let error { - Log.debug("Couldn't decode the data of type \(type): ", error.localizedDescription) + Log.debug("Couldn't decode the firebase data of type \(type): ", error) } return nil } - func decodeDataWithFirebase(key: String, as type: T.Type) -> T? { - guard let dictionary = userDefaults.object(forKey: key) as? [String: Any] else { - userDefaults.removeObject(forKey: key) + func encodeData(_ value: T) throws -> Data where T: Codable { + return try JSONEncoder().encode(value) + } + + func decodeData(key: String, as type: T.Type) -> T? { + guard let data = userDefaults.object(forKey: key) as? Data else { return nil } do { - return try Firestore.Decoder().decode(type, from: dictionary) + return try JSONDecoder().decode(type, from: data) } catch let error { - Log.debug("Couldn't decode the firebase data of type \(type): ", error) + Log.debug("Couldn't decode the data of type \(type): ", error.localizedDescription) } return nil } diff --git a/Dialer/Dialer/Views/ContentView.swift b/Dialer/Dialer/Views/ContentView.swift index d0f837e..2e866bd 100644 --- a/Dialer/Dialer/Views/ContentView.swift +++ b/Dialer/Dialer/Views/ContentView.swift @@ -19,7 +19,7 @@ struct ContentView: View { do { UserViewModel.shared.offerings = try await Purchases.shared.offerings() } catch { - print("Error fetching offerings: \(error)") + Log.debug("Error fetching offerings: \(error)") } } } @@ -36,7 +36,7 @@ struct ContentView: View { Text($0.message) } .fullScreenCover(isPresented: $data.hasReachSync) { - CongratulationsView(isPresented: $data.hasReachSync) + ThanksYouView(isPresented: $data.hasReachSync) } .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in Task { diff --git a/Dialer/Dialer/Views/DashBoardView.swift b/Dialer/Dialer/Views/DashBoardView.swift index bee5fcc..b8b4c43 100644 --- a/Dialer/Dialer/Views/DashBoardView.swift +++ b/Dialer/Dialer/Views/DashBoardView.swift @@ -10,10 +10,10 @@ import SwiftUI struct DashBoardView: View { @EnvironmentObject private var data: MainViewModel - @AppStorage(UserDefaults.Keys.showWelcomeView) + @AppStorage(UserDefaultsKeys.showWelcomeView) private var showWelcomeView: Bool = true - @AppStorage(UserDefaults.Keys.allowBiometrics) + @AppStorage(UserDefaultsKeys.allowBiometrics) private var allowBiometrics = false @State private var presentQuickDial = false @@ -170,7 +170,7 @@ extension DashBoardView { Label("Quick Dial", systemImage: "plus.circle.fill") .font(.system(size: 18, weight: .semibold, design: .rounded)) } - .foregroundColor(.mainRed) + .foregroundStyle(.mainRed) } Spacer(minLength: 5) diff --git a/Dialer/Dialer/Views/Main/PurchaseDetailView.swift b/Dialer/Dialer/Views/Main/PurchaseDetailView.swift index ad55af7..0677ec1 100644 --- a/Dialer/Dialer/Views/Main/PurchaseDetailView.swift +++ b/Dialer/Dialer/Views/Main/PurchaseDetailView.swift @@ -93,13 +93,12 @@ struct PurchaseDetailView: View { self.codepin = "" }){ Text("Save") - .font(.caption) .fontWeight(.semibold) - .padding(.horizontal, 10) + .padding(.horizontal, 20) .frame(height: 40) .background(Color.primary) .cornerRadius(8) - .foregroundColor(Color(.systemBackground)) + .foregroundStyle(.background) } .disabled(!data.isPinCodeValid) .opacity(data.isPinCodeValid ? 1 : 0.4) diff --git a/Dialer/Dialer/Views/Other/QuickDialingView.swift b/Dialer/Dialer/Views/Other/QuickDialingView.swift index 93b1417..772dfc2 100644 --- a/Dialer/Dialer/Views/Other/QuickDialingView.swift +++ b/Dialer/Dialer/Views/Other/QuickDialingView.swift @@ -57,7 +57,7 @@ struct QuickDialingView: View { .scaledToFit() .frame(width: 75, height: 75) .clipShape(Circle()) - .foregroundColor(.green) + .foregroundStyle(.white, Color.accentColor) }) .frame(maxWidth: .infinity) .overlay(bottomNavigationView) diff --git a/Dialer/Dialer/Views/Other/SettingsView.swift b/Dialer/Dialer/Views/Other/SettingsView.swift index 509c6f4..d009ea5 100644 --- a/Dialer/Dialer/Views/Other/SettingsView.swift +++ b/Dialer/Dialer/Views/Other/SettingsView.swift @@ -11,116 +11,101 @@ import MessageUI struct SettingsView: View { @EnvironmentObject var dataStore: MainViewModel - @AppStorage(UserDefaults.Keys.allowBiometrics) + @AppStorage(UserDefaultsKeys.allowBiometrics) private var allowBiometrics = false - @State var showMailView = false - @State var showMailErrorAlert = false @State var alertItem: AlertDialog? @State var showDialog = false - + + @StateObject private var mailComposer = MailComposer() + var body: some View { NavigationView { - ScrollView { - VStack(alignment: .leading, spacing: 0) { + Form { + Section { + HStack(spacing: 3) { + SettingsRow(.biometrics) + Toggle("Biometrics", isOn: $allowBiometrics) + .toggleStyle(SwitchToggleStyle()) + .labelsHidden() + } - Section(header: sectionHeader("General settings")) { - VStack { - - HStack(spacing: 3) { - SettingsRow(.biometrics) - Toggle("Biometrics", isOn: $allowBiometrics) - .toggleStyle(SwitchToggleStyle()) - .labelsHidden() - } - - if dataStore.hasStoredCodePin() { - SettingsRow(.deletePin, - action: presentPinRemovalSheet) - } - - if !dataStore.ussdCodes.isEmpty { - SettingsRow(.deleteUSSDs, - action: presentUSSDsRemovalSheet) - } - } - .padding(.bottom, 20) + if dataStore.hasStoredCodePin() { + SettingsRow(.deletePin, + action: presentPinRemovalSheet) } - .confirmationDialog("Confirmation", - isPresented: $showDialog, - titleVisibility: .visible, - presenting: alertItem) { item in - - Button("Delete", - role: .destructive, - action: item.action) - - Button("Cancel", - role: .cancel) { - alertItem = nil - } - } message: { item in - VStack { - Text(item.message) - Text(item.title ?? "asfa") - } + + if !dataStore.ussdCodes.isEmpty { + SettingsRow(.deleteUSSDs, + action: presentUSSDsRemovalSheet) } - -// Section(header: sectionHeader("Tips and Guides")){ -// VStack { -// HStack(spacing: 0) { -// SettingsRow(.getStarted, exists: false) -// } -// } -// .padding(.bottom, 20) -// } + } header: { + sectionHeader("General settings") + } + + .confirmationDialog("Confirmation", + isPresented: $showDialog, + titleVisibility: .visible, + presenting: alertItem) { item in - Section(header: sectionHeader("Reach Out")) { - VStack { - SettingsRow(.contactUs, action: openMail) - .alert("No Email Client Found", - isPresented: $showMailErrorAlert) { - Button("OK", role: .cancel) { } - Button("Copy Support Email", action: copyEmail) - Button("Open Twitter", action: openTwitter) - } message: { - Text(String(format: - NSLocalizedString("We could not detect a default mail service on your device.\n\n You can reach us on Twitter, or send us an email to supportEmail as well.", comment: ""), - DialerlLinks.supportEmail - ) - ) - } - Link(destination: URL(string: DialerlLinks.dialerTwitter)!) { - SettingsRow(.tweetUs) - } - - SettingsRow(.translationSuggestion, action: openMail) - - } - .padding(.bottom, 20) + Button("Delete", + role: .destructive, + action: item.action) + + Button("Cancel", + role: .cancel) { + alertItem = nil } - Section(header: sectionHeader("Colophon")) { - - VStack { - NavigationLink(destination: AboutView()) { - SettingsRow(.about, allowNavigation: true) - } - - SettingsRow(.review, allowNavigation: true, action: ReviewHandler.requestReviewManually) + } message: { item in + VStack { + Text(item.message) + Text(item.title ?? "asfa") + } + } + + Section { + + SettingsRow(.contactUs, action: mailComposer.openMail) + .alert("No Email Client Found", + isPresented: $mailComposer.showMailErrorAlert) { + Button("OK", role: .cancel) { } + Button("Copy Support Email", action: copyEmail) + Button("Open Twitter", action: openTwitter) + } message: { + Text(String(format: + NSLocalizedString("We could not detect a default mail service on your device.\n\n You can reach us on Twitter, or send us an email to supportEmail as well.", comment: ""), + DialerlLinks.supportEmail + ) + ) } - .padding(.bottom, 20) + Link(destination: URL(string: DialerlLinks.dialerTwitter)!) { + SettingsRow(.tweetUs) + } + + SettingsRow(.translationSuggestion, action: mailComposer.openMail) + + } header: { + sectionHeader("Reach Out") + } + + Section { + NavigationLink(destination: AboutView()) { + SettingsRow(.about) } + + SettingsRow(.review, action: ReviewHandler.requestReviewManually) + + } header: { + sectionHeader("Colophon") } - .padding(.horizontal, 10) - .foregroundColor(.primary.opacity(0.8)) + } - .background(Color.primaryBackground) + + .foregroundColor(.primary.opacity(0.8)) .navigationTitle("Help & More") .navigationBarTitleDisplayMode(.inline) .onAppear(perform: ReviewHandler.requestReview) - .sheet(isPresented: $showMailView) { - MailView(recipientEmail: DialerlLinks.supportEmail, - subject: "Dialer Question", - bodyMessage: getEmailBody()) + .sheet(isPresented: $mailComposer.showMailView) { + mailComposer.makeMailView() } .safeAreaInset(edge: .bottom, content: { @@ -141,14 +126,14 @@ struct SettingsView: View { .trackAppearance(.settings) } } - + private func presentPinRemovalSheet() { alertItem = .init("Confirmation", message: "Do you really want to remove your pin?.\nYou'll need to re-enter it manually later.", action: dataStore.removePin) showDialog.toggle() } - + private func presentUSSDsRemovalSheet() { alertItem = .init("Confirmation", message: "Do you really want to remove your saved USSD codes?\nThis action can not be undone.", @@ -156,25 +141,6 @@ struct SettingsView: View { showDialog.toggle() } - - private func getEmailBody() -> String { - var body = "\n\n\n\n\n\n\n\n" - - let deviceName = UIDevice.current.localizedModel - body.append(contentsOf: deviceName) - - let iosVersion = "iOS Version: \(UIDevice.current.systemVersion)" - body.append(iosVersion) - - if let appVersion = UIApplication.appVersion { - body.append("\nDialer Version: \(appVersion)") - } - if let buildVersion = UIApplication.buildVersion { - body.append("\nDialer Build: \(buildVersion)") - } - return body - } - private func copyEmail() { let pasteBoard = UIPasteboard.general pasteBoard.string = DialerlLinks.dialerTwitter @@ -183,7 +149,7 @@ struct SettingsView: View { private func sectionHeader(_ title: LocalizedStringKey) -> some View { Text(title) .font(.title3.weight(.semibold)) - .padding(.vertical) + .textCase(nil) } @@ -192,15 +158,6 @@ struct SettingsView: View { guard let url = URL(string: DialerlLinks.dialerTwitter) else { return } UIApplication.shared.open(url) } - - private func openMail() { - - if MFMailComposeViewController.canSendMail() { - showMailView.toggle() - } else { - showMailErrorAlert = true - } - } } struct SettingsView_Previews: PreviewProvider { @@ -209,26 +166,23 @@ struct SettingsView_Previews: PreviewProvider { .environmentObject(MainViewModel()) } } + extension SettingsView { struct SettingsRow: View { init(_ option: SettingsOption, - allowNavigation: Bool = false, action: @escaping () -> Void) { self.item = option.getSettingsItem() - self.allowNavigation = allowNavigation self.action = action } - init(_ option: SettingsOption, allowNavigation: Bool = false) { + init(_ option: SettingsOption) { self.item = option.getSettingsItem() - self.allowNavigation = allowNavigation self.action = nil } private let item: SettingsItem - private let allowNavigation: Bool private let action: (() -> Void)? var body: some View { @@ -245,7 +199,7 @@ extension SettingsView { .resizable() .scaledToFit() .padding(6) - .frame(width: 30, height:30) + .frame(width: 28, height: 28) .background(item.color) .cornerRadius(6) .foregroundColor(.white) @@ -263,13 +217,46 @@ extension SettingsView { .padding(.leading, 15) Spacer(minLength: 1) - - if allowNavigation { - Image(systemName: "chevron.right") - .foregroundColor(.secondary) - } } - .padding(.vertical, 5) } } } + + + +private class MailComposer: ObservableObject { + @Published var showMailView = false + @Published var showMailErrorAlert = false + + func openMail() { + if MFMailComposeViewController.canSendMail() { + showMailView.toggle() + } else { + showMailErrorAlert = true + } + } + + @MainActor func makeMailView() -> MailView { + MailView(recipientEmail: DialerlLinks.supportEmail, + subject: "Dialer Question", + bodyMessage: getEmailBody()) + } + + private func getEmailBody() -> String { + var body = "\n\n\n\n\n\n\n\n" + + let deviceName = UIDevice.current.localizedModel + body.append(contentsOf: deviceName) + + let iosVersion = "iOS Version: \(UIDevice.current.systemVersion)" + body.append(iosVersion) + + if let appVersion = UIApplication.appVersion { + body.append("\nDialer Version: \(appVersion)") + } + if let buildVersion = UIApplication.buildVersion { + body.append("\nDialer Build: \(buildVersion)") + } + return body + } +} diff --git a/Dialer/Dialer/Views/Other/WhatsNewView.swift b/Dialer/Dialer/Views/Other/WhatsNewView.swift index 85be243..87ff23e 100644 --- a/Dialer/Dialer/Views/Other/WhatsNewView.swift +++ b/Dialer/Dialer/Views/Other/WhatsNewView.swift @@ -59,11 +59,11 @@ struct WhatsNewView: View { .font(.body.weight(.semibold)) .frame(maxWidth: .infinity) .frame(height: 50) - .background(Color.primaryBackground) + .background(Color.accentColor) .cornerRadius(15) .shadow(color: .lightShadow, radius: 3, x: -3, y: -3) .shadow(color: .darkShadow, radius: 3, x: 3, y: 3) - .foregroundColor(.mainRed) + .foregroundStyle(.white) } .padding([.horizontal,.bottom]) } @@ -124,7 +124,7 @@ private struct ChangeLog: Identifiable { } private extension ChangeLog { - static let version193 = [ + static let version500 = [ ChangeLog("phone.circle", "Airtime", "Ability to quickly generate USSD for buying airtime."), ChangeLog("clock.arrow.circlepath", "History", "Get direct access to your frequently used USSD codes."), ChangeLog("francsign.circle", "Transfer/Pay", "Get the right USSD code for transferring to your friend or paying to the store."), @@ -132,6 +132,6 @@ private extension ChangeLog { ] static var latestLogs: [ChangeLog] { - version193 + version500 } } diff --git a/Dialer/Dialer/Views/Reusable/QRScanner/CodeScannerViewController.swift b/Dialer/Dialer/Views/Reusable/QRScanner/CodeScannerViewController.swift index 925f082..61ada39 100644 --- a/Dialer/Dialer/Views/Reusable/QRScanner/CodeScannerViewController.swift +++ b/Dialer/Dialer/Views/Reusable/QRScanner/CodeScannerViewController.swift @@ -302,11 +302,11 @@ extension CodeScannerView.CodeScannerViewController: AVCapturePhotoCaptureDelega ) { isCapturing = false guard let imageData = photo.fileDataRepresentation() else { - print("Error while generating image from photo capture data."); + Log.debug("Error while generating image from photo capture data."); return } guard let qrImage = UIImage(data: imageData) else { - print("Unable to generate UIImage from image data."); + Log.debug("Unable to generate UIImage from image data."); return } handler?(qrImage) diff --git a/Dialer/Dialer/Views/SubViews/CongratsViewController.swift b/Dialer/Dialer/Views/SubViews/CongratsViewController.swift deleted file mode 100644 index 06336fb..0000000 --- a/Dialer/Dialer/Views/SubViews/CongratsViewController.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// CongratsViewController.swift -// Dialer -// -// Created by Cédric Bahirwe on 05/07/2021. -// - -import UIKit -import SwiftUI - -extension CongratulationsView { - internal struct CongratsView: UIViewControllerRepresentable { - func makeUIViewController(context: Context) -> UIViewController { - CongratsViewController() - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } - } -} - -fileprivate class CongratsViewController: UIViewController { - - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - createParticles() - } - - func createParticles() { - let particleEmitter = CAEmitterLayer() - - particleEmitter.emitterPosition = CGPoint(x: view.center.x, y: -96) - particleEmitter.emitterShape = .line - particleEmitter.emitterSize = CGSize(width: view.frame.size.width, height: 1) - - let red = makeEmitterCell(color: UIColor.red) - let green = makeEmitterCell(color: UIColor.yellow) - let blue = makeEmitterCell(color: UIColor.green) - let dialer = makeEmitterCell(color: UIColor.label) - - particleEmitter.emitterCells = [red, green, blue, dialer] - - view.layer.addSublayer(particleEmitter) - } - - - func makeEmitterCell(color: UIColor) -> CAEmitterCell { - let cell = CAEmitterCell() - cell.birthRate = 3 - cell.lifetime = 7.0 - cell.lifetimeRange = 0 - cell.color = color.cgColor - cell.velocity = 200 - cell.velocityRange = 50 - cell.emissionLongitude = CGFloat.pi - cell.emissionRange = CGFloat.pi / 4 - cell.spin = 2 - cell.spinRange = 3 - cell.scaleRange = 0.5 - cell.scaleSpeed = -0.05 - - cell.contents = drawImage().cgImage - return cell - } - - func drawImage() -> UIImage { - let renderer = UIGraphicsImageRenderer(size: CGSize(width: 35, height: 35)) - return renderer.image { _ in - // Draw image in circle - let image = UIImage(named: "emitter")! - let size = CGSize(width: 32, height: 32) - let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) - image.draw(in: rect) - } - } -} - - diff --git a/Dialer/Dialer/Views/SubViews/CongratulationsView.swift b/Dialer/Dialer/Views/SubViews/ThanksYouView.swift similarity index 63% rename from Dialer/Dialer/Views/SubViews/CongratulationsView.swift rename to Dialer/Dialer/Views/SubViews/ThanksYouView.swift index 128d90f..33da21c 100644 --- a/Dialer/Dialer/Views/SubViews/CongratulationsView.swift +++ b/Dialer/Dialer/Views/SubViews/ThanksYouView.swift @@ -1,5 +1,5 @@ // -// CongratulationsView.swift +// ThanksYouView.swift // Dialer // // Created by Cédric Bahirwe on 05/07/2021. @@ -7,47 +7,51 @@ import SwiftUI -struct CongratulationsView: View { +struct ThanksYouView: View { @Binding var isPresented: Bool - @State private var timeRemaining: Int = 71 + @State private var timeRemaining: Int = 30 @State private var isAppActive = true private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() - + var body: some View { ZStack { Color.primaryBackground .ignoresSafeArea() - CongratsView() - .opacity(timeRemaining <= 1 ? 0.3 : 1) - .blur(radius: timeRemaining <= 1 ? 3 : 0) - .animation(.linear(duration: 0.5), value: timeRemaining) + VStack(spacing: 10) { - Image("congrats") .resizable() .scaledToFit() .frame(height: 80) - .padding(.bottom, -10) - + Text("Thanks for using Dialer for the past month!") .fontWeight(.semibold) .foregroundColor(.secondary) - .opacity(0.8) .lineLimit(1) .minimumScaleFactor(0.8) - - Text("We appreciate your support, and would like to hear how to make **Dialer** even more better.") - .font(.caption) + + Text("Please tell us how we can make Dialer even more useful to you.") + .font(.callout) .multilineTextAlignment(.center) - - Text(String(format: NSLocalizedString("Remaining time: timeRemaining seconds", comment: ""), timeRemaining)) + + Button(action: goToAppStoreRating) { + Text("Rate our app") + .fontWeight(.semibold) + .frame(maxWidth: .infinity) + .frame(height: 45) + .background(Color.accentColor) + .cornerRadius(8) + .foregroundColor(.white) + } + + Text("Remaining time: ^[\(timeRemaining) second](inflect: true)") .font(.callout) .fontWeight(.semibold) .foregroundColor(Color.red.opacity(0.8)) } .padding(20) .frame(maxWidth: .infinity) - .background(Color.primaryBackground) + .background(.primaryBackground) .cornerRadius(15) .shadow(color: .lightShadow, radius: 8, x: -8, y: -8) .shadow(color: .darkShadow, radius: 8, x: 8, y: 8) @@ -59,12 +63,11 @@ struct CongratulationsView: View { }, label: { Image(systemName: "multiply.circle.fill") .resizable() - .frame(width: 35, height: 35) - .foregroundColor(Color.red) - + .frame(width: 32, height: 32) + .foregroundStyle(.black.opacity(0.7), .regularMaterial) }) - .padding(5) - , alignment: .topLeading + .padding() + , alignment: .topTrailing ) .onReceive(timer) { _ in @@ -82,13 +85,18 @@ struct CongratulationsView: View { isAppActive = true } } + + private func goToAppStoreRating() { + isPresented = false + ReviewHandler.requestReviewManually() + } } #if DEBUG struct CongratulationsView_Previews: PreviewProvider { static var previews: some View { - CongratulationsView(isPresented: .constant(true)) - .preferredColorScheme(.dark) + ThanksYouView(isPresented: .constant(true)) + // .preferredColorScheme(.dark) } } #endif