From a96df358e9e987395c19d68bfef6f1d9b2257730 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 2 Mar 2024 15:30:22 -0700 Subject: [PATCH 01/46] Set minimum deployment target to iOS 17; update PhoneNumberKit and SwiftUIKit to latest --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 6 +-- .../xcshareddata/swiftpm/Package.resolved | 42 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 1e837ee..e38de17 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -431,7 +431,7 @@ DEVELOPMENT_TEAM = DJAHJZFYK7; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CatchUp-SwiftUI/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -456,7 +456,7 @@ DEVELOPMENT_TEAM = DJAHJZFYK7; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CatchUp-SwiftUI/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 83fe7ec..da4a267 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,25 +1,23 @@ { - "object": { - "pins": [ - { - "package": "PhoneNumberKit", - "repositoryURL": "https://github.com/marmelroy/PhoneNumberKit.git", - "state": { - "branch": null, - "revision": "5c8c906036dd44d0ac6d721c5fd0bf5602d1ff0e", - "version": "3.2.0" - } - }, - { - "package": "SwiftUIKit", - "repositoryURL": "https://github.com/youjinp/SwiftUIKit.git", - "state": { - "branch": null, - "revision": "16dcc576034f92e3e3429e820e52f414f7f800cd", - "version": "0.0.11" - } + "pins" : [ + { + "identity" : "phonenumberkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/marmelroy/PhoneNumberKit.git", + "state" : { + "revision" : "a8d72d9c90f8336aff6fd6002976d7e36f4fbe8c", + "version" : "3.7.9" } - ] - }, - "version": 1 + }, + { + "identity" : "swiftuikit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/youjinp/SwiftUIKit.git", + "state" : { + "revision" : "a83aedd996bcb59ca846346bb507f090a7350e34", + "version" : "0.0.17" + } + } + ], + "version" : 2 } From 51b38e61e33c6b7b30b4f2efdb51709691bfcd6a Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 3 Mar 2024 10:36:56 -0700 Subject: [PATCH 02/46] Migrate from Core Data to SwiftData --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 76 ++++--------- .../xcschemes/CatchUp-SwiftUI.xcscheme | 71 +++++++++++++ CatchUp-SwiftUI/AppDelegate.swift | 57 +--------- CatchUp-SwiftUI/Assets.xcassets/Contents.json | 6 +- .../Contents.json | 38 +++++++ .../Base.lproj/LaunchScreen.storyboard | 26 ----- CatchUp-SwiftUI/CatchUpApp.swift | 19 ++++ CatchUp-SwiftUI/CatchUp_SwiftUI.entitlements | 10 ++ .../CatchUp_SwiftUI.xcdatamodel/contents | 5 +- CatchUp-SwiftUI/Data/SelectedContact.swift | 83 +++++++++++++++ CatchUp-SwiftUI/DetailScreen.swift | 8 +- CatchUp-SwiftUI/HomeScreen.swift | 100 ++++++++++++++++-- CatchUp-SwiftUI/Info.plist | 61 +++-------- CatchUp-SwiftUI/PreferenceScreen.swift | 13 +-- CatchUp-SwiftUI/SceneDelegate.swift | 71 ------------- .../Services/NotificationService.swift | 90 +++++++--------- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 99 ----------------- .../Utilities/GeneralHelpers.swift | 8 -- SelectedContact+CoreDataClass.swift | 16 --- SelectedContact+CoreDataProperties.swift | 42 -------- 20 files changed, 403 insertions(+), 496 deletions(-) create mode 100644 CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme create mode 100644 CatchUp-SwiftUI/Assets.xcassets/LaunchScreenBackgroundColor.colorset/Contents.json delete mode 100644 CatchUp-SwiftUI/Base.lproj/LaunchScreen.storyboard create mode 100644 CatchUp-SwiftUI/CatchUpApp.swift create mode 100644 CatchUp-SwiftUI/CatchUp_SwiftUI.entitlements create mode 100644 CatchUp-SwiftUI/Data/SelectedContact.swift delete mode 100644 CatchUp-SwiftUI/SceneDelegate.swift delete mode 100644 SelectedContact+CoreDataClass.swift delete mode 100644 SelectedContact+CoreDataProperties.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index e38de17..8c9413f 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -14,25 +14,22 @@ F40293A1256186C2004E0418 /* SwiftUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = F40293A0256186C2004E0418 /* SwiftUIKit */; }; F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40293A625618C4C004E0418 /* ContactHelper.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; - F416505A24440579001DB205 /* CatchUp-SwiftUI.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F416505824440579001DB205 /* CatchUp-SwiftUI.xcdatamodeld */; }; F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationService.swift */; }; F48E37F222C455C3008B0B8B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F122C455C3008B0B8B /* AppDelegate.swift */; }; - F48E37F422C455C3008B0B8B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F322C455C3008B0B8B /* SceneDelegate.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; F48E37FE22C455CB008B0B8B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */; }; - F48E380122C455CB008B0B8B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FF22C455CB008B0B8B /* LaunchScreen.storyboard */; }; F494F95824424A03003CE7B5 /* ContactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F494F95724424A03003CE7B5 /* ContactService.swift */; }; F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */; }; F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8542442A179001D8C55 /* ContactPhoto.swift */; }; F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8562442A1F7001D8C55 /* GradientView.swift */; }; F4A9B8592442AB81001D8C55 /* PreferenceScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */; }; F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B85A2443FFF3001D8C55 /* Conversions.swift */; }; - F4AD59AA244A960800296568 /* SelectedContact+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59A8244A960800296568 /* SelectedContact+CoreDataClass.swift */; }; - F4AD59AB244A960800296568 /* SelectedContact+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59A9244A960800296568 /* SelectedContact+CoreDataProperties.swift */; }; F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AD244C9FF600296568 /* IAPService.swift */; }; F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AF244CA12C00296568 /* AboutScreen.swift */; }; F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */; }; + F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */; }; + F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -41,15 +38,12 @@ 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesScreen.swift; sourceTree = ""; }; F40293A625618C4C004E0418 /* ContactHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactHelper.swift; sourceTree = ""; }; F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; - F416505924440579001DB205 /* CatchUp_SwiftUI.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CatchUp_SwiftUI.xcdatamodel; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F122C455C3008B0B8B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - F48E37F322C455C3008B0B8B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; F48E37FA22C455CB008B0B8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - F48E380022C455CB008B0B8B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; F48E380222C455CB008B0B8B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F494F95724424A03003CE7B5 /* ContactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactService.swift; sourceTree = ""; }; F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreen.swift; sourceTree = ""; }; @@ -57,12 +51,12 @@ F4A9B8562442A1F7001D8C55 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceScreen.swift; sourceTree = ""; }; F4A9B85A2443FFF3001D8C55 /* Conversions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conversions.swift; sourceTree = ""; }; - F4AD59A8244A960800296568 /* SelectedContact+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SelectedContact+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; - F4AD59A9244A960800296568 /* SelectedContact+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SelectedContact+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "CatchUp-SwiftUI.entitlements"; sourceTree = ""; }; F4AD59AD244C9FF600296568 /* IAPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPService.swift; sourceTree = ""; }; F4AD59AF244CA12C00296568 /* AboutScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutScreen.swift; sourceTree = ""; }; F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+isFirstLaunch.swift"; sourceTree = ""; }; + F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedContact.swift; sourceTree = ""; }; + F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatchUpApp.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -86,16 +80,6 @@ path = Resources; sourceTree = ""; }; - F42440B324468480001ABD14 /* CoreData */ = { - isa = PBXGroup; - children = ( - F4AD59A8244A960800296568 /* SelectedContact+CoreDataClass.swift */, - F4AD59A9244A960800296568 /* SelectedContact+CoreDataProperties.swift */, - F416505824440579001DB205 /* CatchUp-SwiftUI.xcdatamodeld */, - ); - path = CoreData; - sourceTree = ""; - }; F4871DA2244FC44800925392 /* Utilities */ = { isa = PBXGroup; children = ( @@ -125,18 +109,17 @@ F48E37F022C455C3008B0B8B /* CatchUp-SwiftUI */ = { isa = PBXGroup; children = ( + F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */, F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */, F48E37FA22C455CB008B0B8B /* Assets.xcassets */, F48E37F122C455C3008B0B8B /* AppDelegate.swift */, - F48E37F322C455C3008B0B8B /* SceneDelegate.swift */, - F48E37FF22C455CB008B0B8B /* LaunchScreen.storyboard */, F48E380222C455CB008B0B8B /* Info.plist */, 144DBE23245C864F008FDBB6 /* Resources */, F4AD59B1244CC9C100296568 /* Extensions */, F4A9B85024429E40001D8C55 /* Services */, F4871DA2244FC44800925392 /* Utilities */, F4A9B8532442A162001D8C55 /* Supporting Views */, - F42440B324468480001ABD14 /* CoreData */, + F4BAD4292B94E41F0009CD50 /* Data */, F48E37FC22C455CB008B0B8B /* Preview Content */, F48E37F822C455C3008B0B8B /* HomeScreen.swift */, F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */, @@ -183,6 +166,14 @@ path = Extensions; sourceTree = ""; }; + F4BAD4292B94E41F0009CD50 /* Data */ = { + isa = PBXGroup; + children = ( + F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */, + ); + path = Data; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -250,7 +241,6 @@ buildActionMask = 2147483647; files = ( 14ACAC86245E52A40091AE90 /* PhoneNumberMetadata.json in Resources */, - F48E380122C455CB008B0B8B /* LaunchScreen.storyboard in Resources */, F48E37FE22C455CB008B0B8B /* Preview Assets.xcassets in Resources */, F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */, ); @@ -271,16 +261,14 @@ F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */, F494F95824424A03003CE7B5 /* ContactService.swift in Sources */, F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */, - F416505A24440579001DB205 /* CatchUp-SwiftUI.xcdatamodeld in Sources */, + F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */, 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */, F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */, F4A9B8592442AB81001D8C55 /* PreferenceScreen.swift in Sources */, - F4AD59AA244A960800296568 /* SelectedContact+CoreDataClass.swift in Sources */, + F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */, - F4AD59AB244A960800296568 /* SelectedContact+CoreDataProperties.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, - F48E37F422C455C3008B0B8B /* SceneDelegate.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */, ); @@ -288,17 +276,6 @@ }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - F48E37FF22C455CB008B0B8B /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - F48E380022C455CB008B0B8B /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ F48E380322C455CB008B0B8B /* Debug */ = { isa = XCBuildConfiguration; @@ -351,7 +328,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -408,7 +385,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -431,6 +408,8 @@ DEVELOPMENT_TEAM = DJAHJZFYK7; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CatchUp-SwiftUI/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = CatchUp; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -456,6 +435,8 @@ DEVELOPMENT_TEAM = DJAHJZFYK7; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CatchUp-SwiftUI/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = CatchUp; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -524,19 +505,6 @@ productName = SwiftUIKit; }; /* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - F416505824440579001DB205 /* CatchUp-SwiftUI.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - F416505924440579001DB205 /* CatchUp_SwiftUI.xcdatamodel */, - ); - currentVersion = F416505924440579001DB205 /* CatchUp_SwiftUI.xcdatamodel */; - path = "CatchUp-SwiftUI.xcdatamodeld"; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = F48E37E622C455C3008B0B8B /* Project object */; } diff --git a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme new file mode 100644 index 0000000..f97370f --- /dev/null +++ b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CatchUp-SwiftUI/AppDelegate.swift b/CatchUp-SwiftUI/AppDelegate.swift index 7b222ae..4a528ef 100644 --- a/CatchUp-SwiftUI/AppDelegate.swift +++ b/CatchUp-SwiftUI/AppDelegate.swift @@ -7,14 +7,9 @@ // import UIKit -import CoreData import UserNotifications -@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if UserDefaults.isFirstVersion2Launch() { @@ -24,11 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - // Saves changes in the application's managed object context before the application terminates. - self.saveContext() - } + func applicationWillTerminate(_ application: UIApplication) {} // MARK: UISceneSession Lifecycle @@ -43,51 +34,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - // MARK: - Core Data stack - - lazy var persistentContainer: NSPersistentCloudKitContainer = { - /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ - let container = NSPersistentCloudKitContainer(name: "CatchUp-SwiftUI") - container.loadPersistentStores(completionHandler: { (storeDescription, error) in - if let error = error as NSError? { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - - /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - return container - }() - - // MARK: - Core Data Saving support - - func saveContext () { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } - } diff --git a/CatchUp-SwiftUI/Assets.xcassets/Contents.json b/CatchUp-SwiftUI/Assets.xcassets/Contents.json index da4a164..73c0059 100644 --- a/CatchUp-SwiftUI/Assets.xcassets/Contents.json +++ b/CatchUp-SwiftUI/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/CatchUp-SwiftUI/Assets.xcassets/LaunchScreenBackgroundColor.colorset/Contents.json b/CatchUp-SwiftUI/Assets.xcassets/LaunchScreenBackgroundColor.colorset/Contents.json new file mode 100644 index 0000000..be9d677 --- /dev/null +++ b/CatchUp-SwiftUI/Assets.xcassets/LaunchScreenBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CatchUp-SwiftUI/Base.lproj/LaunchScreen.storyboard b/CatchUp-SwiftUI/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 3fa9507..0000000 --- a/CatchUp-SwiftUI/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift new file mode 100644 index 0000000..b9a8ddf --- /dev/null +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -0,0 +1,19 @@ +// +// CatchUpApp.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/3/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +@main +struct CatchUpApp: App { + var body: some Scene { + WindowGroup { + HomeScreen() + } + .modelContainer(for: SelectedContact.self) + } +} diff --git a/CatchUp-SwiftUI/CatchUp_SwiftUI.entitlements b/CatchUp-SwiftUI/CatchUp_SwiftUI.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/CatchUp-SwiftUI/CatchUp_SwiftUI.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/CatchUp-SwiftUI/CoreData/CatchUp-SwiftUI.xcdatamodeld/CatchUp_SwiftUI.xcdatamodel/contents b/CatchUp-SwiftUI/CoreData/CatchUp-SwiftUI.xcdatamodeld/CatchUp_SwiftUI.xcdatamodel/contents index e921421..61c3737 100644 --- a/CatchUp-SwiftUI/CoreData/CatchUp-SwiftUI.xcdatamodeld/CatchUp_SwiftUI.xcdatamodel/contents +++ b/CatchUp-SwiftUI/CoreData/CatchUp-SwiftUI.xcdatamodeld/CatchUp_SwiftUI.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -23,7 +23,4 @@ - - - \ No newline at end of file diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift new file mode 100644 index 0000000..44584e8 --- /dev/null +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -0,0 +1,83 @@ +// +// SelectedContact.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/3/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// +// + +import Foundation +import SwiftData + +@Model +class SelectedContact { + var address: String + var anniversary: String + var anniversary_notification_id: UUID + var birthday: String + var birthday_notification_id: UUID + var email: String + var id: UUID + var name: String + var notification_identifier: UUID + var notification_preference: Int16 = 0 + var notification_preference_custom_day: Int16 = 0 + var notification_preference_custom_month: Int16 = 0 + var notification_preference_custom_year: Int16 = 0 + var notification_preference_hour: Int16 = 0 + var notification_preference_minute: Int16 = 0 + var notification_preference_weekday: Int16 = 0 + var phone: String + var picture: String + var secondary_address: String + var secondary_email: String + var secondary_phone: String + + init( + address: String, + anniversary: String, + anniversary_notification_id: UUID, + birthday: String, + birthday_notification_id: UUID, + email: String, + id: UUID, + name: String, + notification_identifier: UUID, + notification_preference: Int16, + notification_preference_custom_day: Int16, + notification_preference_custom_month: Int16, + notification_preference_custom_year: Int16, + notification_preference_hour: Int16, + notification_preference_minute: Int16, + notification_preference_weekday: Int16, + phone: String, + picture: String, + secondary_address: String, + secondary_email: String, + secondary_phone: String + ) { + self.address = address + self.anniversary = anniversary + self.anniversary_notification_id = anniversary_notification_id + self.birthday = birthday + self.birthday_notification_id = birthday_notification_id + self.email = email + self.id = id + self.name = name + self.notification_identifier = notification_identifier + self.notification_preference = notification_preference + self.notification_preference_custom_day = notification_preference_custom_day + self.notification_preference_custom_month = notification_preference_custom_month + self.notification_preference_custom_year = notification_preference_custom_year + self.notification_preference_hour = notification_preference_hour + self.notification_preference_minute = notification_preference_minute + self.notification_preference_weekday = notification_preference_weekday + self.phone = phone + self.picture = picture + self.secondary_address = secondary_address + self.secondary_email = secondary_email + self.secondary_phone = secondary_phone + } + +} diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index 772ab37..b3bb0e5 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -10,8 +10,8 @@ import SwiftUI struct DetailScreen: View { @State private var showingPreferenceScreen = false - @Environment(\.managedObjectContext) var managedObjectContext - + @Environment(\.modelContext) var modelContext + let notificationService = NotificationService() let converter = Conversions() let helper = GeneralHelpers() @@ -129,13 +129,13 @@ struct DetailScreen: View { isPresented: $showingPreferenceScreen, onDismiss: { self.notificationService.removeExistingNotifications(for: self.contact) - self.notificationService.createNewNotification(for: self.contact, moc: self.managedObjectContext) + self.notificationService.createNewNotification(for: self.contact, modelContext: modelContext) }) { // the fact that I have to manually pass in the MOC is dumb // hopefully this is a SwiftUI v1 bug that's fixed at WWDC this year // (https://stackoverflow.com/questions/58328201/saving-core-data-entity-in-popover-in-swiftui-throws-nilerror-without-passing-e) - PreferenceScreen(contact: self.contact).environment(\.managedObjectContext, self.managedObjectContext) + PreferenceScreen(contact: self.contact) } .onAppear(perform: helper.clearNotificationBadge) } diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index b01caed..10fe616 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -6,6 +6,7 @@ // Copyright © 2019 Token Solutions. All rights reserved. // +import SwiftData import SwiftUI import SwiftUIKit import ContactsUI @@ -26,14 +27,13 @@ struct HomeScreen : View { @State private var contacts: [CNContact] = [] @State private var activeSheet: ActiveSheet? @State private var showUpdates: ActiveSheet = .updates - @Environment(\.managedObjectContext) var managedObjectContext - @FetchRequest(entity: SelectedContact.entity(), - sortDescriptors: []) var selectedContacts: FetchedResults + + @Environment(\.modelContext) var modelContext + @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] let notificationService = NotificationService() let helper = GeneralHelpers() let converter = Conversions() - let contactHelper = ContactHelper() init() { //Use this if NavigationBarTitle is with Large Font @@ -101,7 +101,7 @@ struct HomeScreen : View { showPicker: $showContactPicker, onSelectContacts: { c in contacts = c - contactHelper.saveSelectedContact(for: contacts) + saveSelectedContact(for: contacts) } ) case .about: @@ -118,7 +118,7 @@ struct HomeScreen : View { func clearNotificationBadgeAndCheckForUpdate() { fetchAvailableIAPs() helper.clearNotificationBadge() - helper.saveMOC(moc: managedObjectContext) + try? modelContext.save() checkForUpdate() } @@ -127,7 +127,7 @@ struct HomeScreen : View { let contact = selectedContacts[index] notificationService.removeExistingNotifications(for: contact) - managedObjectContext.delete(contact) + modelContext.delete(contact) } } @@ -160,13 +160,91 @@ struct HomeScreen : View { print("fetching IAPs") IAPService.shared.fetchAvailableProducts() } + + // save selected contacts and their properties to Core Data + func saveSelectedContact(for contacts: [CNContact]) { + print("saving...") + + let contactService = ContactService() + for contact in contacts { + let currentMinute = Calendar.current.component(.minute, from: Date()) + let currentHour = Calendar.current.component(.hour, from: Date()) + let currentDay = Calendar.current.component(.day, from: Date()) + let currentMonth = Calendar.current.component(.month, from: Date()) + let currentYear = Calendar.current.component(.year, from: Date()) + + let id = UUID() + let address = contactService.getContactPrimaryAddress(for: contact) + let anniversary = contactService.getContactAnniversary(for: contact) + let anniversary_notification_ID = UUID() + let birthday = contactService.getContactBirthday(for: contact) + let birthday_notification_ID = UUID() + let email = contactService.getContactPrimaryEmail(for: contact) + let name = contactService.getContactName(for: contact) + let notification_identifier = UUID() + let notification_preference = 0 + let notification_preference_hour = currentHour + let notification_preference_minute = currentMinute + let notification_preference_weekday = 0 + let notification_preference_custom_year = currentYear + let notification_preference_custom_month = currentMonth + let notification_preference_custom_day = currentDay + let phone = contactService.getContactPrimaryPhone(for: contact) + let picture = contactService.encodeContactPicture(for: contact) + let secondary_email = contactService.getContactSecondaryEmail(for: contact) + let secondary_address = contactService.getContactSecondaryAddress(for: contact) + let secondary_phone = contactService.getContactSecondaryPhone(for: contact) + + if !contactAlreadyAdded(name: name) { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + + let selectedContact = SelectedContact( + address: address, + anniversary: anniversary, + anniversary_notification_id: anniversary_notification_ID, + birthday: birthday, + birthday_notification_id: birthday_notification_ID, + email: email, + id: id, + name: name, + notification_identifier: notification_identifier, + notification_preference: Int16(notification_preference), + notification_preference_custom_day: Int16(notification_preference_custom_day), + notification_preference_custom_month: Int16(notification_preference_custom_month), + notification_preference_custom_year: Int16(notification_preference_custom_year), + notification_preference_hour: Int16(notification_preference_hour), + notification_preference_minute: Int16(notification_preference_minute), + notification_preference_weekday: Int16(notification_preference_weekday), + phone: phone, + picture: picture, + secondary_address: secondary_address, + secondary_email: secondary_email, + secondary_phone: secondary_phone + ) + + modelContext.insert(selectedContact) + } else { + print("Do nothing. Contact was already added to the database") + } + } + + } + + func contactAlreadyAdded(name: String) -> Bool { + for contact in selectedContacts { + if contact.name == name { + return true + } + } + + return false + } } struct HomeScreen_Previews : PreviewProvider { static var previews: some View { - - let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext - - return HomeScreen().environment(\.managedObjectContext, context) + return HomeScreen() } } diff --git a/CatchUp-SwiftUI/Info.plist b/CatchUp-SwiftUI/Info.plist index 695e4eb..b04e8b8 100644 --- a/CatchUp-SwiftUI/Info.plist +++ b/CatchUp-SwiftUI/Info.plist @@ -2,65 +2,32 @@ - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSApplicationCategoryType - public.app-category.social-networking - LSRequiresIPhoneOS - NSContactsUsageDescription CatchUp needs access to your Contacts in order to remind you to catch up with the people you choose. CatchUp does not store or save anything. - UIApplicationSceneManifest + UILaunchScreen - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UILaunchStoryboardName - LaunchScreen - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - + UIColorName + LaunchScreenBackgroundColor - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationLandscapeLeft + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + CFBundleShortVersionString + $(MARKETING_VERSION) UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index 0ed886a..6584fde 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -13,9 +13,10 @@ struct PreferenceScreen: View { @State private var notificationPreferenceTime: Date @State private var notificationPreferenceWeekday: Int @State private var notificationPreferenceCustomDate: Date + @Environment(\.presentationMode) var presentationMode - @Environment(\.managedObjectContext) var managedObjectContext - + @Environment(\.modelContext) var modelContext + let notificationService = NotificationService() let now = Date() @@ -74,7 +75,7 @@ struct PreferenceScreen: View { // Found it buried in comments on Stack Overflow. But it works. // (https://stackoverflow.com/questions/58676483/is-there-a-way-to-call-a-function-when-a-swiftui-picker-selection-changes) .onReceive([self.notificationPreference].publisher.first()) { (preference) in - self.notificationService.updateNotificationPreference(for: self.contact, selection: preference, moc: self.managedObjectContext) + self.notificationService.updateNotificationPreference(for: self.contact, selection: preference, modelContext: modelContext) } VStack(alignment: .leading, spacing: 20) { @@ -92,7 +93,7 @@ struct PreferenceScreen: View { .onReceive([self.notificationPreferenceTime].publisher.first()) { (datetime) in let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute], from : datetime) - self.notificationService.updateNotificationTime(for: self.contact, hour: components.hour!, minute: components.minute!, moc: self.managedObjectContext) + self.notificationService.updateNotificationTime(for: self.contact, hour: components.hour!, minute: components.minute!, modelContext: modelContext) } Spacer() } @@ -115,7 +116,7 @@ struct PreferenceScreen: View { }.pickerStyle(SegmentedPickerStyle()) .onReceive([self.notificationPreferenceWeekday].publisher.first()) { (weekday) in - self.notificationService.updateNotificationPreferenceWeekday(for: self.contact, weekday: weekday, moc: self.managedObjectContext) + self.notificationService.updateNotificationPreferenceWeekday(for: self.contact, weekday: weekday, modelContext: modelContext) } } } @@ -136,7 +137,7 @@ struct PreferenceScreen: View { let year = Calendar.current.component(.year, from: date) let month = Calendar.current.component(.month, from: date) let day = Calendar.current.component(.day, from: date) - self.notificationService.updateNotificationCustomDate(for: self.contact, month: month, day: day, year: year, moc: self.managedObjectContext) + self.notificationService.updateNotificationCustomDate(for: self.contact, month: month, day: day, year: year, modelContext: modelContext) } Spacer() diff --git a/CatchUp-SwiftUI/SceneDelegate.swift b/CatchUp-SwiftUI/SceneDelegate.swift deleted file mode 100644 index b332817..0000000 --- a/CatchUp-SwiftUI/SceneDelegate.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// SceneDelegate.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 6/26/19. -// Copyright © 2019 Token Solutions. All rights reserved. -// - -import UIKit -import SwiftUI - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - - // Get the managed object context from the shared persistent container. - let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext - - // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. - // Add `@Environment(\.managedObjectContext)` in the views that will need the context. - let homeScreen = HomeScreen().environment(\.managedObjectContext, context) - - // Use a UIHostingController as window root view controller - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: homeScreen) - self.window = window - window.makeKeyAndVisible() - } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - - // Save changes in the application's managed object context when the application transitions to the background. - (UIApplication.shared.delegate as? AppDelegate)?.saveContext() - } - - -} - diff --git a/CatchUp-SwiftUI/Services/NotificationService.swift b/CatchUp-SwiftUI/Services/NotificationService.swift index b843590..c1a1a6e 100644 --- a/CatchUp-SwiftUI/Services/NotificationService.swift +++ b/CatchUp-SwiftUI/Services/NotificationService.swift @@ -6,6 +6,7 @@ // Copyright © 2020 Token Solutions. All rights reserved. // +import SwiftData import SwiftUI import UserNotifications import CoreData @@ -15,18 +16,18 @@ struct NotificationService { let helper = GeneralHelpers() let contactService = ContactService() - func createNewNotification(for contact: SelectedContact, moc: NSManagedObjectContext) { + func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { let addRequest = { if self.preferenceIsNotSetToNever(for: contact) { - self.addGeneralNotification(for: contact, moc: moc) + self.addGeneralNotification(for: contact, modelContext: modelContext) } if self.contactHasBirthday(contact) { - self.addBirthdayNotification(for: contact, moc: moc) + self.addBirthdayNotification(for: contact, modelContext: modelContext) } if self.contactHasAnniversary(contact) { - self.addAnniversaryNotification(for: contact, moc: moc) + self.addAnniversaryNotification(for: contact, modelContext: modelContext) } } @@ -45,7 +46,7 @@ struct NotificationService { return contact.anniversary != "" ? true : false } - func addGeneralNotification(for contact: SelectedContact, moc: NSManagedObjectContext) { + func addGeneralNotification(for contact: SelectedContact, modelContext: ModelContext) { let notificationContent = UNMutableNotificationContent() notificationContent.title = "👋 CatchUp with \(contact.name)" notificationContent.body = self.generateRandomNotificationBody() @@ -55,10 +56,10 @@ struct NotificationService { let identifier = UUID() let dateComponents = self.setNotificationDateComponents(for: contact) - self.scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent, moc: moc) + self.scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent, modelContext: modelContext) } - func addBirthdayNotification(for contact: SelectedContact, moc: NSManagedObjectContext) { + func addBirthdayNotification(for contact: SelectedContact, modelContext: ModelContext) { let birthdayNotificationContent = UNMutableNotificationContent() birthdayNotificationContent.title = "🥳 Today is \(contact.name)'s birthday!" birthdayNotificationContent.body = "Be sure to CatchUp and wish them a great one!" @@ -68,10 +69,10 @@ struct NotificationService { let birthdayIdentifier = UUID() let birthdayDateComponents = self.setBirthdayDateComponents(for: contact) - self.scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent, moc: moc) + self.scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent, modelContext: modelContext) } - func addAnniversaryNotification(for contact: SelectedContact, moc: NSManagedObjectContext) { + func addAnniversaryNotification(for contact: SelectedContact, modelContext: ModelContext) { let anniversaryNotificationContent = UNMutableNotificationContent() anniversaryNotificationContent.title = "😍 Tomorrow is \(contact.name)'s anniversary!" anniversaryNotificationContent.body = "Be sure to CatchUp and wish them the best." @@ -81,7 +82,7 @@ struct NotificationService { let anniversaryIdentifier = UUID() let anniversaryDateComponents = self.setAnniversaryDateComponents(for: contact) - self.scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent, moc: moc) + self.scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent, modelContext: modelContext) } func checkNotificationAuthorizationStatusAndAddRequest(action: @escaping() -> Void) { @@ -171,22 +172,21 @@ struct NotificationService { return anniversaryDateComponents } - func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent, moc: NSManagedObjectContext) { - + func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent, modelContext: ModelContext) { + let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let request = UNNotificationRequest(identifier: identifier.uuidString, content: content, trigger: trigger) - moc.performAndWait { - if content.title == "👋 CatchUp with \(contact.name)" { - contact.notification_identifier = identifier - } else if content.title == "🥳 Today is \(contact.name)'s birthday!" { - contact.birthday_notification_id = identifier - } else { - contact.anniversary_notification_id = identifier - } + if content.title == "👋 CatchUp with \(contact.name)" { + contact.notification_identifier = identifier + } else if content.title == "🥳 Today is \(contact.name)'s birthday!" { + contact.birthday_notification_id = identifier + } else { + contact.anniversary_notification_id = identifier } - self.helper.saveMOC(moc: moc) - + + try? modelContext.save() + notificationCenter.add(request) } @@ -239,46 +239,38 @@ struct NotificationService { } } - func updateNotificationPreference(for contact: SelectedContact, selection: Int, moc: NSManagedObjectContext) { + func updateNotificationPreference(for contact: SelectedContact, selection: Int, modelContext: ModelContext) { let newPreference = selection - moc.performAndWait { - contact.notification_preference = Int16(newPreference) - } - - helper.saveMOC(moc: moc) + contact.notification_preference = Int16(newPreference) + + try? modelContext.save() } - func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int, moc: NSManagedObjectContext) { + func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int, modelContext: ModelContext) { let newHour = hour let newMinute = minute - moc.performAndWait { - contact.notification_preference_hour = Int16(newHour) - contact.notification_preference_minute = Int16(newMinute) - } - - helper.saveMOC(moc: moc) + contact.notification_preference_hour = Int16(newHour) + contact.notification_preference_minute = Int16(newMinute) + + try? modelContext.save() } - func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int, moc: NSManagedObjectContext) { + func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int, modelContext: ModelContext) { let newWeekday = weekday - moc.performAndWait { - contact.notification_preference_weekday = Int16(newWeekday) - } - - helper.saveMOC(moc: moc) + contact.notification_preference_weekday = Int16(newWeekday) + + try? modelContext.save() } - func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int, moc: NSManagedObjectContext) { + func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int, modelContext: ModelContext) { let customMonth = month let customDay = day let customYear = year - moc.performAndWait { - contact.notification_preference_custom_month = Int16(customMonth) - contact.notification_preference_custom_day = Int16(customDay) - contact.notification_preference_custom_year = Int16(customYear) - } - - helper.saveMOC(moc: moc) + contact.notification_preference_custom_month = Int16(customMonth) + contact.notification_preference_custom_day = Int16(customDay) + contact.notification_preference_custom_year = Int16(customYear) + + try? modelContext.save() } func requestAuthorizationForNotifications() { diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 7bd0d29..9db89b6 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -11,104 +11,5 @@ import ContactsUI import CoreData struct ContactHelper { - // save selected contacts and their properties to Core Data - func saveSelectedContact(for contacts: [CNContact]) { - print("saving...") - - let contactService = ContactService() - for contact in contacts { - let currentMinute = Calendar.current.component(.minute, from: Date()) - let currentHour = Calendar.current.component(.hour, from: Date()) - let currentDay = Calendar.current.component(.day, from: Date()) - let currentMonth = Calendar.current.component(.month, from: Date()) - let currentYear = Calendar.current.component(.year, from: Date()) - - let id = UUID() - let address = contactService.getContactPrimaryAddress(for: contact) - let anniversary = contactService.getContactAnniversary(for: contact) - let anniversary_notification_ID = UUID() - let birthday = contactService.getContactBirthday(for: contact) - let birthday_notification_ID = UUID() - let email = contactService.getContactPrimaryEmail(for: contact) - let name = contactService.getContactName(for: contact) - let notification_identifier = UUID() - let notification_preference = 0 - let notification_preference_hour = currentHour - let notification_preference_minute = currentMinute - let notification_preference_weekday = 0 - let notification_preference_custom_year = currentYear - let notification_preference_custom_month = currentMonth - let notification_preference_custom_day = currentDay - let phone = contactService.getContactPrimaryPhone(for: contact) - let picture = contactService.encodeContactPicture(for: contact) - let secondary_email = contactService.getContactSecondaryEmail(for: contact) - let secondary_address = contactService.getContactSecondaryAddress(for: contact) - let secondary_phone = contactService.getContactSecondaryPhone(for: contact) - - if !contactAlreadyAdded(name: name) { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return - } - - let managedObjectContext = appDelegate.persistentContainer.viewContext - let entity = NSEntityDescription.entity(forEntityName: "SelectedContact", in: managedObjectContext)! - - let selectedContact = NSManagedObject(entity: entity, insertInto: managedObjectContext) - - selectedContact.setValue(id, forKeyPath: "id") - selectedContact.setValue(address, forKeyPath: "address") - selectedContact.setValue(anniversary, forKeyPath: "anniversary") - selectedContact.setValue(anniversary_notification_ID, forKeyPath: "anniversary_notification_id") - selectedContact.setValue(birthday, forKeyPath: "birthday") - selectedContact.setValue(birthday_notification_ID, forKeyPath: "birthday_notification_id") - selectedContact.setValue(email, forKeyPath: "email") - selectedContact.setValue(name, forKeyPath: "name") - selectedContact.setValue(notification_identifier, forKeyPath: "notification_identifier") - selectedContact.setValue(notification_preference, forKeyPath: "notification_preference") - selectedContact.setValue(notification_preference_hour, forKeyPath: "notification_preference_hour") - selectedContact.setValue(notification_preference_minute, forKeyPath: "notification_preference_minute") - selectedContact.setValue(notification_preference_weekday, forKeyPath: "notification_preference_weekday") - selectedContact.setValue(notification_preference_custom_year, forKeyPath: "notification_preference_custom_year") - selectedContact.setValue(notification_preference_custom_month, forKeyPath: "notification_preference_custom_month") - selectedContact.setValue(notification_preference_custom_day, forKeyPath: "notification_preference_custom_day") - selectedContact.setValue(phone, forKeyPath: "phone") - selectedContact.setValue(picture, forKeyPath: "picture") - selectedContact.setValue(secondary_address, forKeyPath: "secondary_address") - selectedContact.setValue(secondary_email, forKeyPath: "secondary_email") - selectedContact.setValue(secondary_phone, forKeyPath: "secondary_phone") - - do { - try managedObjectContext.save() - } catch let error as NSError { - print("Could not save. \(error), \(error.userInfo)") - } - } else { - print("Do nothing. Contact was already added to the database") - } - } - - } - func contactAlreadyAdded(name: String) -> Bool { - print("Checking \(name)") - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return true - } - let managedObjectContext = appDelegate.persistentContainer.viewContext - - let fetchRequest = NSFetchRequest(entityName: "SelectedContact") - fetchRequest.predicate = NSPredicate(format: "name = %@", name) - - var contactsWithThatName = 0 - - do { - contactsWithThatName = try managedObjectContext.count(for: fetchRequest) - } - catch { - print("error executing fetch request: \(error)") - } - - print("contacts with that name: \(contactsWithThatName)") - return contactsWithThatName > 0 - } } diff --git a/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift b/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift index 4a0b46c..db37e51 100644 --- a/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift +++ b/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift @@ -16,14 +16,6 @@ struct GeneralHelpers { UIApplication.shared.applicationIconBadgeNumber = 0 } - func saveMOC(moc: NSManagedObjectContext) { - do { - try moc.save() - } catch let error as NSError { - print("Could not update the MOC. \(error), \(error.userInfo)") - } - } - func getCurrentAppVersion() -> String { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] let version = (appVersion as! String) diff --git a/SelectedContact+CoreDataClass.swift b/SelectedContact+CoreDataClass.swift deleted file mode 100644 index 8e33d84..0000000 --- a/SelectedContact+CoreDataClass.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// SelectedContact+CoreDataClass.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 4/17/20. -// Copyright © 2020 Token Solutions. All rights reserved. -// -// - -import Foundation -import CoreData - -@objc(SelectedContact) -public class SelectedContact: NSManagedObject, Identifiable { - -} diff --git a/SelectedContact+CoreDataProperties.swift b/SelectedContact+CoreDataProperties.swift deleted file mode 100644 index 4c6ba3b..0000000 --- a/SelectedContact+CoreDataProperties.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SelectedContact+CoreDataProperties.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 4/17/20. -// Copyright © 2020 Token Solutions. All rights reserved. -// -// - -import Foundation -import CoreData - - -extension SelectedContact { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "SelectedContact") - } - - @NSManaged public var address: String - @NSManaged public var anniversary: String - @NSManaged public var anniversary_notification_id: UUID - @NSManaged public var birthday: String - @NSManaged public var birthday_notification_id: UUID - @NSManaged public var email: String - @NSManaged public var id: UUID - @NSManaged public var name: String - @NSManaged public var notification_identifier: UUID - @NSManaged public var notification_preference: Int16 - @NSManaged public var notification_preference_hour: Int16 - @NSManaged public var notification_preference_minute: Int16 - @NSManaged public var notification_preference_weekday: Int16 - @NSManaged public var phone: String - @NSManaged public var picture: String - @NSManaged public var secondary_address: String - @NSManaged public var secondary_email: String - @NSManaged public var secondary_phone: String - @NSManaged public var notification_preference_custom_month: Int16 - @NSManaged public var notification_preference_custom_day: Int16 - @NSManaged public var notification_preference_custom_year: Int16 - -} From ea07f16df9cb5c27518475d5e2a4d08ab8184e46 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 3 Mar 2024 11:07:47 -0700 Subject: [PATCH 03/46] Fix some warnings --- CatchUp-SwiftUI/CatchUpApp.swift | 1 + CatchUp-SwiftUI/HomeScreen.swift | 10 ++++---- CatchUp-SwiftUI/Info.plist | 25 ++++++++----------- CatchUp-SwiftUI/PreferenceScreen.swift | 4 +-- .../Utilities/GeneralHelpers.swift | 4 +-- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index b9a8ddf..46960ad 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -6,6 +6,7 @@ // Copyright © 2024 Token Solutions. All rights reserved. // +import SwiftData import SwiftUI @main diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index 10fe616..2421952 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -112,7 +112,11 @@ struct HomeScreen : View { } } .accentColor(.orange) - .onAppear(perform: clearNotificationBadgeAndCheckForUpdate) + .onAppear { + clearNotificationBadgeAndCheckForUpdate() + + print("contacts: \(selectedContacts)") + } } func clearNotificationBadgeAndCheckForUpdate() { @@ -196,10 +200,6 @@ struct HomeScreen : View { let secondary_phone = contactService.getContactSecondaryPhone(for: contact) if !contactAlreadyAdded(name: name) { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return - } - let selectedContact = SelectedContact( address: address, anniversary: anniversary, diff --git a/CatchUp-SwiftUI/Info.plist b/CatchUp-SwiftUI/Info.plist index b04e8b8..12637e8 100644 --- a/CatchUp-SwiftUI/Info.plist +++ b/CatchUp-SwiftUI/Info.plist @@ -2,6 +2,16 @@ + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) NSContactsUsageDescription CatchUp needs access to your Contacts in order to remind you to catch up with the people you choose. CatchUp does not store or save anything. UILaunchScreen @@ -10,24 +20,11 @@ LaunchScreenBackgroundColor UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleName - $(PRODUCT_NAME) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - CFBundleShortVersionString - $(MARKETING_VERSION) - UISupportedInterfaceOrientations~ipad UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index 6584fde..bb231b8 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -64,7 +64,7 @@ struct PreferenceScreen: View { Text("How often should we notify you to CatchUp with \(contact.name)?") Picker(selection: $notificationPreference, label: Text("How often should we notify you to CatchUp with \(contact.name)?")) { - ForEach(0.. String { From 8f3127039bb35ab89c073a434144d3e3f97c6d2f Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 3 Mar 2024 12:28:23 -0700 Subject: [PATCH 04/46] Proper Core Data to SwiftData migration; code cleanup and fixes --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 + CatchUp-SwiftUI/CatchUpApp.swift | 16 ++- CatchUp-SwiftUI/Data/SelectedContact.swift | 28 ++--- CatchUp-SwiftUI/DetailScreen.swift | 50 +++++--- .../ModelContext+sqliteCommand.swift | 19 +++ CatchUp-SwiftUI/HomeScreen.swift | 16 +-- CatchUp-SwiftUI/PreferenceScreen.swift | 118 ++++++++---------- .../Services/NotificationService.swift | 14 +-- CatchUp-SwiftUI/Utilities/Conversions.swift | 10 +- 9 files changed, 164 insertions(+), 111 deletions(-) create mode 100644 CatchUp-SwiftUI/Extensions/ModelContext+sqliteCommand.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 8c9413f..0e59add 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */; }; F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */; }; F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */; }; + F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -57,6 +58,7 @@ F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+isFirstLaunch.swift"; sourceTree = ""; }; F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedContact.swift; sourceTree = ""; }; F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatchUpApp.swift; sourceTree = ""; }; + F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelContext+sqliteCommand.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -162,6 +164,7 @@ children = ( F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */, F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */, + F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */, ); path = Extensions; sourceTree = ""; @@ -269,6 +272,7 @@ F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, + F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */, ); diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index 46960ad..e6451ad 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -11,10 +11,24 @@ import SwiftUI @main struct CatchUpApp: App { + // use the SQLite file created by Core Data originally, instead of SwiftData's default.store file + let url = URL.applicationSupportDirectory.appending(path: "CatchUp-SwiftUI.sqlite") + let modelContainer: ModelContainer + + init() { + do { + modelContainer = try ModelContainer( + for: SelectedContact.self, + configurations: ModelConfiguration(url: url)) + } catch { + fatalError("Failed to initialize model container.") + } + } + var body: some Scene { WindowGroup { HomeScreen() } - .modelContainer(for: SelectedContact.self) + .modelContainer(modelContainer) } } diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift index 44584e8..5542b19 100644 --- a/CatchUp-SwiftUI/Data/SelectedContact.swift +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -21,13 +21,13 @@ class SelectedContact { var id: UUID var name: String var notification_identifier: UUID - var notification_preference: Int16 = 0 - var notification_preference_custom_day: Int16 = 0 - var notification_preference_custom_month: Int16 = 0 - var notification_preference_custom_year: Int16 = 0 - var notification_preference_hour: Int16 = 0 - var notification_preference_minute: Int16 = 0 - var notification_preference_weekday: Int16 = 0 + var notification_preference: Int = 0 + var notification_preference_custom_day: Int = 0 + var notification_preference_custom_month: Int = 0 + var notification_preference_custom_year: Int = 0 + var notification_preference_hour: Int = 0 + var notification_preference_minute: Int = 0 + var notification_preference_weekday: Int = 0 var phone: String var picture: String var secondary_address: String @@ -44,13 +44,13 @@ class SelectedContact { id: UUID, name: String, notification_identifier: UUID, - notification_preference: Int16, - notification_preference_custom_day: Int16, - notification_preference_custom_month: Int16, - notification_preference_custom_year: Int16, - notification_preference_hour: Int16, - notification_preference_minute: Int16, - notification_preference_weekday: Int16, + notification_preference: Int, + notification_preference_custom_day: Int, + notification_preference_custom_month: Int, + notification_preference_custom_year: Int, + notification_preference_hour: Int, + notification_preference_minute: Int, + notification_preference_weekday: Int, phone: String, picture: String, secondary_address: String, diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index b3bb0e5..1a7f11e 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -17,8 +17,32 @@ struct DetailScreen: View { let helper = GeneralHelpers() let contactService = ContactService() - let contact: SelectedContact - + @Bindable var contact: SelectedContact + + var formattedPrimaryPhoneNumber: String { + converter.getFormattedPhoneNumber(from: contact.phone) + } + + var formattedSecondaryPhoneNumber: String { + converter.getFormattedPhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryPhoneNumber: URL { + converter.getTappablePhoneNumber(from: contact.phone) + } + + var tappableSecondaryPhoneNumber: URL { + converter.getTappablePhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryEmail: URL { + converter.getTappablePhoneNumber(from: contact.email) + } + + var tappableSecondaryEmail: URL { + converter.getTappablePhoneNumber(from: contact.secondary_email) + } + var body: some View { VStack { GradientView() @@ -55,8 +79,8 @@ struct DetailScreen: View { Text("Phone") .font(.caption) - Button(converter.getFormattedPhoneNumber(from: contact.phone)) { - UIApplication.shared.open(self.converter.getTappablePhoneNumber(from: self.contact.phone)) + Button(formattedPrimaryPhoneNumber) { + UIApplication.shared.open(tappablePrimaryPhoneNumber) } .foregroundColor(.blue) } @@ -66,8 +90,8 @@ struct DetailScreen: View { Text("Secondary Phone") .font(.caption) - Button(converter.getFormattedPhoneNumber(from: contact.secondary_phone)) { - UIApplication.shared.open(self.converter.getTappablePhoneNumber(from: self.contact.secondary_phone)) + Button(formattedSecondaryPhoneNumber) { + UIApplication.shared.open(tappableSecondaryPhoneNumber) } .foregroundColor(.blue) } @@ -78,7 +102,7 @@ struct DetailScreen: View { .font(.caption) Button(contact.email) { - UIApplication.shared.open(self.converter.getTappableEmail(from: self.contact.email)) + UIApplication.shared.open(tappablePrimaryEmail) } .foregroundColor(.blue) } @@ -89,7 +113,7 @@ struct DetailScreen: View { .font(.caption) Button(contact.secondary_email) { - UIApplication.shared.open(self.converter.getTappableEmail(from: self.contact.secondary_email)) + UIApplication.shared.open(tappableSecondaryEmail) } .foregroundColor(.blue) } @@ -128,14 +152,10 @@ struct DetailScreen: View { .sheet( isPresented: $showingPreferenceScreen, onDismiss: { - self.notificationService.removeExistingNotifications(for: self.contact) - self.notificationService.createNewNotification(for: self.contact, modelContext: modelContext) + self.notificationService.removeExistingNotifications(for: contact) + self.notificationService.createNewNotification(for: contact, modelContext: modelContext) }) { - - // the fact that I have to manually pass in the MOC is dumb - // hopefully this is a SwiftUI v1 bug that's fixed at WWDC this year - // (https://stackoverflow.com/questions/58328201/saving-core-data-entity-in-popover-in-swiftui-throws-nilerror-without-passing-e) - PreferenceScreen(contact: self.contact) + PreferenceScreen(contact: contact) } .onAppear(perform: helper.clearNotificationBadge) } diff --git a/CatchUp-SwiftUI/Extensions/ModelContext+sqliteCommand.swift b/CatchUp-SwiftUI/Extensions/ModelContext+sqliteCommand.swift new file mode 100644 index 0000000..4e0f44c --- /dev/null +++ b/CatchUp-SwiftUI/Extensions/ModelContext+sqliteCommand.swift @@ -0,0 +1,19 @@ +// +// ModelContext+sqliteCommand.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/3/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftData + +extension ModelContext { + var sqliteCommand: String { + if let url = container.configurations.first?.url.path(percentEncoded: false) { + "sqlite3 \"\(url)\"" + } else { + "No SQLite database found." + } + } +} diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index 2421952..dd6171a 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -115,6 +115,8 @@ struct HomeScreen : View { .onAppear { clearNotificationBadgeAndCheckForUpdate() + print(modelContext.sqliteCommand) + print("contacts: \(selectedContacts)") } } @@ -210,13 +212,13 @@ struct HomeScreen : View { id: id, name: name, notification_identifier: notification_identifier, - notification_preference: Int16(notification_preference), - notification_preference_custom_day: Int16(notification_preference_custom_day), - notification_preference_custom_month: Int16(notification_preference_custom_month), - notification_preference_custom_year: Int16(notification_preference_custom_year), - notification_preference_hour: Int16(notification_preference_hour), - notification_preference_minute: Int16(notification_preference_minute), - notification_preference_weekday: Int16(notification_preference_weekday), + notification_preference: notification_preference, + notification_preference_custom_day: notification_preference_custom_day, + notification_preference_custom_month: notification_preference_custom_month, + notification_preference_custom_year: notification_preference_custom_year, + notification_preference_hour: notification_preference_hour, + notification_preference_minute: notification_preference_minute, + notification_preference_weekday: notification_preference_weekday, phone: phone, picture: picture, secondary_address: secondary_address, diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index bb231b8..f6ce8c1 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -9,45 +9,26 @@ import SwiftUI struct PreferenceScreen: View { - @State private var notificationPreference: Int - @State private var notificationPreferenceTime: Date - @State private var notificationPreferenceWeekday: Int - @State private var notificationPreferenceCustomDate: Date - - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @Environment(\.modelContext) var modelContext + @State private var notificationPreferenceTime = Date() + @State private var notificationPreferenceCustomDate = Date() + + @Bindable var contact: SelectedContact + let notificationService = NotificationService() let now = Date() - - var contact: SelectedContact + var notificationOptions = ["Never", "Daily", "Weekly", "Monthly", "Custom"] var dayOptions = ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"] - - // set default values equal to their Core Data values for new contacts who haven't been changed yet - // many of these defaults are set in ContactPickerViewController.swift - init(contact: SelectedContact) { - - let calendar = Calendar.current - let timeComponents = DateComponents(calendar: calendar, hour: Int(contact.notification_preference_hour), minute: Int(contact.notification_preference_minute)) - let time = Calendar.current.date(from: timeComponents) - - let customDateComponents = DateComponents(calendar: calendar, year: Int(contact.notification_preference_custom_year), month: Int(contact.notification_preference_custom_month), day: Int(contact.notification_preference_custom_day)) - let customDate = Calendar.current.date(from: customDateComponents) - - self.contact = contact - self._notificationPreference = State(initialValue: Int(contact.notification_preference)) - self._notificationPreferenceTime = State(initialValue: time!) - self._notificationPreferenceWeekday = State(initialValue: Int(contact.notification_preference_weekday)) - self._notificationPreferenceCustomDate = State(initialValue: customDate!) - } var body: some View { VStack(alignment: .leading, spacing: 10) { HStack { Spacer() Button("Save") { - self.presentationMode.wrappedValue.dismiss() + dismiss() } .foregroundColor(.blue) .font(.headline) @@ -63,24 +44,16 @@ struct PreferenceScreen: View { Text("How often should we notify you to CatchUp with \(contact.name)?") - Picker(selection: $notificationPreference, label: Text("How often should we notify you to CatchUp with \(contact.name)?")) { + Picker(selection: $contact.notification_preference, label: Text("How often should we notify you to CatchUp with \(contact.name)?")) { ForEach(0.. String { let phoneNumberKit = PhoneNumberKit() + + print("formatting phone number: \(phoneNumber)") + do { - let parsedPhoneNumber = try phoneNumberKit.parse(phoneNumber) + let parsedPhoneNumber = try phoneNumberKit.parse(phoneNumber.trimmingCharacters(in: .whitespacesAndNewlines)) let formattedPhoneNumber = phoneNumberKit.format(parsedPhoneNumber, toType: .national) return formattedPhoneNumber } catch { - print("PhoneNumberKit Parse Error") + print("PhoneNumberKit Parse Error: \(error)") return phoneNumber } } func getTappablePhoneNumber(from phoneNumber: String) -> URL { + print("getting tappable phone number: \(phoneNumber)") + let tel = "tel://" let cleanNumber = phoneNumber.replacingOccurrences(of: "[^\\d+]", with: "", options: [.regularExpression]) let formattedString = tel + cleanNumber From 97a080eb25a95b71c913a9a48bf8917c11a4124f Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 3 Mar 2024 15:43:09 -0700 Subject: [PATCH 05/46] SwiftData cloud syncing --- .../xcschemes/CatchUp-SwiftUI.xcscheme | 10 +++++++ CatchUp-SwiftUI/CatchUp-SwiftUI.entitlements | 10 +++++++ CatchUp-SwiftUI/CatchUpApp.swift | 2 ++ CatchUp-SwiftUI/Data/SelectedContact.swift | 28 +++++++++---------- CatchUp-SwiftUI/DetailScreen.swift | 4 +-- CatchUp-SwiftUI/HomeScreen.swift | 24 ++++++++++++++-- CatchUp-SwiftUI/Info.plist | 4 +++ CatchUp-SwiftUI/PreferenceScreen.swift | 1 - 8 files changed, 63 insertions(+), 20 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme index f97370f..cb6ade8 100644 --- a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme +++ b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme @@ -44,6 +44,16 @@ ReferencedContainer = "container:CatchUp-SwiftUI.xcodeproj"> + + + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.com.ryantoken.CatchUp + + com.apple.developer.icloud-services + + CloudKit + com.apple.security.app-sandbox com.apple.security.network.client diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index e6451ad..67fd740 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -11,6 +11,8 @@ import SwiftUI @main struct CatchUpApp: App { + @Environment(\.scenePhase) var scenePhase + // use the SQLite file created by Core Data originally, instead of SwiftData's default.store file let url = URL.applicationSupportDirectory.appending(path: "CatchUp-SwiftUI.sqlite") let modelContainer: ModelContainer diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift index 5542b19..9cfb5d9 100644 --- a/CatchUp-SwiftUI/Data/SelectedContact.swift +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -12,15 +12,15 @@ import SwiftData @Model class SelectedContact { - var address: String - var anniversary: String - var anniversary_notification_id: UUID - var birthday: String - var birthday_notification_id: UUID - var email: String - var id: UUID - var name: String - var notification_identifier: UUID + var address: String = "" + var anniversary: String = "" + var anniversary_notification_id: UUID = UUID() + var birthday: String = "" + var birthday_notification_id: UUID = UUID() + var email: String = "" + var id: UUID = UUID() + var name: String = "" + var notification_identifier: UUID = UUID() var notification_preference: Int = 0 var notification_preference_custom_day: Int = 0 var notification_preference_custom_month: Int = 0 @@ -28,11 +28,11 @@ class SelectedContact { var notification_preference_hour: Int = 0 var notification_preference_minute: Int = 0 var notification_preference_weekday: Int = 0 - var phone: String - var picture: String - var secondary_address: String - var secondary_email: String - var secondary_phone: String + var phone: String = "" + var picture: String = "" + var secondary_address: String = "" + var secondary_email: String = "" + var secondary_phone: String = "" init( address: String, diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index 1a7f11e..51b418e 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -152,8 +152,8 @@ struct DetailScreen: View { .sheet( isPresented: $showingPreferenceScreen, onDismiss: { - self.notificationService.removeExistingNotifications(for: contact) - self.notificationService.createNewNotification(for: contact, modelContext: modelContext) + notificationService.removeExistingNotifications(for: contact) + notificationService.createNewNotification(for: contact, modelContext: modelContext) }) { PreferenceScreen(contact: contact) } diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index dd6171a..bf85db6 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -28,13 +28,14 @@ struct HomeScreen : View { @State private var activeSheet: ActiveSheet? @State private var showUpdates: ActiveSheet = .updates + @Environment(\.scenePhase) var scenePhase @Environment(\.modelContext) var modelContext @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] let notificationService = NotificationService() let helper = GeneralHelpers() let converter = Conversions() - + init() { //Use this if NavigationBarTitle is with Large Font UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange] @@ -113,11 +114,19 @@ struct HomeScreen : View { } .accentColor(.orange) .onAppear { + print("HomeScreen onAppear") clearNotificationBadgeAndCheckForUpdate() + notificationService.requestAuthorizationForNotifications() + resetNotifications() print(modelContext.sqliteCommand) + } - print("contacts: \(selectedContacts)") + .onChange(of: scenePhase) { oldPhase, newPhase in + if newPhase == .active { + print("HomeScreen scenePhase Active") + resetNotifications() + } } } @@ -152,7 +161,16 @@ struct HomeScreen : View { UserDefaults.standard.set(version, forKey: "savedVersion") } } - + + func resetNotifications() { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + for contact in selectedContacts { + notificationService.removeExistingNotifications(for: contact) + notificationService.createNewNotification(for: contact, modelContext: modelContext) + } + } + } + func updateIsMajor() -> Bool { let version = helper.getCurrentAppVersion() if version.suffix(2) == ".0" { diff --git a/CatchUp-SwiftUI/Info.plist b/CatchUp-SwiftUI/Info.plist index 12637e8..7aff5c3 100644 --- a/CatchUp-SwiftUI/Info.plist +++ b/CatchUp-SwiftUI/Info.plist @@ -14,6 +14,10 @@ $(CURRENT_PROJECT_VERSION) NSContactsUsageDescription CatchUp needs access to your Contacts in order to remind you to catch up with the people you choose. CatchUp does not store or save anything. + UIBackgroundModes + + remote-notification + UILaunchScreen UIColorName diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index f6ce8c1..b8057ef 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -109,7 +109,6 @@ struct PreferenceScreen: View { .padding(15) .onAppear { - notificationService.requestAuthorizationForNotifications() setInitialNotificateDateTime() print("contact notification preference: \(contact.notification_preference)") From 32cd69f0720adfbedc8a02949fe1488f312a2e49 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Fri, 8 Mar 2024 22:04:44 -0700 Subject: [PATCH 06/46] Remove self. wherever possible --- CatchUp-SwiftUI/AboutScreen.swift | 8 +++--- CatchUp-SwiftUI/DetailScreen.swift | 4 +-- CatchUp-SwiftUI/HomeScreen.swift | 15 ++-------- CatchUp-SwiftUI/PreferenceScreen.swift | 2 +- CatchUp-SwiftUI/Services/IAPService.swift | 2 +- .../Services/NotificationService.swift | 28 +++++++++---------- 6 files changed, 25 insertions(+), 34 deletions(-) diff --git a/CatchUp-SwiftUI/AboutScreen.swift b/CatchUp-SwiftUI/AboutScreen.swift index e06756c..e60e094 100644 --- a/CatchUp-SwiftUI/AboutScreen.swift +++ b/CatchUp-SwiftUI/AboutScreen.swift @@ -58,7 +58,7 @@ struct AboutScreen: View { Spacer() Button(smallTip) { - self.graciousTipPressed() + graciousTipPressed() } .font(.headline) .foregroundColor(.white) @@ -70,7 +70,7 @@ struct AboutScreen: View { Spacer() Button(mediumTip) { - self.generousTipPressed() + generousTipPressed() } .font(.headline) .foregroundColor(.white) @@ -82,7 +82,7 @@ struct AboutScreen: View { Spacer() Button(largeTip) { - self.gratuitousTipPressed() + gratuitousTipPressed() } .font(.headline) .foregroundColor(.white) @@ -106,7 +106,7 @@ struct AboutScreen: View { Group { Button(action: { - self.showingUpdateScreen = true + showingUpdateScreen = true }) { Text("Show Latest Update Details") .font(.headline) diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index 51b418e..8d99aa5 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -49,7 +49,7 @@ struct DetailScreen: View { .edgesIgnoringSafeArea(.top) .frame(height: 75) - ContactPhoto(image: self.converter.getContactPicture(from: contact.picture)) + ContactPhoto(image: converter.getContactPicture(from: contact.picture)) .offset(x: 0, y: -130) .padding(.bottom, -130) @@ -64,7 +64,7 @@ struct DetailScreen: View { .foregroundColor(.gray) } Button(action: { - self.showingPreferenceScreen = true + showingPreferenceScreen = true }) { Text("Change Notification Preference") .font(.headline) diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index bf85db6..4648e52 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -121,13 +121,6 @@ struct HomeScreen : View { print(modelContext.sqliteCommand) } - - .onChange(of: scenePhase) { oldPhase, newPhase in - if newPhase == .active { - print("HomeScreen scenePhase Active") - resetNotifications() - } - } } func clearNotificationBadgeAndCheckForUpdate() { @@ -163,11 +156,9 @@ struct HomeScreen : View { } func resetNotifications() { - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - for contact in selectedContacts { - notificationService.removeExistingNotifications(for: contact) - notificationService.createNewNotification(for: contact, modelContext: modelContext) - } + for contact in selectedContacts { + notificationService.removeExistingNotifications(for: contact) + notificationService.createNewNotification(for: contact, modelContext: modelContext) } } diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index b8057ef..835d650 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -80,7 +80,7 @@ struct PreferenceScreen: View { // Show Day of the Week Picker Picker(selection: $contact.notification_preference_weekday, label: Text("What day?")) { ForEach(0.. Void) { @@ -91,7 +91,7 @@ struct NotificationService { action() print("Scheduled new notification(s)") } else { - self.notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { action() } else { From 8a0107d64a464362104320d1ed8b01921b6f4c14 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Fri, 8 Mar 2024 22:27:04 -0700 Subject: [PATCH 07/46] Use the new #Preview macro everywhere; a bit of cleanup --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 8 -- CatchUp-SwiftUI/AboutScreen.swift | 6 +- CatchUp-SwiftUI/AppDelegate.swift | 38 -------- CatchUp-SwiftUI/DetailScreen.swift | 6 +- .../UserDefaults+isFirstLaunch.swift | 24 ----- CatchUp-SwiftUI/HomeScreen.swift | 87 ++++++++----------- .../Supporting Views/ContactPhoto.swift | 6 +- .../Supporting Views/GradientView.swift | 16 ++-- CatchUp-SwiftUI/UpdatesScreen.swift | 8 +- 9 files changed, 51 insertions(+), 148 deletions(-) delete mode 100644 CatchUp-SwiftUI/AppDelegate.swift delete mode 100644 CatchUp-SwiftUI/Extensions/UserDefaults+isFirstLaunch.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 0e59add..53c071d 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40293A625618C4C004E0418 /* ContactHelper.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationService.swift */; }; - F48E37F222C455C3008B0B8B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F122C455C3008B0B8B /* AppDelegate.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; F48E37FE22C455CB008B0B8B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */; }; @@ -27,7 +26,6 @@ F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B85A2443FFF3001D8C55 /* Conversions.swift */; }; F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AD244C9FF600296568 /* IAPService.swift */; }; F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AF244CA12C00296568 /* AboutScreen.swift */; }; - F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */; }; F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */; }; F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */; }; F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */; }; @@ -41,7 +39,6 @@ F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - F48E37F122C455C3008B0B8B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; F48E37FA22C455CB008B0B8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -55,7 +52,6 @@ F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "CatchUp-SwiftUI.entitlements"; sourceTree = ""; }; F4AD59AD244C9FF600296568 /* IAPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPService.swift; sourceTree = ""; }; F4AD59AF244CA12C00296568 /* AboutScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutScreen.swift; sourceTree = ""; }; - F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+isFirstLaunch.swift"; sourceTree = ""; }; F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedContact.swift; sourceTree = ""; }; F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatchUpApp.swift; sourceTree = ""; }; F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelContext+sqliteCommand.swift"; sourceTree = ""; }; @@ -114,7 +110,6 @@ F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */, F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */, F48E37FA22C455CB008B0B8B /* Assets.xcassets */, - F48E37F122C455C3008B0B8B /* AppDelegate.swift */, F48E380222C455CB008B0B8B /* Info.plist */, 144DBE23245C864F008FDBB6 /* Resources */, F4AD59B1244CC9C100296568 /* Extensions */, @@ -162,7 +157,6 @@ F4AD59B1244CC9C100296568 /* Extensions */ = { isa = PBXGroup; children = ( - F4AD59B2244CC9D600296568 /* UserDefaults+isFirstLaunch.swift */, F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */, F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */, ); @@ -256,7 +250,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F48E37F222C455C3008B0B8B /* AppDelegate.swift in Sources */, F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */, F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */, F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, @@ -269,7 +262,6 @@ F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */, F4A9B8592442AB81001D8C55 /* PreferenceScreen.swift in Sources */, F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, - F4AD59B3244CC9D600296568 /* UserDefaults+isFirstLaunch.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, diff --git a/CatchUp-SwiftUI/AboutScreen.swift b/CatchUp-SwiftUI/AboutScreen.swift index e60e094..1f71742 100644 --- a/CatchUp-SwiftUI/AboutScreen.swift +++ b/CatchUp-SwiftUI/AboutScreen.swift @@ -137,8 +137,6 @@ struct AboutScreen: View { } } -struct AboutScreen_Previews: PreviewProvider { - static var previews: some View { - AboutScreen() - } +#Preview { + AboutScreen() } diff --git a/CatchUp-SwiftUI/AppDelegate.swift b/CatchUp-SwiftUI/AppDelegate.swift deleted file mode 100644 index 4a528ef..0000000 --- a/CatchUp-SwiftUI/AppDelegate.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// AppDelegate.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 6/26/19. -// Copyright © 2019 Token Solutions. All rights reserved. -// - -import UIKit -import UserNotifications - -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - - if UserDefaults.isFirstVersion2Launch() { - UNUserNotificationCenter.current().removeAllPendingNotificationRequests() - } - - return true - } - - func applicationWillTerminate(_ application: UIApplication) {} - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } -} - diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index 8d99aa5..d34e08d 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -9,7 +9,7 @@ import SwiftUI struct DetailScreen: View { - @State private var showingPreferenceScreen = false + @State private var isShowingPreferenceScreen = false @Environment(\.modelContext) var modelContext let notificationService = NotificationService() @@ -64,7 +64,7 @@ struct DetailScreen: View { .foregroundColor(.gray) } Button(action: { - showingPreferenceScreen = true + isShowingPreferenceScreen = true }) { Text("Change Notification Preference") .font(.headline) @@ -150,7 +150,7 @@ struct DetailScreen: View { } } .sheet( - isPresented: $showingPreferenceScreen, + isPresented: $isShowingPreferenceScreen, onDismiss: { notificationService.removeExistingNotifications(for: contact) notificationService.createNewNotification(for: contact, modelContext: modelContext) diff --git a/CatchUp-SwiftUI/Extensions/UserDefaults+isFirstLaunch.swift b/CatchUp-SwiftUI/Extensions/UserDefaults+isFirstLaunch.swift deleted file mode 100644 index c06411d..0000000 --- a/CatchUp-SwiftUI/Extensions/UserDefaults+isFirstLaunch.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// UserDefaults+isFirstLaunch.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 4/19/20. -// Copyright © 2020 Token Solutions. All rights reserved. -// - -import Foundation - -extension UserDefaults { - // check for is first launch on version 2.0 - only true on first invocation after app install, false on all further invocations - // Note: this is used in AppDelegate.swift in didFinishLaunchingWithOptions - static func isFirstVersion2Launch() -> Bool { - let hasLaunchedVersion2BeforeFlag = "hasLaunchedVersion2BeforeFlag" - let isFirstVersion2Launch = !UserDefaults.standard.bool(forKey: hasLaunchedVersion2BeforeFlag) - if (isFirstVersion2Launch) { - UserDefaults.standard.set(true, forKey: hasLaunchedVersion2BeforeFlag) - UserDefaults.standard.synchronize() - } - isFirstVersion2Launch ? print("It is the first launch on version 2.0") : print("It is not the first launch on version 2.0") - return isFirstVersion2Launch - } -} diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index 4648e52..061e122 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -11,26 +11,16 @@ import SwiftUI import SwiftUIKit import ContactsUI -enum ActiveSheet: Identifiable { - case contactPicker - case about - case updates - - var id: UUID { - UUID() - } -} - struct HomeScreen : View { - @State private var showSheet = false - @State private var showContactPicker = false - @State private var contacts: [CNContact] = [] - @State private var activeSheet: ActiveSheet? - @State private var showUpdates: ActiveSheet = .updates - - @Environment(\.scenePhase) var scenePhase - @Environment(\.modelContext) var modelContext + @Environment(\.modelContext) var modelContext @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] + + @AppStorage("initialVersion") var initialVersion = "2.0" + + @State private var isColdLaunch = true + @State private var isShowingContactPicker = false + @State private var isShowingUpdatesSheet = false + @State private var isShowingAboutSheet = false let notificationService = NotificationService() let helper = GeneralHelpers() @@ -68,8 +58,7 @@ struct HomeScreen : View { } Button(action: { - activeSheet = .contactPicker - showContactPicker.toggle() + isShowingContactPicker = true }) { HStack(alignment: .center, spacing: 6) { Image(systemName: "person.crop.circle.fill.badge.plus") @@ -81,43 +70,42 @@ struct HomeScreen : View { .padding(.top) .padding(.bottom) } - + .sheet(isPresented: $isShowingContactPicker) { + ContactPicker( + showPicker: $isShowingContactPicker, + onSelectContacts: { selectedContacts in + saveSelectedContact(for: selectedContacts) + } + ) + } + .navigationBarTitle(Text("CatchUp")) .navigationBarItems(trailing: - Button(action: { - activeSheet = .about - }) { + Button { + isShowingAboutSheet = true + } label: { Image(systemName: "ellipsis.circle") .font(.title2) .foregroundColor(.blue) } + .sheet(isPresented: $isShowingAboutSheet) { + AboutScreen() + } ) } - - .sheet(item: $activeSheet, onDismiss: { activeSheet = nil }) { item in - switch item { - case .contactPicker: - ContactPicker( - showPicker: $showContactPicker, - onSelectContacts: { c in - contacts = c - saveSelectedContact(for: contacts) - } - ) - case .about: - AboutScreen() - case .updates: - UpdatesScreen() - } - } } .accentColor(.orange) + .onAppear { print("HomeScreen onAppear") clearNotificationBadgeAndCheckForUpdate() notificationService.requestAuthorizationForNotifications() - resetNotifications() + + if isColdLaunch { + resetNotifications() + isColdLaunch = false + } print(modelContext.sqliteCommand) } @@ -141,17 +129,17 @@ struct HomeScreen : View { func checkForUpdate() { let version = helper.getCurrentAppVersion() - let savedVersion = UserDefaults.standard.string(forKey: "savedVersion") + print("latest version: \(version)") - if savedVersion == version { + if initialVersion == version { print("App is up to date!") } else { if updateIsMajor() { // Toggle to show UpdatesScreen as a sheet print("Major update detected, showing UpdatesScreen...") - activeSheet = .updates + isShowingUpdatesSheet = true } - UserDefaults.standard.set(version, forKey: "savedVersion") + initialVersion = version } } @@ -240,7 +228,6 @@ struct HomeScreen : View { print("Do nothing. Contact was already added to the database") } } - } func contactAlreadyAdded(name: String) -> Bool { @@ -254,8 +241,6 @@ struct HomeScreen : View { } } -struct HomeScreen_Previews : PreviewProvider { - static var previews: some View { - return HomeScreen() - } +#Preview { + HomeScreen() } diff --git a/CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift b/CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift index 2aea393..c8a06ff 100644 --- a/CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift +++ b/CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift @@ -21,8 +21,6 @@ struct ContactPhoto: View { } } -struct ContactPhoto_Previews: PreviewProvider { - static var previews: some View { - ContactPhoto(image: Image("DefaultPhoto")) - } +#Preview { + ContactPhoto(image: Image("DefaultPhoto")) } diff --git a/CatchUp-SwiftUI/Supporting Views/GradientView.swift b/CatchUp-SwiftUI/Supporting Views/GradientView.swift index db0f82f..79ac9dd 100644 --- a/CatchUp-SwiftUI/Supporting Views/GradientView.swift +++ b/CatchUp-SwiftUI/Supporting Views/GradientView.swift @@ -29,15 +29,11 @@ struct GradientView: View { } } -struct Gradient_Previews: PreviewProvider { - @Environment(\.colorScheme) var colorScheme - - static var previews: some View { - VStack { - GradientView() - .edgesIgnoringSafeArea(.top) - .frame(height: 150) - Spacer() - } +#Preview { + VStack { + GradientView() + .edgesIgnoringSafeArea(.top) + .frame(height: 150) + Spacer() } } diff --git a/CatchUp-SwiftUI/UpdatesScreen.swift b/CatchUp-SwiftUI/UpdatesScreen.swift index b050647..3adf3aa 100644 --- a/CatchUp-SwiftUI/UpdatesScreen.swift +++ b/CatchUp-SwiftUI/UpdatesScreen.swift @@ -56,10 +56,6 @@ struct UpdatesScreen: View { } } -struct UpdatesScreen_Previews: PreviewProvider { - @Environment(\.presentationMode) var presentationMode - - static var previews: some View { - UpdatesScreen() - } +#Preview { + UpdatesScreen() } From c91f9660c34646169447e74975d8ce37891511df Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Fri, 8 Mar 2024 22:28:48 -0700 Subject: [PATCH 08/46] Update project to recommend settings --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 7 ++++++- .../xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 53c071d..7eb06db 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -201,8 +201,9 @@ F48E37E622C455C3008B0B8B /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1100; - LastUpgradeCheck = 1200; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = "Token Solutions"; TargetAttributes = { F48E37ED22C455C3008B0B8B = { @@ -277,6 +278,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -310,6 +312,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; @@ -340,6 +343,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -373,6 +377,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; diff --git a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme index cb6ade8..bc0b26a 100644 --- a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme +++ b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 8 Mar 2024 22:30:41 -0700 Subject: [PATCH 09/46] initialVersion -> savedVersion --- CatchUp-SwiftUI/HomeScreen.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index 061e122..cc70da1 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -15,7 +15,7 @@ struct HomeScreen : View { @Environment(\.modelContext) var modelContext @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] - @AppStorage("initialVersion") var initialVersion = "2.0" + @AppStorage("savedVersion") var savedVersion = "2.0.0" @State private var isColdLaunch = true @State private var isShowingContactPicker = false @@ -131,7 +131,7 @@ struct HomeScreen : View { let version = helper.getCurrentAppVersion() print("latest version: \(version)") - if initialVersion == version { + if savedVersion == version { print("App is up to date!") } else { if updateIsMajor() { @@ -139,7 +139,7 @@ struct HomeScreen : View { print("Major update detected, showing UpdatesScreen...") isShowingUpdatesSheet = true } - initialVersion = version + savedVersion = version } } From 5f2cdf29d0d7c4189b79190a1d49d0e49f281a71 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 10 Mar 2024 17:56:34 -0600 Subject: [PATCH 10/46] Remove SwiftUIKit dependency and use Apple's native CNContactPickerViewController --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 21 ++-------- .../xcshareddata/swiftpm/Package.resolved | 12 +----- CatchUp-SwiftUI/HomeScreen.swift | 40 ++++++++++++++----- .../Supporting Views/ContactPicker.swift | 22 ++++++++++ 4 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 CatchUp-SwiftUI/Supporting Views/ContactPicker.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 7eb06db..79fde86 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -11,9 +11,9 @@ 14ACAC86245E52A40091AE90 /* PhoneNumberMetadata.json in Resources */ = {isa = PBXBuildFile; fileRef = 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */; }; 14BE3AD324593556004F72DE /* GeneralHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD224593556004F72DE /* GeneralHelpers.swift */; }; 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */; }; - F40293A1256186C2004E0418 /* SwiftUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = F40293A0256186C2004E0418 /* SwiftUIKit */; }; F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40293A625618C4C004E0418 /* ContactHelper.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; + F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */; }; F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationService.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -37,6 +37,7 @@ 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesScreen.swift; sourceTree = ""; }; F40293A625618C4C004E0418 /* ContactHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactHelper.swift; sourceTree = ""; }; F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; + F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicker.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -63,7 +64,6 @@ buildActionMask = 2147483647; files = ( 144DBE22245C8282008FDBB6 /* PhoneNumberKit in Frameworks */, - F40293A1256186C2004E0418 /* SwiftUIKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,6 +150,7 @@ children = ( F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, F4A9B8562442A1F7001D8C55 /* GradientView.swift */, + F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */, ); path = "Supporting Views"; sourceTree = ""; @@ -189,7 +190,6 @@ name = "CatchUp-SwiftUI"; packageProductDependencies = ( 144DBE21245C8282008FDBB6 /* PhoneNumberKit */, - F40293A0256186C2004E0418 /* SwiftUIKit */, ); productName = "CatchUp-SwiftUI"; productReference = F48E37EE22C455C3008B0B8B /* CatchUp.app */; @@ -222,7 +222,6 @@ mainGroup = F48E37E522C455C3008B0B8B; packageReferences = ( 144DBE20245C8282008FDBB6 /* XCRemoteSwiftPackageReference "PhoneNumberKit" */, - F402939F256186C2004E0418 /* XCRemoteSwiftPackageReference "SwiftUIKit" */, ); productRefGroup = F48E37EF22C455C3008B0B8B /* Products */; projectDirPath = ""; @@ -267,6 +266,7 @@ F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, + F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */, F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -484,14 +484,6 @@ minimumVersion = 3.2.0; }; }; - F402939F256186C2004E0418 /* XCRemoteSwiftPackageReference "SwiftUIKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/youjinp/SwiftUIKit.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.0.11; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -500,11 +492,6 @@ package = 144DBE20245C8282008FDBB6 /* XCRemoteSwiftPackageReference "PhoneNumberKit" */; productName = PhoneNumberKit; }; - F40293A0256186C2004E0418 /* SwiftUIKit */ = { - isa = XCSwiftPackageProductDependency; - package = F402939F256186C2004E0418 /* XCRemoteSwiftPackageReference "SwiftUIKit" */; - productName = SwiftUIKit; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = F48E37E622C455C3008B0B8B /* Project object */; diff --git a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index da4a267..4e5c8e0 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "d01b822f6446cef501cebe37b5b78101e0a82173bb523ee64501d3331d40b50c", "pins" : [ { "identity" : "phonenumberkit", @@ -8,16 +9,7 @@ "revision" : "a8d72d9c90f8336aff6fd6002976d7e36f4fbe8c", "version" : "3.7.9" } - }, - { - "identity" : "swiftuikit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/youjinp/SwiftUIKit.git", - "state" : { - "revision" : "a83aedd996bcb59ca846346bb507f090a7350e34", - "version" : "0.0.17" - } } ], - "version" : 2 + "version" : 3 } diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index cc70da1..b0281bb 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -21,7 +21,9 @@ struct HomeScreen : View { @State private var isShowingContactPicker = false @State private var isShowingUpdatesSheet = false @State private var isShowingAboutSheet = false - + + @State private var contactPicker = ContactPicker() + let notificationService = NotificationService() let helper = GeneralHelpers() let converter = Conversions() @@ -56,9 +58,15 @@ struct HomeScreen : View { } .onDelete(perform: removePendingNotificationsAndDeleteContact) } - + .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in + if !contacts.isEmpty { + saveSelectedContact(for: contacts) + } + contactPicker.chosenContacts = [] + } + Button(action: { - isShowingContactPicker = true + openContactPicker() }) { HStack(alignment: .center, spacing: 6) { Image(systemName: "person.crop.circle.fill.badge.plus") @@ -70,14 +78,14 @@ struct HomeScreen : View { .padding(.top) .padding(.bottom) } - .sheet(isPresented: $isShowingContactPicker) { - ContactPicker( - showPicker: $isShowingContactPicker, - onSelectContacts: { selectedContacts in - saveSelectedContact(for: selectedContacts) - } - ) - } +// .sheet(isPresented: $isShowingContactPicker) { +// ContactPicker( +// showPicker: $isShowingContactPicker, +// onSelectContacts: { selectedContacts in +// saveSelectedContact(for: selectedContacts) +// } +// ) +// } .navigationBarTitle(Text("CatchUp")) @@ -143,6 +151,15 @@ struct HomeScreen : View { } } + func openContactPicker() { + let contactPicker = CNContactPickerViewController() + contactPicker.delegate = self.contactPicker + let scenes = UIApplication.shared.connectedScenes + let windowScenes = scenes.first as? UIWindowScene + let window = windowScenes?.windows.first + window?.rootViewController?.present(contactPicker, animated: true, completion: nil) + } + func resetNotifications() { for contact in selectedContacts { notificationService.removeExistingNotifications(for: contact) @@ -230,6 +247,7 @@ struct HomeScreen : View { } } + func contactAlreadyAdded(name: String) -> Bool { for contact in selectedContacts { if contact.name == name { diff --git a/CatchUp-SwiftUI/Supporting Views/ContactPicker.swift b/CatchUp-SwiftUI/Supporting Views/ContactPicker.swift new file mode 100644 index 0000000..fca5c5c --- /dev/null +++ b/CatchUp-SwiftUI/Supporting Views/ContactPicker.swift @@ -0,0 +1,22 @@ +// +// ContactPicker.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import ContactsUI +import SwiftUI + +@Observable +class ContactPicker: NSObject, CNContactPickerDelegate { + var chosenContacts = [CNContact]() + + func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) { + chosenContacts = contacts + for contact in chosenContacts { + print("selected contact: \(contact.givenName) \(contact.familyName)") + } + } +} From 4e08e9f3a8ccf42e88e57c89b1874a1c08a4ac8b Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 10 Mar 2024 18:52:39 -0600 Subject: [PATCH 11/46] Cleanup and an IAPService fix --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 28 +- CatchUp-SwiftUI/AboutScreen.swift | 4 +- CatchUp-SwiftUI/DetailScreen.swift | 51 ++-- CatchUp-SwiftUI/HomeScreen.swift | 178 +++--------- CatchUp-SwiftUI/PreferenceScreen.swift | 6 +- CatchUp-SwiftUI/Services/ContactService.swift | 227 --------------- CatchUp-SwiftUI/Services/IAPService.swift | 19 +- CatchUp-SwiftUI/UpdatesScreen.swift | 4 +- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 275 +++++++++++++++++- CatchUp-SwiftUI/Utilities/Conversions.swift | 21 +- .../NotificationHelper.swift} | 75 +++-- .../{GeneralHelpers.swift => Utils.swift} | 21 +- 12 files changed, 426 insertions(+), 483 deletions(-) delete mode 100644 CatchUp-SwiftUI/Services/ContactService.swift rename CatchUp-SwiftUI/{Services/NotificationService.swift => Utilities/NotificationHelper.swift} (77%) rename CatchUp-SwiftUI/Utilities/{GeneralHelpers.swift => Utils.swift} (50%) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 79fde86..dc70b34 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -9,16 +9,15 @@ /* Begin PBXBuildFile section */ 144DBE22245C8282008FDBB6 /* PhoneNumberKit in Frameworks */ = {isa = PBXBuildFile; productRef = 144DBE21245C8282008FDBB6 /* PhoneNumberKit */; }; 14ACAC86245E52A40091AE90 /* PhoneNumberMetadata.json in Resources */ = {isa = PBXBuildFile; fileRef = 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */; }; - 14BE3AD324593556004F72DE /* GeneralHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD224593556004F72DE /* GeneralHelpers.swift */; }; + 14BE3AD324593556004F72DE /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD224593556004F72DE /* Utils.swift */; }; 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */; }; - F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40293A625618C4C004E0418 /* ContactHelper.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */; }; - F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationService.swift */; }; + F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; F48E37FE22C455CB008B0B8B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */; }; - F494F95824424A03003CE7B5 /* ContactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F494F95724424A03003CE7B5 /* ContactService.swift */; }; + F494F95824424A03003CE7B5 /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F494F95724424A03003CE7B5 /* ContactHelper.swift */; }; F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */; }; F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8542442A179001D8C55 /* ContactPhoto.swift */; }; F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8562442A1F7001D8C55 /* GradientView.swift */; }; @@ -33,18 +32,17 @@ /* Begin PBXFileReference section */ 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PhoneNumberMetadata.json; sourceTree = ""; }; - 14BE3AD224593556004F72DE /* GeneralHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralHelpers.swift; sourceTree = ""; }; + 14BE3AD224593556004F72DE /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesScreen.swift; sourceTree = ""; }; - F40293A625618C4C004E0418 /* ContactHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactHelper.swift; sourceTree = ""; }; F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicker.swift; sourceTree = ""; }; - F4871DA0244FC43C00925392 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; F48E37FA22C455CB008B0B8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F48E37FD22C455CB008B0B8B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; F48E380222C455CB008B0B8B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F494F95724424A03003CE7B5 /* ContactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactService.swift; sourceTree = ""; }; + F494F95724424A03003CE7B5 /* ContactHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactHelper.swift; sourceTree = ""; }; F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreen.swift; sourceTree = ""; }; F4A9B8542442A179001D8C55 /* ContactPhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPhoto.swift; sourceTree = ""; }; F4A9B8562442A1F7001D8C55 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; @@ -82,8 +80,9 @@ isa = PBXGroup; children = ( F4A9B85A2443FFF3001D8C55 /* Conversions.swift */, - 14BE3AD224593556004F72DE /* GeneralHelpers.swift */, - F40293A625618C4C004E0418 /* ContactHelper.swift */, + 14BE3AD224593556004F72DE /* Utils.swift */, + F494F95724424A03003CE7B5 /* ContactHelper.swift */, + F4871DA0244FC43C00925392 /* NotificationHelper.swift */, ); path = Utilities; sourceTree = ""; @@ -138,9 +137,7 @@ F4A9B85024429E40001D8C55 /* Services */ = { isa = PBXGroup; children = ( - F494F95724424A03003CE7B5 /* ContactService.swift */, F4AD59AD244C9FF600296568 /* IAPService.swift */, - F4871DA0244FC43C00925392 /* NotificationService.swift */, ); path = Services; sourceTree = ""; @@ -253,9 +250,9 @@ F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */, F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */, F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, - 14BE3AD324593556004F72DE /* GeneralHelpers.swift in Sources */, - F4871DA1244FC43C00925392 /* NotificationService.swift in Sources */, - F494F95824424A03003CE7B5 /* ContactService.swift in Sources */, + 14BE3AD324593556004F72DE /* Utils.swift in Sources */, + F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, + F494F95824424A03003CE7B5 /* ContactHelper.swift in Sources */, F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */, F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */, 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */, @@ -267,7 +264,6 @@ F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */, - F40293A725618C4C004E0418 /* ContactHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CatchUp-SwiftUI/AboutScreen.swift b/CatchUp-SwiftUI/AboutScreen.swift index 1f71742..e6e1243 100644 --- a/CatchUp-SwiftUI/AboutScreen.swift +++ b/CatchUp-SwiftUI/AboutScreen.swift @@ -105,9 +105,9 @@ struct AboutScreen: View { } Group { - Button(action: { + Button { showingUpdateScreen = true - }) { + } label: { Text("Show Latest Update Details") .font(.headline) .foregroundColor(.blue) diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/DetailScreen.swift index d34e08d..ccd77c0 100644 --- a/CatchUp-SwiftUI/DetailScreen.swift +++ b/CatchUp-SwiftUI/DetailScreen.swift @@ -11,36 +11,31 @@ import SwiftUI struct DetailScreen: View { @State private var isShowingPreferenceScreen = false @Environment(\.modelContext) var modelContext - - let notificationService = NotificationService() - let converter = Conversions() - let helper = GeneralHelpers() - let contactService = ContactService() @Bindable var contact: SelectedContact var formattedPrimaryPhoneNumber: String { - converter.getFormattedPhoneNumber(from: contact.phone) + Converter.getFormattedPhoneNumber(from: contact.phone) } var formattedSecondaryPhoneNumber: String { - converter.getFormattedPhoneNumber(from: contact.secondary_phone) + Converter.getFormattedPhoneNumber(from: contact.secondary_phone) } var tappablePrimaryPhoneNumber: URL { - converter.getTappablePhoneNumber(from: contact.phone) + Converter.getTappablePhoneNumber(from: contact.phone) } var tappableSecondaryPhoneNumber: URL { - converter.getTappablePhoneNumber(from: contact.secondary_phone) + Converter.getTappablePhoneNumber(from: contact.secondary_phone) } var tappablePrimaryEmail: URL { - converter.getTappablePhoneNumber(from: contact.email) + Converter.getTappablePhoneNumber(from: contact.email) } var tappableSecondaryEmail: URL { - converter.getTappablePhoneNumber(from: contact.secondary_email) + Converter.getTappablePhoneNumber(from: contact.secondary_email) } var body: some View { @@ -49,7 +44,7 @@ struct DetailScreen: View { .edgesIgnoringSafeArea(.top) .frame(height: 75) - ContactPhoto(image: converter.getContactPicture(from: contact.picture)) + ContactPhoto(image: Converter.getContactPicture(from: contact.picture)) .offset(x: 0, y: -130) .padding(.bottom, -130) @@ -60,12 +55,12 @@ struct DetailScreen: View { HStack(spacing: 0) { Text("Preference: ") .foregroundColor(.gray) - Text(converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) + Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) .foregroundColor(.gray) } - Button(action: { + Button { isShowingPreferenceScreen = true - }) { + } label: { Text("Change Notification Preference") .font(.headline) .foregroundColor(.orange) @@ -74,7 +69,7 @@ struct DetailScreen: View { List { Section(header: Text("Contact Information")) { - if contactService.contactHasPhone(contact) { + if ContactHelper.contactHasPhone(contact) { VStack(alignment: .leading, spacing: 3) { Text("Phone") .font(.caption) @@ -85,7 +80,7 @@ struct DetailScreen: View { .foregroundColor(.blue) } } - if contactService.contactHasSecondaryPhone(contact) { + if ContactHelper.contactHasSecondaryPhone(contact) { VStack(alignment: .leading, spacing: 3) { Text("Secondary Phone") .font(.caption) @@ -96,7 +91,7 @@ struct DetailScreen: View { .foregroundColor(.blue) } } - if contactService.contactHasEmail(contact) { + if ContactHelper.contactHasEmail(contact) { VStack(alignment: .leading, spacing: 3) { Text("Email") .font(.caption) @@ -107,7 +102,7 @@ struct DetailScreen: View { .foregroundColor(.blue) } } - if contactService.contactHasSecondaryEmail(contact) { + if ContactHelper.contactHasSecondaryEmail(contact) { VStack(alignment: .leading, spacing: 3) { Text("Secondary Email") .font(.caption) @@ -118,32 +113,32 @@ struct DetailScreen: View { .foregroundColor(.blue) } } - if contactService.contactHasAddress(contact) { + if ContactHelper.contactHasAddress(contact) { VStack(alignment: .leading, spacing: 3) { Text("Address") .font(.caption) Text(contact.address) } } - if contactService.contactHasSecondaryAddress(contact) { + if ContactHelper.contactHasSecondaryAddress(contact) { VStack(alignment: .leading, spacing: 3) { Text("Secondary Address") .font(.caption) Text(contact.secondary_address) } } - if notificationService.contactHasBirthday(contact) { + if NotificationHelper.contactHasBirthday(contact) { VStack(alignment: .leading, spacing: 3) { Text("Birthday") .font(.caption) - Text(converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) } } - if notificationService.contactHasAnniversary(contact) { + if NotificationHelper.contactHasAnniversary(contact) { VStack(alignment: .leading, spacing: 3) { Text("Anniversary") .font(.caption) - Text(converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) } } } @@ -152,11 +147,11 @@ struct DetailScreen: View { .sheet( isPresented: $isShowingPreferenceScreen, onDismiss: { - notificationService.removeExistingNotifications(for: contact) - notificationService.createNewNotification(for: contact, modelContext: modelContext) + NotificationHelper.removeExistingNotifications(for: contact) + NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) }) { PreferenceScreen(contact: contact) } - .onAppear(perform: helper.clearNotificationBadge) + .onAppear(perform: Utils.clearNotificationBadge) } } diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/HomeScreen.swift index b0281bb..ce783ac 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/HomeScreen.swift @@ -18,16 +18,10 @@ struct HomeScreen : View { @AppStorage("savedVersion") var savedVersion = "2.0.0" @State private var isColdLaunch = true - @State private var isShowingContactPicker = false @State private var isShowingUpdatesSheet = false @State private var isShowingAboutSheet = false - @State private var contactPicker = ContactPicker() - let notificationService = NotificationService() - let helper = GeneralHelpers() - let converter = Conversions() - init() { //Use this if NavigationBarTitle is with Large Font UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange] @@ -40,7 +34,7 @@ struct HomeScreen : View { ForEach(selectedContacts) { contact in NavigationLink(destination: DetailScreen(contact: contact)) { HStack { - converter.getContactPicture(from: contact.picture) + Converter.getContactPicture(from: contact.picture) .renderingMode(.original) .resizable() .frame(width: 45, height: 45, alignment: .leading) @@ -49,7 +43,7 @@ struct HomeScreen : View { VStack(alignment: .leading, spacing: 2) { Text(contact.name) .font(.headline) - Text(converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) + Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) .font(.caption) .foregroundColor(.gray) } @@ -65,9 +59,9 @@ struct HomeScreen : View { contactPicker.chosenContacts = [] } - Button(action: { + Button { openContactPicker() - }) { + } label: { HStack(alignment: .center, spacing: 6) { Image(systemName: "person.crop.circle.fill.badge.plus") @@ -78,14 +72,6 @@ struct HomeScreen : View { .padding(.top) .padding(.bottom) } -// .sheet(isPresented: $isShowingContactPicker) { -// ContactPicker( -// showPicker: $isShowingContactPicker, -// onSelectContacts: { selectedContacts in -// saveSelectedContact(for: selectedContacts) -// } -// ) -// } .navigationBarTitle(Text("CatchUp")) @@ -106,23 +92,50 @@ struct HomeScreen : View { .accentColor(.orange) .onAppear { - print("HomeScreen onAppear") + print("sqlite path: \(modelContext.sqliteCommand)") clearNotificationBadgeAndCheckForUpdate() - notificationService.requestAuthorizationForNotifications() + NotificationHelper.requestAuthorizationForNotifications() if isColdLaunch { - resetNotifications() + NotificationHelper.resetNotifications(for: selectedContacts, modelContext: modelContext) isColdLaunch = false } + } + } + + func openContactPicker() { + let contactPicker = CNContactPickerViewController() + contactPicker.delegate = self.contactPicker + let scenes = UIApplication.shared.connectedScenes + let windowScenes = scenes.first as? UIWindowScene + let window = windowScenes?.windows.first + window?.rootViewController?.present(contactPicker, animated: true, completion: nil) + } + + // save selected contacts and their properties to SwiftData + func saveSelectedContact(for contacts: [CNContact]) { + for contact in contacts { + let contactName = ContactHelper.getContactName(for: contact) + if !contactAlreadyAdded(name: contactName) { + let selectedContact = ContactHelper.createSelectedContact(contact: contact) + modelContext.insert(selectedContact) + } + } + } - print(modelContext.sqliteCommand) + func contactAlreadyAdded(name: String) -> Bool { + for contact in selectedContacts { + if contact.name == name { + return true + } } + return false } - + func clearNotificationBadgeAndCheckForUpdate() { - fetchAvailableIAPs() - helper.clearNotificationBadge() - try? modelContext.save() + Utils.fetchAvailableIAPs() + Utils.clearNotificationBadge() + checkForUpdate() } @@ -130,19 +143,19 @@ struct HomeScreen : View { for index in offsets { let contact = selectedContacts[index] - notificationService.removeExistingNotifications(for: contact) + NotificationHelper.removeExistingNotifications(for: contact) modelContext.delete(contact) } } func checkForUpdate() { - let version = helper.getCurrentAppVersion() + let version = Utils.getCurrentAppVersion() print("latest version: \(version)") if savedVersion == version { print("App is up to date!") } else { - if updateIsMajor() { + if Utils.updateIsMajor() { // Toggle to show UpdatesScreen as a sheet print("Major update detected, showing UpdatesScreen...") isShowingUpdatesSheet = true @@ -150,113 +163,6 @@ struct HomeScreen : View { savedVersion = version } } - - func openContactPicker() { - let contactPicker = CNContactPickerViewController() - contactPicker.delegate = self.contactPicker - let scenes = UIApplication.shared.connectedScenes - let windowScenes = scenes.first as? UIWindowScene - let window = windowScenes?.windows.first - window?.rootViewController?.present(contactPicker, animated: true, completion: nil) - } - - func resetNotifications() { - for contact in selectedContacts { - notificationService.removeExistingNotifications(for: contact) - notificationService.createNewNotification(for: contact, modelContext: modelContext) - } - } - - func updateIsMajor() -> Bool { - let version = helper.getCurrentAppVersion() - if version.suffix(2) == ".0" { - return true - } else { - return false - } - } - - func fetchAvailableIAPs() { - print("fetching IAPs") - IAPService.shared.fetchAvailableProducts() - } - - // save selected contacts and their properties to Core Data - func saveSelectedContact(for contacts: [CNContact]) { - print("saving...") - - let contactService = ContactService() - for contact in contacts { - let currentMinute = Calendar.current.component(.minute, from: Date()) - let currentHour = Calendar.current.component(.hour, from: Date()) - let currentDay = Calendar.current.component(.day, from: Date()) - let currentMonth = Calendar.current.component(.month, from: Date()) - let currentYear = Calendar.current.component(.year, from: Date()) - - let id = UUID() - let address = contactService.getContactPrimaryAddress(for: contact) - let anniversary = contactService.getContactAnniversary(for: contact) - let anniversary_notification_ID = UUID() - let birthday = contactService.getContactBirthday(for: contact) - let birthday_notification_ID = UUID() - let email = contactService.getContactPrimaryEmail(for: contact) - let name = contactService.getContactName(for: contact) - let notification_identifier = UUID() - let notification_preference = 0 - let notification_preference_hour = currentHour - let notification_preference_minute = currentMinute - let notification_preference_weekday = 0 - let notification_preference_custom_year = currentYear - let notification_preference_custom_month = currentMonth - let notification_preference_custom_day = currentDay - let phone = contactService.getContactPrimaryPhone(for: contact) - let picture = contactService.encodeContactPicture(for: contact) - let secondary_email = contactService.getContactSecondaryEmail(for: contact) - let secondary_address = contactService.getContactSecondaryAddress(for: contact) - let secondary_phone = contactService.getContactSecondaryPhone(for: contact) - - if !contactAlreadyAdded(name: name) { - let selectedContact = SelectedContact( - address: address, - anniversary: anniversary, - anniversary_notification_id: anniversary_notification_ID, - birthday: birthday, - birthday_notification_id: birthday_notification_ID, - email: email, - id: id, - name: name, - notification_identifier: notification_identifier, - notification_preference: notification_preference, - notification_preference_custom_day: notification_preference_custom_day, - notification_preference_custom_month: notification_preference_custom_month, - notification_preference_custom_year: notification_preference_custom_year, - notification_preference_hour: notification_preference_hour, - notification_preference_minute: notification_preference_minute, - notification_preference_weekday: notification_preference_weekday, - phone: phone, - picture: picture, - secondary_address: secondary_address, - secondary_email: secondary_email, - secondary_phone: secondary_phone - ) - - modelContext.insert(selectedContact) - } else { - print("Do nothing. Contact was already added to the database") - } - } - } - - - func contactAlreadyAdded(name: String) -> Bool { - for contact in selectedContacts { - if contact.name == name { - return true - } - } - - return false - } } #Preview { diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/PreferenceScreen.swift index 835d650..5b57079 100644 --- a/CatchUp-SwiftUI/PreferenceScreen.swift +++ b/CatchUp-SwiftUI/PreferenceScreen.swift @@ -17,9 +17,7 @@ struct PreferenceScreen: View { @Bindable var contact: SelectedContact - let notificationService = NotificationService() let now = Date() - var notificationOptions = ["Never", "Daily", "Weekly", "Monthly", "Custom"] var dayOptions = ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"] @@ -117,14 +115,14 @@ struct PreferenceScreen: View { .onChange(of: notificationPreferenceTime) { initialTime, newTime in let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute], from : newTime) - notificationService.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!, modelContext: modelContext) + NotificationHelper.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!, modelContext: modelContext) } .onChange(of: notificationPreferenceCustomDate) { initialDate, newDate in let year = Calendar.current.component(.year, from: newDate) let month = Calendar.current.component(.month, from: newDate) let day = Calendar.current.component(.day, from: newDate) - notificationService.updateNotificationCustomDate(for: contact, month: month, day: day, year: year, modelContext: modelContext) + NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year, modelContext: modelContext) } } diff --git a/CatchUp-SwiftUI/Services/ContactService.swift b/CatchUp-SwiftUI/Services/ContactService.swift deleted file mode 100644 index 13bb24f..0000000 --- a/CatchUp-SwiftUI/Services/ContactService.swift +++ /dev/null @@ -1,227 +0,0 @@ -// -// ContactUtilityFunctions.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 4/11/20. -// Copyright © 2020 Token Solutions. All rights reserved. -// - -import SwiftUI -import UIKit -import Contacts -import CoreData - -struct ContactService { - - // MARK: Functions for DetailScreen - - func contactHasPhone(_ contact: SelectedContact) -> Bool { - return contact.phone != "" ? true : false - } - - func contactHasSecondaryPhone(_ contact: SelectedContact) -> Bool { - return contact.secondary_phone != "" ? true : false - } - - func contactHasEmail(_ contact: SelectedContact) -> Bool { - return contact.email != "" ? true : false - } - - func contactHasSecondaryEmail(_ contact: SelectedContact) -> Bool { - return contact.secondary_email != "" ? true : false - } - - func contactHasAddress(_ contact: SelectedContact) -> Bool { - return contact.address != "" ? true : false - } - - func contactHasSecondaryAddress(_ contact: SelectedContact) -> Bool { - return contact.secondary_address != "" ? true : false - } - - // MARK: Functions for ContactPickerViewController - - func encodeContactPicture(for contact: CNContact) -> String { - let picture: String - - if contact.imageDataAvailable == true { - let dataPicture: NSData = contact.thumbnailImageData! as NSData - picture = dataPicture.base64EncodedString() - } else { - // there is not an image for this contact, use the default image - let image = UIImage(named: "DefaultPhoto") - let imageData: NSData = image!.pngData()! as NSData - picture = imageData.base64EncodedString() - } - - return picture - } - - func getContactName(for contact: CNContact) -> String { - var name:String - - //if they have a first and a last name - if contact.givenName != "" && contact.familyName != "" { - name = contact.givenName + " " + contact.familyName - //if they have a first name, but no last name - } else if contact.givenName != "" && contact.familyName == "" { - name = contact.givenName - //if they have no first name, but have a last name - } else if contact.givenName == "" && contact.familyName != "" { - name = contact.familyName - } else { - name = "" - } - - return name - } - - func getContactPrimaryPhone(for contact: CNContact) -> String { - let userPhoneNumbers: [CNLabeledValue] = contact.phoneNumbers - var phone: String - - //check for phone numbers and set values - if userPhoneNumbers.count > 0 { - let firstPhoneNumber = userPhoneNumbers[0].value - phone = firstPhoneNumber.stringValue - } else { - phone = "" - } - - return phone - } - - func getContactSecondaryPhone(for contact: CNContact) -> String { - let userPhoneNumbers: [CNLabeledValue] = contact.phoneNumbers - var secondary_phone: String - - if userPhoneNumbers.count > 1 { - let secondPhoneNumber = userPhoneNumbers[1].value - secondary_phone = secondPhoneNumber.stringValue - } else { - secondary_phone = "" - } - - return secondary_phone - } - - func getContactPrimaryEmail(for contact: CNContact) -> String { - let emailAddresses = contact.emailAddresses - var email: String - - if emailAddresses.count > 0 { - let firstEmail = emailAddresses[0].value - email = firstEmail as String - } else { - email = "" - } - - return email - } - - func getContactSecondaryEmail(for contact: CNContact) -> String { - let emailAddresses = contact.emailAddresses - var secondary_email: String - - if emailAddresses.count > 1 { - let secondEmail = emailAddresses[1].value - secondary_email = secondEmail as String - } else { - secondary_email = "" - } - - return secondary_email - } - - func getContactPrimaryAddress(for contact: CNContact) -> String { - //contact postal address array - let addresses = contact.postalAddresses - var address: String - - //check for postal addresses and set values - if addresses.count > 0 { - let firstAddress = addresses[0].value - let fullAddress = firstAddress.street + ", " + firstAddress.city + ", " + firstAddress.state + " " + firstAddress.postalCode - address = fullAddress.replacingOccurrences(of: "\n", with: " ") - } else { - address = "" - } - - return address - } - - func getContactSecondaryAddress(for contact: CNContact) -> String { - //contact postal address array - let addresses = contact.postalAddresses - var secondary_address: String - - //check for postal addresses and set values - if addresses.count > 1 { - let secondAddress = addresses[1].value - let fullAddress = secondAddress.street + ", " + secondAddress.city + ", " + secondAddress.state + " " + secondAddress.postalCode - secondary_address = fullAddress.replacingOccurrences(of: "\n", with: " ") - } else { - secondary_address = "" - } - - return secondary_address - } - - func getContactBirthday(for contact: CNContact) -> String { - var birthdayString: String - - if contact.birthday != nil { - - let birthday = contact.birthday?.date - - let formatter = DateFormatter() - formatter.dateFormat = "MM-dd" - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - - birthdayString = formatter.string(from: birthday!) - - let birthdayDate = formatter.date(from: birthdayString)! - birthdayString = formatter.string(from: birthdayDate) - - } else { - birthdayString = "" - } - - return birthdayString - } - - func getContactAnniversary(for contact: CNContact) -> String { - //check for anniversary and set value for anniversary and reminder preference - var anniversaryString: String - - let anniversary = contact.dates.filter { date -> Bool in - - guard let label = date.label else { - return false - } - - return label.contains("Anniversary") - - } .first?.value as DateComponents? - - if anniversary?.date != nil { - - let formatter = DateFormatter() - formatter.dateFormat = "MM-dd" - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - - anniversaryString = formatter.string(from: (anniversary?.date!)!) - - let anniversaryDate = formatter.date(from: anniversaryString)! - anniversaryString = formatter.string(from: anniversaryDate) - - } else { - anniversaryString = "" - } - - return anniversaryString - } - -} diff --git a/CatchUp-SwiftUI/Services/IAPService.swift b/CatchUp-SwiftUI/Services/IAPService.swift index 3992ddd..f90e24a 100644 --- a/CatchUp-SwiftUI/Services/IAPService.swift +++ b/CatchUp-SwiftUI/Services/IAPService.swift @@ -59,27 +59,15 @@ class IAPService: NSObject { } func getSmallTipAmount() -> String { - if iapProducts.count > 0 { - return iapProducts[1].localizedPrice - } else { - return "$0.99" - } + return iapProducts.first(where: { $0.productIdentifier == "gracious_tip_0.99" })?.localizedPrice ?? "$0.99" } func getMediumTipAmount() -> String { - if iapProducts.count > 0 { - return iapProducts[0].localizedPrice - } else { - return "$1.99" - } + return iapProducts.first(where: { $0.productIdentifier == "generous_tip_1.99" })?.localizedPrice ?? "$1.99" } func getLargeTipAmount() -> String { - if iapProducts.count > 0 { - return iapProducts[2].localizedPrice - } else { - return "$4.99" - } + return iapProducts.first(where: { $0.productIdentifier == "gratuitous_tip_4.99" })?.localizedPrice ?? "$4.99" } // MARK: - RESTORE PURCHASE @@ -103,7 +91,6 @@ class IAPService: NSObject { extension IAPService: SKProductsRequestDelegate, SKPaymentTransactionObserver{ // MARK: - REQUEST IAP PRODUCTS func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) { - if (response.products.count > 0) { iapProducts = response.products for product in iapProducts{ diff --git a/CatchUp-SwiftUI/UpdatesScreen.swift b/CatchUp-SwiftUI/UpdatesScreen.swift index 3adf3aa..8ba4be2 100644 --- a/CatchUp-SwiftUI/UpdatesScreen.swift +++ b/CatchUp-SwiftUI/UpdatesScreen.swift @@ -9,8 +9,6 @@ import SwiftUI struct UpdatesScreen: View { - let helper = GeneralHelpers() - var body: some View { ScrollView { VStack(alignment: .leading, spacing: 10) { @@ -24,7 +22,7 @@ struct UpdatesScreen: View { .bold() .foregroundColor(.orange) - Text("Version \(helper.getCurrentAppVersion())") + Text("Version \(Utils.getCurrentAppVersion())") .font(.headline) .foregroundColor(.blue) diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 9db89b6..21d8b8e 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -2,14 +2,281 @@ // ContactHelper.swift // CatchUp-SwiftUI // -// Created by Ryan Token on 11/15/20. +// Created by Ryan Token on 4/11/20. // Copyright © 2020 Token Solutions. All rights reserved. // -import Foundation -import ContactsUI +import SwiftUI +import UIKit +import Contacts import CoreData struct ContactHelper { - + + // MARK: Functions for DetailScreen + + static func contactHasPhone(_ contact: SelectedContact) -> Bool { + return contact.phone != "" ? true : false + } + + static func contactHasSecondaryPhone(_ contact: SelectedContact) -> Bool { + return contact.secondary_phone != "" ? true : false + } + + static func contactHasEmail(_ contact: SelectedContact) -> Bool { + return contact.email != "" ? true : false + } + + static func contactHasSecondaryEmail(_ contact: SelectedContact) -> Bool { + return contact.secondary_email != "" ? true : false + } + + static func contactHasAddress(_ contact: SelectedContact) -> Bool { + return contact.address != "" ? true : false + } + + static func contactHasSecondaryAddress(_ contact: SelectedContact) -> Bool { + return contact.secondary_address != "" ? true : false + } + + // MARK: Functions for ContactPickerViewController + + static func encodeContactPicture(for contact: CNContact) -> String { + let picture: String + + if contact.imageDataAvailable == true { + let dataPicture: NSData = contact.thumbnailImageData! as NSData + picture = dataPicture.base64EncodedString() + } else { + // there is not an image for this contact, use the default image + let image = UIImage(named: "DefaultPhoto") + let imageData: NSData = image!.pngData()! as NSData + picture = imageData.base64EncodedString() + } + + return picture + } + + static func getContactName(for contact: CNContact) -> String { + var name:String + + //if they have a first and a last name + if contact.givenName != "" && contact.familyName != "" { + name = contact.givenName + " " + contact.familyName + //if they have a first name, but no last name + } else if contact.givenName != "" && contact.familyName == "" { + name = contact.givenName + //if they have no first name, but have a last name + } else if contact.givenName == "" && contact.familyName != "" { + name = contact.familyName + } else { + name = "" + } + + return name + } + + static func getContactPrimaryPhone(for contact: CNContact) -> String { + let userPhoneNumbers: [CNLabeledValue] = contact.phoneNumbers + var phone: String + + //check for phone numbers and set values + if userPhoneNumbers.count > 0 { + let firstPhoneNumber = userPhoneNumbers[0].value + phone = firstPhoneNumber.stringValue + } else { + phone = "" + } + + return phone + } + + static func getContactSecondaryPhone(for contact: CNContact) -> String { + let userPhoneNumbers: [CNLabeledValue] = contact.phoneNumbers + var secondary_phone: String + + if userPhoneNumbers.count > 1 { + let secondPhoneNumber = userPhoneNumbers[1].value + secondary_phone = secondPhoneNumber.stringValue + } else { + secondary_phone = "" + } + + return secondary_phone + } + + static func getContactPrimaryEmail(for contact: CNContact) -> String { + let emailAddresses = contact.emailAddresses + var email: String + + if emailAddresses.count > 0 { + let firstEmail = emailAddresses[0].value + email = firstEmail as String + } else { + email = "" + } + + return email + } + + static func getContactSecondaryEmail(for contact: CNContact) -> String { + let emailAddresses = contact.emailAddresses + var secondary_email: String + + if emailAddresses.count > 1 { + let secondEmail = emailAddresses[1].value + secondary_email = secondEmail as String + } else { + secondary_email = "" + } + + return secondary_email + } + + static func getContactPrimaryAddress(for contact: CNContact) -> String { + //contact postal address array + let addresses = contact.postalAddresses + var address: String + + //check for postal addresses and set values + if addresses.count > 0 { + let firstAddress = addresses[0].value + let fullAddress = firstAddress.street + ", " + firstAddress.city + ", " + firstAddress.state + " " + firstAddress.postalCode + address = fullAddress.replacingOccurrences(of: "\n", with: " ") + } else { + address = "" + } + + return address + } + + static func getContactSecondaryAddress(for contact: CNContact) -> String { + //contact postal address array + let addresses = contact.postalAddresses + var secondary_address: String + + //check for postal addresses and set values + if addresses.count > 1 { + let secondAddress = addresses[1].value + let fullAddress = secondAddress.street + ", " + secondAddress.city + ", " + secondAddress.state + " " + secondAddress.postalCode + secondary_address = fullAddress.replacingOccurrences(of: "\n", with: " ") + } else { + secondary_address = "" + } + + return secondary_address + } + + static func getContactBirthday(for contact: CNContact) -> String { + var birthdayString: String + + if contact.birthday != nil { + + let birthday = contact.birthday?.date + + let formatter = DateFormatter() + formatter.dateFormat = "MM-dd" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + + birthdayString = formatter.string(from: birthday!) + + let birthdayDate = formatter.date(from: birthdayString)! + birthdayString = formatter.string(from: birthdayDate) + + } else { + birthdayString = "" + } + + return birthdayString + } + + static func getContactAnniversary(for contact: CNContact) -> String { + //check for anniversary and set value for anniversary and reminder preference + var anniversaryString: String + + let anniversary = contact.dates.filter { date -> Bool in + + guard let label = date.label else { + return false + } + + return label.contains("Anniversary") + + } .first?.value as DateComponents? + + if anniversary?.date != nil { + + let formatter = DateFormatter() + formatter.dateFormat = "MM-dd" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + + anniversaryString = formatter.string(from: (anniversary?.date!)!) + + let anniversaryDate = formatter.date(from: anniversaryString)! + anniversaryString = formatter.string(from: anniversaryDate) + + } else { + anniversaryString = "" + } + + return anniversaryString + } + + static func createSelectedContact(contact: CNContact) -> SelectedContact { + let currentMinute = Calendar.current.component(.minute, from: Date()) + let currentHour = Calendar.current.component(.hour, from: Date()) + let currentDay = Calendar.current.component(.day, from: Date()) + let currentMonth = Calendar.current.component(.month, from: Date()) + let currentYear = Calendar.current.component(.year, from: Date()) + + let id = UUID() + let address = ContactHelper.getContactPrimaryAddress(for: contact) + let anniversary = ContactHelper.getContactAnniversary(for: contact) + let anniversary_notification_ID = UUID() + let birthday = ContactHelper.getContactBirthday(for: contact) + let birthday_notification_ID = UUID() + let email = ContactHelper.getContactPrimaryEmail(for: contact) + let name = ContactHelper.getContactName(for: contact) + let notification_identifier = UUID() + let notification_preference = 0 + let notification_preference_hour = currentHour + let notification_preference_minute = currentMinute + let notification_preference_weekday = 0 + let notification_preference_custom_year = currentYear + let notification_preference_custom_month = currentMonth + let notification_preference_custom_day = currentDay + let phone = ContactHelper.getContactPrimaryPhone(for: contact) + let picture = ContactHelper.encodeContactPicture(for: contact) + let secondary_email = ContactHelper.getContactSecondaryEmail(for: contact) + let secondary_address = ContactHelper.getContactSecondaryAddress(for: contact) + let secondary_phone = ContactHelper.getContactSecondaryPhone(for: contact) + + let selectedContact = SelectedContact( + address: address, + anniversary: anniversary, + anniversary_notification_id: anniversary_notification_ID, + birthday: birthday, + birthday_notification_id: birthday_notification_ID, + email: email, + id: id, + name: name, + notification_identifier: notification_identifier, + notification_preference: notification_preference, + notification_preference_custom_day: notification_preference_custom_day, + notification_preference_custom_month: notification_preference_custom_month, + notification_preference_custom_year: notification_preference_custom_year, + notification_preference_hour: notification_preference_hour, + notification_preference_minute: notification_preference_minute, + notification_preference_weekday: notification_preference_weekday, + phone: phone, + picture: picture, + secondary_address: secondary_address, + secondary_email: secondary_email, + secondary_phone: secondary_phone + ) + + return selectedContact + } } diff --git a/CatchUp-SwiftUI/Utilities/Conversions.swift b/CatchUp-SwiftUI/Utilities/Conversions.swift index 195e0c9..3f8242e 100644 --- a/CatchUp-SwiftUI/Utilities/Conversions.swift +++ b/CatchUp-SwiftUI/Utilities/Conversions.swift @@ -10,10 +10,9 @@ import Foundation import SwiftUI import PhoneNumberKit -struct Conversions { +class Converter { // MARK: Only used in DetailScreen - - func getFormattedPhoneNumber(from phoneNumber: String) -> String { + static func getFormattedPhoneNumber(from phoneNumber: String) -> String { let phoneNumberKit = PhoneNumberKit() print("formatting phone number: \(phoneNumber)") @@ -29,7 +28,7 @@ struct Conversions { } } - func getTappablePhoneNumber(from phoneNumber: String) -> URL { + static func getTappablePhoneNumber(from phoneNumber: String) -> URL { print("getting tappable phone number: \(phoneNumber)") let tel = "tel://" @@ -40,7 +39,7 @@ struct Conversions { return tappableNumber } - func getTappableEmail(from emailAddress: String) -> URL { + static func getTappableEmail(from emailAddress: String) -> URL { let mailto = "mailto:" let formattedString = mailto + emailAddress let tappableEmail = URL(string: formattedString)! @@ -48,7 +47,7 @@ struct Conversions { return tappableEmail } - func getFormattedBirthdayOrAnniversary(from storedDate: String) -> String { + static func getFormattedBirthdayOrAnniversary(from storedDate: String) -> String { var month = storedDate.prefix(2) let day = storedDate.suffix(2) @@ -100,7 +99,7 @@ struct Conversions { // MARK: Used in HomeScreen and DetailScreen - func getContactPicture(from string: String) -> Image { + static func getContactPicture(from string: String) -> Image { let imageData = NSData(base64Encoded: string) let uiImage = UIImage(data: imageData! as Data)! let image = Image(uiImage: uiImage) @@ -108,7 +107,7 @@ struct Conversions { return image } - func convertNotificationPreferenceIntToString(preference: Int, contact: SelectedContact) -> String { + static func convertNotificationPreferenceIntToString(preference: Int, contact: SelectedContact) -> String { let time = convertHourAndMinuteFromIntToString(for: contact) let weekday = convertWeekdayFromIntToString(for: contact) let customDate = convertCustomDateFromIntToString(for: contact) @@ -129,7 +128,7 @@ struct Conversions { } } - func convertWeekdayFromIntToString(for contact: SelectedContact) -> String { + static func convertWeekdayFromIntToString(for contact: SelectedContact) -> String { let weekday: String switch contact.notification_preference_weekday { @@ -162,7 +161,7 @@ struct Conversions { return weekday } - func convertHourAndMinuteFromIntToString(for contact: SelectedContact) -> String { + static func convertHourAndMinuteFromIntToString(for contact: SelectedContact) -> String { var hour: String var suffix: String @@ -266,7 +265,7 @@ struct Conversions { return time } - func convertCustomDateFromIntToString(for contact: SelectedContact) -> String { + static func convertCustomDateFromIntToString(for contact: SelectedContact) -> String { var month: String var day: String var year: String diff --git a/CatchUp-SwiftUI/Services/NotificationService.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift similarity index 77% rename from CatchUp-SwiftUI/Services/NotificationService.swift rename to CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 22ee847..8155b83 100644 --- a/CatchUp-SwiftUI/Services/NotificationService.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -1,5 +1,5 @@ // -// NotificationService.swift +// NotificationHelper.swift // CatchUp-SwiftUI // // Created by Ryan Token on 4/21/20. @@ -11,12 +11,8 @@ import SwiftUI import UserNotifications import CoreData -struct NotificationService { - let notificationCenter = UNUserNotificationCenter.current() - let helper = GeneralHelpers() - let contactService = ContactService() - - func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { +class NotificationHelper { + static func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { let addRequest = { if preferenceIsNotSetToNever(for: contact) { addGeneralNotification(for: contact, modelContext: modelContext) @@ -34,19 +30,19 @@ struct NotificationService { checkNotificationAuthorizationStatusAndAddRequest(action: addRequest) } - func preferenceIsNotSetToNever(for contact: SelectedContact) -> Bool { + static func preferenceIsNotSetToNever(for contact: SelectedContact) -> Bool { return contact.notification_preference != 0 ? true : false } - func contactHasBirthday(_ contact: SelectedContact) -> Bool { + static func contactHasBirthday(_ contact: SelectedContact) -> Bool { return contact.birthday != "" ? true : false } - func contactHasAnniversary(_ contact: SelectedContact) -> Bool { + static func contactHasAnniversary(_ contact: SelectedContact) -> Bool { return contact.anniversary != "" ? true : false } - func addGeneralNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addGeneralNotification(for contact: SelectedContact, modelContext: ModelContext) { let notificationContent = UNMutableNotificationContent() notificationContent.title = "👋 CatchUp with \(contact.name)" notificationContent.body = generateRandomNotificationBody() @@ -59,7 +55,7 @@ struct NotificationService { scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent, modelContext: modelContext) } - func addBirthdayNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addBirthdayNotification(for contact: SelectedContact, modelContext: ModelContext) { let birthdayNotificationContent = UNMutableNotificationContent() birthdayNotificationContent.title = "🥳 Today is \(contact.name)'s birthday!" birthdayNotificationContent.body = "Be sure to CatchUp and wish them a great one!" @@ -72,7 +68,7 @@ struct NotificationService { scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent, modelContext: modelContext) } - func addAnniversaryNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addAnniversaryNotification(for contact: SelectedContact, modelContext: ModelContext) { let anniversaryNotificationContent = UNMutableNotificationContent() anniversaryNotificationContent.title = "😍 Tomorrow is \(contact.name)'s anniversary!" anniversaryNotificationContent.body = "Be sure to CatchUp and wish them the best." @@ -85,14 +81,14 @@ struct NotificationService { scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent, modelContext: modelContext) } - func checkNotificationAuthorizationStatusAndAddRequest(action: @escaping() -> Void) { - notificationCenter.getNotificationSettings { settings in + static func checkNotificationAuthorizationStatusAndAddRequest(action: @escaping() -> Void) { + UNUserNotificationCenter.current().getNotificationSettings { settings in if settings.authorizationStatus == .authorized { action() - print("Scheduled new notification(s)") } else { - notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { + print("Scheduled new notification(s)") action() } else { print("User isn't allowing notifications :(") @@ -102,7 +98,7 @@ struct NotificationService { } } - func setNotificationDateComponents(for contact: SelectedContact) -> DateComponents { + static func setNotificationDateComponents(for contact: SelectedContact) -> DateComponents { var dateComponents = DateComponents() switch contact.notification_preference { @@ -138,7 +134,7 @@ struct NotificationService { return dateComponents } - func setBirthdayDateComponents(for contact: SelectedContact) -> DateComponents { + static func setBirthdayDateComponents(for contact: SelectedContact) -> DateComponents { var birthdayDateComponents = DateComponents() let month = (contact.birthday).prefix(2) @@ -152,7 +148,7 @@ struct NotificationService { return birthdayDateComponents } - func setAnniversaryDateComponents(for contact: SelectedContact) -> DateComponents { + static func setAnniversaryDateComponents(for contact: SelectedContact) -> DateComponents { var anniversaryDateComponents = DateComponents() let formatter = DateFormatter() @@ -172,7 +168,7 @@ struct NotificationService { return anniversaryDateComponents } - func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent, modelContext: ModelContext) { + static func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent, modelContext: ModelContext) { let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let request = UNNotificationRequest(identifier: identifier.uuidString, content: content, trigger: trigger) @@ -185,12 +181,13 @@ struct NotificationService { contact.anniversary_notification_id = identifier } + print("scheduling notification for contact: \(contact.name)") try? modelContext.save() - notificationCenter.add(request) + UNUserNotificationCenter.current().add(request) } - func generateRandomNotificationBody() -> String { + static func generateRandomNotificationBody() -> String { let randomInt = Int.random(in: 0..<20) switch randomInt { @@ -239,14 +236,14 @@ struct NotificationService { } } - func updateNotificationPreference(for contact: SelectedContact, selection: Int, modelContext: ModelContext) { + static func updateNotificationPreference(for contact: SelectedContact, selection: Int, modelContext: ModelContext) { let newPreference = selection contact.notification_preference = newPreference try? modelContext.save() } - func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int, modelContext: ModelContext) { + static func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int, modelContext: ModelContext) { let newHour = hour let newMinute = minute contact.notification_preference_hour = newHour @@ -255,14 +252,14 @@ struct NotificationService { try? modelContext.save() } - func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int, modelContext: ModelContext) { + static func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int, modelContext: ModelContext) { let newWeekday = weekday contact.notification_preference_weekday = newWeekday try? modelContext.save() } - func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int, modelContext: ModelContext) { + static func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int, modelContext: ModelContext) { let customMonth = month let customDay = day let customYear = year @@ -273,7 +270,7 @@ struct NotificationService { try? modelContext.save() } - func requestAuthorizationForNotifications() { + static func requestAuthorizationForNotifications() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { print("User authorized CatchUp to send notifications") @@ -283,7 +280,7 @@ struct NotificationService { } } - func removeExistingNotifications(for contact: SelectedContact) { + static func removeExistingNotifications(for contact: SelectedContact) { removeGeneralNotification(for: contact) if contactHasBirthday(contact) { @@ -295,15 +292,29 @@ struct NotificationService { } } - func removeGeneralNotification(for contact: SelectedContact) { + static func removeGeneralNotification(for contact: SelectedContact) { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [contact.notification_identifier.uuidString]) + + UNUserNotificationCenter.current().getPendingNotificationRequests { requests in + print("Pending requests after removing existing request: \(requests.count)") + } } - func removeBirthdayNotification(for contact: SelectedContact) { + static func removeBirthdayNotification(for contact: SelectedContact) { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [contact.birthday_notification_id.uuidString]) } - func removeAnniversaryNotification(for contact: SelectedContact) { + static func removeAnniversaryNotification(for contact: SelectedContact) { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [contact.anniversary_notification_id.uuidString]) } + + static func resetNotifications(for selectedContacts: [SelectedContact], modelContext: ModelContext) { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + + for contact in selectedContacts { + NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + } + } + } } diff --git a/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift b/CatchUp-SwiftUI/Utilities/Utils.swift similarity index 50% rename from CatchUp-SwiftUI/Utilities/GeneralHelpers.swift rename to CatchUp-SwiftUI/Utilities/Utils.swift index 504a1b8..68bbddf 100644 --- a/CatchUp-SwiftUI/Utilities/GeneralHelpers.swift +++ b/CatchUp-SwiftUI/Utilities/Utils.swift @@ -1,5 +1,5 @@ // -// GeneralHelpers.swift +// Utils.swift // CatchUp-SwiftUI // // Created by Ryan Token on 4/28/20. @@ -11,12 +11,12 @@ import Foundation import CoreData import UserNotifications -struct GeneralHelpers { - func clearNotificationBadge() { +class Utils { + static func clearNotificationBadge() { UNUserNotificationCenter.current().setBadgeCount(0) } - func getCurrentAppVersion() -> String { + static func getCurrentAppVersion() -> String { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] let version = (appVersion as! String) @@ -24,4 +24,17 @@ struct GeneralHelpers { return version } + static func updateIsMajor() -> Bool { + let version = getCurrentAppVersion() + if version.suffix(2) == ".0" { + return true + } else { + return false + } + } + + static func fetchAvailableIAPs() { + print("fetching IAPs") + IAPService.shared.fetchAvailableProducts() + } } From ca42870266d05c49bc9e480062952cd303196fb5 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 10 Mar 2024 18:57:39 -0600 Subject: [PATCH 12/46] File reorganization --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 34 ++++++++----------- .../Resources/PhoneNumberMetadata.json | 1 - .../ContactPickerDelegate.swift} | 4 +-- CatchUp-SwiftUI/{ => Views}/AboutScreen.swift | 0 .../{ => Views}/DetailScreen.swift | 0 CatchUp-SwiftUI/{ => Views}/HomeScreen.swift | 2 +- .../{ => Views}/PreferenceScreen.swift | 0 .../Supporting Views/ContactPhoto.swift | 0 .../Supporting Views/GradientView.swift | 0 .../{ => Views}/UpdatesScreen.swift | 0 10 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 CatchUp-SwiftUI/Resources/PhoneNumberMetadata.json rename CatchUp-SwiftUI/{Supporting Views/ContactPicker.swift => Services/ContactPickerDelegate.swift} (83%) rename CatchUp-SwiftUI/{ => Views}/AboutScreen.swift (100%) rename CatchUp-SwiftUI/{ => Views}/DetailScreen.swift (100%) rename CatchUp-SwiftUI/{ => Views}/HomeScreen.swift (98%) rename CatchUp-SwiftUI/{ => Views}/PreferenceScreen.swift (100%) rename CatchUp-SwiftUI/{ => Views}/Supporting Views/ContactPhoto.swift (100%) rename CatchUp-SwiftUI/{ => Views}/Supporting Views/GradientView.swift (100%) rename CatchUp-SwiftUI/{ => Views}/UpdatesScreen.swift (100%) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index dc70b34..6c071fa 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -8,11 +8,10 @@ /* Begin PBXBuildFile section */ 144DBE22245C8282008FDBB6 /* PhoneNumberKit in Frameworks */ = {isa = PBXBuildFile; productRef = 144DBE21245C8282008FDBB6 /* PhoneNumberKit */; }; - 14ACAC86245E52A40091AE90 /* PhoneNumberMetadata.json in Resources */ = {isa = PBXBuildFile; fileRef = 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */; }; 14BE3AD324593556004F72DE /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD224593556004F72DE /* Utils.swift */; }; 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; - F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */; }; + F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -31,11 +30,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PhoneNumberMetadata.json; sourceTree = ""; }; 14BE3AD224593556004F72DE /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesScreen.swift; sourceTree = ""; }; F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; - F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicker.swift; sourceTree = ""; }; + F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerDelegate.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -68,12 +66,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 144DBE23245C864F008FDBB6 /* Resources */ = { + F4118B472B9E8E10001BC8C7 /* Views */ = { isa = PBXGroup; children = ( - 14ACAC85245E52A40091AE90 /* PhoneNumberMetadata.json */, + F48E37F822C455C3008B0B8B /* HomeScreen.swift */, + F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */, + F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */, + F4AD59AF244CA12C00296568 /* AboutScreen.swift */, + 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */, + F4A9B8532442A162001D8C55 /* Supporting Views */, ); - path = Resources; + path = Views; sourceTree = ""; }; F4871DA2244FC44800925392 /* Utilities */ = { @@ -110,18 +113,12 @@ F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */, F48E37FA22C455CB008B0B8B /* Assets.xcassets */, F48E380222C455CB008B0B8B /* Info.plist */, - 144DBE23245C864F008FDBB6 /* Resources */, - F4AD59B1244CC9C100296568 /* Extensions */, + F4118B472B9E8E10001BC8C7 /* Views */, + F4BAD4292B94E41F0009CD50 /* Data */, F4A9B85024429E40001D8C55 /* Services */, F4871DA2244FC44800925392 /* Utilities */, - F4A9B8532442A162001D8C55 /* Supporting Views */, - F4BAD4292B94E41F0009CD50 /* Data */, + F4AD59B1244CC9C100296568 /* Extensions */, F48E37FC22C455CB008B0B8B /* Preview Content */, - F48E37F822C455C3008B0B8B /* HomeScreen.swift */, - F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */, - F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */, - F4AD59AF244CA12C00296568 /* AboutScreen.swift */, - 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */, ); path = "CatchUp-SwiftUI"; sourceTree = ""; @@ -138,6 +135,7 @@ isa = PBXGroup; children = ( F4AD59AD244C9FF600296568 /* IAPService.swift */, + F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */, ); path = Services; sourceTree = ""; @@ -147,7 +145,6 @@ children = ( F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, F4A9B8562442A1F7001D8C55 /* GradientView.swift */, - F4118B442B9E68AE001BC8C7 /* ContactPicker.swift */, ); path = "Supporting Views"; sourceTree = ""; @@ -234,7 +231,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 14ACAC86245E52A40091AE90 /* PhoneNumberMetadata.json in Resources */, F48E37FE22C455CB008B0B8B /* Preview Assets.xcassets in Resources */, F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */, ); @@ -263,7 +259,7 @@ F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, - F4118B452B9E68AE001BC8C7 /* ContactPicker.swift in Sources */, + F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CatchUp-SwiftUI/Resources/PhoneNumberMetadata.json b/CatchUp-SwiftUI/Resources/PhoneNumberMetadata.json deleted file mode 100644 index a6df265..0000000 --- a/CatchUp-SwiftUI/Resources/PhoneNumberMetadata.json +++ /dev/null @@ -1 +0,0 @@ -{ "phoneNumberMetadata": {"territories": {"territory": [{"id": "AC","countryCode": "247","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "(?:[01589]\\d|[46])\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "62889","nationalNumberPattern": "6[2-467]\\d{3}"},"mobile": {"possibleLengths": {"national": "5"},"exampleNumber": "40123","nationalNumberPattern": "4\\d{4}"},"uan": {"possibleLengths": {"national": "6"},"exampleNumber": "542011","nationalNumberPattern": "(?:0[1-9]|[1589]\\d)\\d{4}"}},{"id": "AD","countryCode": "376","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "[136-9]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "6","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:1|6\\d)\\d{7}|[136-9]\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "1800\\d{4}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "712345","nationalNumberPattern": "[78]\\d{5}"},"mobile": {"possibleLengths": {"national": "6,9"},"exampleNumber": "312345","nationalNumberPattern": "690\\d{6}|[36]\\d{5}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "18001234","nationalNumberPattern": "180[02]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "6"},"exampleNumber": "912345","nationalNumberPattern": "[19]\\d{5}"}},{"id": "AE","countryCode": "971","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2,9})","leadingDigits": "60|8","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[236]|[479][2-8]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d)(\\d{5})","leadingDigits": "[479]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[4-7]\\d|9[0-689])\\d{7}|800\\d{2,9}|[2-4679]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "22345678","nationalNumberPattern": "[2-4679][2-8]\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "501234567","nationalNumberPattern": "5[024-68]\\d{7}"},"tollFree": {"possibleLengths": {"national": "[5-12]"},"exampleNumber": "800123456","nationalNumberPattern": "400\\d{6}|800\\d{2,9}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900234567","nationalNumberPattern": "900[02]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "700012345","nationalNumberPattern": "700[05]\\d{5}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "600212345","nationalNumberPattern": "600[25]\\d{5}"}},{"id": "AF","countryCode": "93","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[1-9]","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-7]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2-7]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "234567890","nationalNumberPattern": "(?:[25][0-8]|[34][0-4]|6[0-5])[2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "701234567","nationalNumberPattern": "7\\d{8}"}},{"id": "AG","countryCode": "1","leadingDigits": "268","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([457]\\d{6})$","nationalPrefixTransformRule": "268$1","generalDesc": {"nationalNumberPattern": "(?:268|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2684601234","nationalNumberPattern": "268(?:4(?:6[0-38]|84)|56[0-2])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2684641234","nationalNumberPattern": "268(?:464|7(?:1[3-9]|2\\d|3[246]|64|[78][0-689]))\\d{4}"},"pager": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2684061234","nationalNumberPattern": "26840[69]\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"voip": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2684801234","nationalNumberPattern": "26848[01]\\d{4}"}},{"id": "AI","countryCode": "1","leadingDigits": "264","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2457]\\d{6})$","nationalPrefixTransformRule": "264$1","generalDesc": {"nationalNumberPattern": "(?:264|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2644612345","nationalNumberPattern": "2644(?:6[12]|9[78])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2642351234","nationalNumberPattern": "264(?:235|476|5(?:3[6-9]|8[1-4])|7(?:29|72))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "AL","countryCode": "355","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "80|9","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "4[2-6]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2358][2-5]|4","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[23578]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:700\\d\\d|900)\\d{3}|8\\d{5,7}|(?:[2-5]|6\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "[5-7]"},"exampleNumber": "22345678","nationalNumberPattern": "(?:[2358](?:[16-9]\\d[2-9]|[2-5][2-9]\\d)|4(?:[2-57-9][2-9]|6\\d)\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "672123456","nationalNumberPattern": "6(?:[78][2-9]|9\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}"},"premiumRate": {"possibleLengths": {"national": "6"},"exampleNumber": "900123","nationalNumberPattern": "900[1-9]\\d\\d"},"sharedCost": {"possibleLengths": {"national": "6"},"exampleNumber": "808123","nationalNumberPattern": "808[1-9]\\d\\d"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "70021234","nationalNumberPattern": "700[2-9]\\d{4}"}},{"id": "AM","countryCode": "374","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "[89]0","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2|3[12]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "1|47","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-9]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:[1-489]\\d|55|60|77)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "5,6"},"exampleNumber": "10123456","nationalNumberPattern": "(?:(?:1[0-25]|47)\\d|2(?:2[2-46]|3[1-8]|4[2-69]|5[2-7]|6[1-9]|8[1-7])|3[12]2)\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "77123456","nationalNumberPattern": "(?:33|4[1349]|55|77|88|9[13-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "90[016]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80112345","nationalNumberPattern": "80[1-4]\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "60271234","nationalNumberPattern": "60(?:2[78]|3[5-9]|4[02-9]|5[0-46-9]|[6-8]\\d|90)\\d{4}"}},{"id": "AO","countryCode": "244","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[29]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[29]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "222123456","nationalNumberPattern": "2\\d(?:[0134][25-9]|[25-9]\\d)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "923123456","nationalNumberPattern": "9[1-49]\\d{7}"}},{"id": "AR","countryCode": "54","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[0-24-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:[069]|1[1568]|2[15]|3[145]|4[13]|5[14-8]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))15)?","nationalPrefixTransformRule": "9$1","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})","leadingDigits": "[09]|1(?:[02]|1[02-5])","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{4})","leadingDigits": "[2-8]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-8]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "2[0-8]|[3-8]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["2(?:2[024-9]|3[0-59]|47|6[245]|9[02-8])|3(?:3[28]|4[03-9]|5[2-46-8]|7[1-578]|8[2-9])","2(?:[23]02|6(?:[25]|4[6-8])|9(?:[02356]|4[02568]|72|8[23]))|3(?:3[28]|4(?:[04679]|3[5-8]|5[4-68]|8[2379])|5(?:[2467]|3[237]|8[2-5])|7[1-578]|8(?:[2469]|3[2578]|5[4-8]|7[36-8]|8[5-8]))|2(?:2[24-9]|3[1-59]|47)","2(?:[23]02|6(?:[25]|4(?:64|[78]))|9(?:[02356]|4(?:[0268]|5[2-6])|72|8[23]))|3(?:3[28]|4(?:[04679]|3[78]|5(?:4[46]|8)|8[2379])|5(?:[2467]|3[237]|8[23])|7[1-578]|8(?:[2469]|3[278]|5[56][46]|86[3-6]))|2(?:2[24-9]|3[1-59]|47)|38(?:[58][78]|7[378])|3(?:4[35][56]|58[45]|8(?:[38]5|54|76))[4-6]","2(?:[23]02|6(?:[25]|4(?:64|[78]))|9(?:[02356]|4(?:[0268]|5[2-6])|72|8[23]))|3(?:3[28]|4(?:[04679]|3(?:5(?:4[0-25689]|[56])|[78])|58|8[2379])|5(?:[2467]|3[237]|8(?:[23]|4(?:[45]|60)|5(?:4[0-39]|5|64)))|7[1-578]|8(?:[2469]|3[278]|54(?:4|5[13-7]|6[89])|86[3-6]))|2(?:2[24-9]|3[1-59]|47)|38(?:[58][78]|7[378])|3(?:454|85[56])[46]|3(?:4(?:36|5[56])|8(?:[38]5|76))[4-6]"],"format": "$1 $2-$3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "1","format": "$1 $2-$3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[68]","format": "$1-$2-$3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[23]","format": "$1 $2-$3"},{"pattern": "(\\d)(\\d{4})(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["9(?:2[2-469]|3[3-578])","9(?:2(?:2[024-9]|3[0-59]|47|6[245]|9[02-8])|3(?:3[28]|4[03-9]|5[2-46-8]|7[1-578]|8[2-9]))","9(?:2(?:[23]02|6(?:[25]|4[6-8])|9(?:[02356]|4[02568]|72|8[23]))|3(?:3[28]|4(?:[04679]|3[5-8]|5[4-68]|8[2379])|5(?:[2467]|3[237]|8[2-5])|7[1-578]|8(?:[2469]|3[2578]|5[4-8]|7[36-8]|8[5-8])))|92(?:2[24-9]|3[1-59]|47)","9(?:2(?:[23]02|6(?:[25]|4(?:64|[78]))|9(?:[02356]|4(?:[0268]|5[2-6])|72|8[23]))|3(?:3[28]|4(?:[04679]|3[78]|5(?:4[46]|8)|8[2379])|5(?:[2467]|3[237]|8[23])|7[1-578]|8(?:[2469]|3[278]|5(?:[56][46]|[78])|7[378]|8(?:6[3-6]|[78]))))|92(?:2[24-9]|3[1-59]|47)|93(?:4[35][56]|58[45]|8(?:[38]5|54|76))[4-6]","9(?:2(?:[23]02|6(?:[25]|4(?:64|[78]))|9(?:[02356]|4(?:[0268]|5[2-6])|72|8[23]))|3(?:3[28]|4(?:[04679]|3(?:5(?:4[0-25689]|[56])|[78])|5(?:4[46]|8)|8[2379])|5(?:[2467]|3[237]|8(?:[23]|4(?:[45]|60)|5(?:4[0-39]|5|64)))|7[1-578]|8(?:[2469]|3[278]|5(?:4(?:4|5[13-7]|6[89])|[56][46]|[78])|7[378]|8(?:6[3-6]|[78]))))|92(?:2[24-9]|3[1-59]|47)|93(?:4(?:36|5[56])|8(?:[38]5|76))[4-6]"],"format": "$2 15-$3-$4","intlFormat": "$1 $2 $3-$4"},{"pattern": "(\\d)(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "91","format": "$2 15-$3-$4","intlFormat": "$1 $2 $3-$4"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$2 15-$3-$4","intlFormat": "$1 $2 $3-$4"}]},"generalDesc": {"nationalNumberPattern": "11\\d{8}|(?:[2368]|9\\d)\\d{9}"},"noInternationalDialling": {"possibleLengths": {"national": "10"},"nationalNumberPattern": "810\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "[6-8]"},"exampleNumber": "1123456789","nationalNumberPattern": "(?:2954|3(?:777|865))[2-8]\\d{5}|3(?:7(?:1[15]|81)|8(?:21|4[16]|69|9[12]))[46]\\d{5}|(?:(?:11[1-8]|670)\\d|2(?:2(?:1[2-6]|3[3-6])|(?:3[06]|49)4|6(?:04|1[2-7]|4[4-6])|9(?:[17][4-6]|9[3-6]))|3(?:(?:36|64)4|4(?:1[2-7]|[235][4-6]|84)|5(?:1[2-8]|[38][4-6])|8(?:1[2-6]|[58][3-6]|7[24-6])))\\d{6}|(?:2(?:284|657|9(?:20|66))|3(?:4(?:8[27]|92)|755|878))[2-7]\\d{5}|(?:2(?:[28]0|37|6[36]|9[48])|3(?:62|7[069]|8[03]))[45]\\d{6}|(?:2(?:2(?:2[59]|44|52)|3(?:26|4[24])|473|9(?:[07]2|2[26]|34|46))|3327)[45]\\d{5}|(?:2(?:(?:26|62)2|3(?:02|2[03])|477|9(?:42|83))|3(?:4(?:[47]6|62|89)|5(?:41|64)|873))[2-6]\\d{5}|2(?:2(?:21|4[23]|6[145]|7[1-4]|8[356]|9[267])|3(?:16|3[13-8]|43|5[346-8]|9[3-5])|475|6(?:2[46]|4[78]|5[1568])|9(?:03|2[1457-9]|3[1356]|4[08]|[56][23]|82))4\\d{5}|(?:2(?:2(?:57|81)|3(?:24|46|92)|9(?:01|23|64))|3(?:329|4(?:42|71)|5(?:25|37|4[347]|71)|7(?:18|5[17])|888))[3-6]\\d{5}|(?:2(?:2(?:02|2[3467]|4[156]|5[45]|6[6-8]|91)|3(?:1[47]|[24]5|5[25]|96)|47[48]|625|932)|3(?:38[2578]|4(?:0[0-24-9]|3[78]|4[457]|58|6[03-9]|72|83|9[136-8])|5(?:2[124]|[368][23]|4[2689]|7[2-6])|7(?:16|2[15]|3[145]|4[13]|5[468]|7[2-5]|8[26])|8(?:2[5-7]|3[278]|4[3-5]|5[78]|6[1-378]|[78]7|94)))[4-6]\\d{5}"},"mobile": {"possibleLengths": {"national": "10,11","localOnly": "[6-8]"},"exampleNumber": "91123456789","nationalNumberPattern": "9(?:2954|3(?:777|865))[2-8]\\d{5}|93(?:7(?:1[15]|81)|8(?:21|4[16]|69|9[12]))[46]\\d{5}|(?:675\\d|9(?:11[1-8]\\d|2(?:2(?:1[2-6]|3[3-6])|(?:3[06]|49)4|6(?:04|1[2-7]|4[4-6])|9(?:[17][4-6]|9[3-6]))|3(?:(?:36|64)4|4(?:1[2-7]|[235][4-6]|84)|5(?:1[2-8]|[38][4-6])|8(?:1[2-6]|[58][3-6]|7[24-6]))))\\d{6}|9(?:2(?:284|657|9(?:20|66))|3(?:4(?:8[27]|92)|755|878))[2-7]\\d{5}|9(?:2(?:[28]0|37|6[36]|9[48])|3(?:62|7[069]|8[03]))[45]\\d{6}|9(?:2(?:2(?:2[59]|44|52)|3(?:26|4[24])|473|9(?:[07]2|2[26]|34|46))|3327)[45]\\d{5}|9(?:2(?:(?:26|62)2|3(?:02|2[03])|477|9(?:42|83))|3(?:4(?:[47]6|62|89)|5(?:41|64)|873))[2-6]\\d{5}|92(?:2(?:21|4[23]|6[145]|7[1-4]|8[356]|9[267])|3(?:16|3[13-8]|43|5[346-8]|9[3-5])|475|6(?:2[46]|4[78]|5[1568])|9(?:03|2[1457-9]|3[1356]|4[08]|[56][23]|82))4\\d{5}|9(?:2(?:2(?:57|81)|3(?:24|46|92)|9(?:01|23|64))|3(?:329|4(?:42|71)|5(?:25|37|4[347]|71)|7(?:18|5[17])|888))[3-6]\\d{5}|9(?:2(?:2(?:02|2[3467]|4[156]|5[45]|6[6-8]|91)|3(?:1[47]|[24]5|5[25]|96)|47[48]|625|932)|3(?:38[2578]|4(?:0[0-24-9]|3[78]|4[457]|58|6[03-9]|72|83|9[136-8])|5(?:2[124]|[368][23]|4[2689]|7[2-6])|7(?:16|2[15]|3[145]|4[13]|5[468]|7[2-5]|8[26])|8(?:2[5-7]|3[278]|4[3-5]|5[78]|6[1-378]|[78]7|94)))[4-6]\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "6001234567","nationalNumberPattern": "60[04579]\\d{7}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "8101234567","nationalNumberPattern": "810\\d{7}"}},{"id": "AS","countryCode": "1","leadingDigits": "684","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([267]\\d{6})$","nationalPrefixTransformRule": "684$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|684|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6846221234","nationalNumberPattern": "6846(?:22|33|44|55|77|88|9[19])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6847331234","nationalNumberPattern": "684(?:2(?:5[2468]|72)|7(?:3[13]|70))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "AT","countryCode": "43","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3,12})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1(?:11|[2-9])","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "517","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5[079]","format": "$1 $2"},{"pattern": "(\\d{6})","leadingDigits": "1","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3,10})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:31|4)6|51|6(?:5[0-3579]|[6-9])|7(?:20|32|8)|[89]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{3,9})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-467]|5[2-6]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{3,12}|2\\d{6,12}|43(?:(?:0\\d|5[02-9])\\d{3,9}|2\\d{4,5}|[3467]\\d{4}|8\\d{4,6}|9\\d{4,7})|5\\d{4,12}|8\\d{7,12}|9\\d{8,12}|(?:[367]\\d|4[0-24-9])\\d{4,11}"},"fixedLine": {"possibleLengths": {"national": "[4-13]","localOnly": "3"},"exampleNumber": "1234567890","nationalNumberPattern": "1(?:11\\d|[2-9]\\d{3,11})|(?:316|463|(?:51|66|73)2)\\d{3,10}|(?:2(?:1[467]|2[13-8]|5[2357]|6[1-46-8]|7[1-8]|8[124-7]|9[1458])|3(?:1[1-578]|3[23568]|4[5-7]|5[1378]|6[1-38]|8[3-68])|4(?:2[1-8]|35|7[1368]|8[2457])|5(?:2[1-8]|3[357]|4[147]|5[12578]|6[37])|6(?:13|2[1-47]|4[135-8]|5[468])|7(?:2[1-8]|35|4[13478]|5[68]|6[16-8]|7[1-6]|9[45]))\\d{4,10}"},"mobile": {"possibleLengths": {"national": "[7-13]"},"exampleNumber": "664123456","nationalNumberPattern": "6(?:5[0-3579]|6[013-9]|[7-9]\\d)\\d{4,10}"},"tollFree": {"possibleLengths": {"national": "[9-13]"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6,10}"},"premiumRate": {"possibleLengths": {"national": "[9-13]"},"exampleNumber": "900123456","nationalNumberPattern": "9(?:0[01]|3[019])\\d{6,10}"},"sharedCost": {"possibleLengths": {"national": "[8-13]"},"exampleNumber": "810123456","nationalNumberPattern": "8(?:10|2[018])\\d{6,10}|828\\d{5}"},"voip": {"possibleLengths": {"national": "[5-13]"},"exampleNumber": "780123456","nationalNumberPattern": "5(?:0[1-9]|17|[79]\\d)\\d{2,10}|7[28]0\\d{6,10}"}},{"id": "AU","mainCountryForCode": "true","countryCode": "61","preferredInternationalPrefix": "0011","internationalPrefix": "001[14-689]|14(?:1[14]|34|4[17]|[56]6|7[47]|88)0011","nationalPrefix": "0","nationalPrefixForParsing": "0|(183[12])","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "16","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "13","format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "19","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": ["180","1802"],"format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{3,4})","leadingDigits": "19","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3})(\\d{2,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "16","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "14|[45]","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","carrierCodeFormattingRule": "$CC ($FG)","leadingDigits": "[2378]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "1(?:30|[89])","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1(?:[0-79]\\d{7,8}|8[0-24-9]\\d{7})|(?:[2-478]\\d\\d|550)\\d{6}|1\\d{4,7}"},"noInternationalDialling": {"possibleLengths": {"national": "[6-8],10"},"nationalNumberPattern": "1[38]00\\d{6}|1(?:345[0-4]|802)\\d{3}|13\\d{4}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "8"},"exampleNumber": "212345678","nationalNumberPattern": "(?:[237]\\d{5}|8(?:51(?:0(?:0[03-9]|[1247]\\d|3[2-9]|5[0-8]|6[1-9]|8[0-6])|1(?:1[69]|[23]\\d|4[0-4]))|(?:[6-8]\\d{3}|9(?:[02-9]\\d\\d|1(?:[0-57-9]\\d|6[0135-9])))\\d))\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "412345678","nationalNumberPattern": "483[0-3]\\d{5}|4(?:[0-3]\\d|4[047-9]|5[0-25-9]|6[06-9]|7[02-9]|8[0-2457-9]|9[0-27-9])\\d{6}"},"pager": {"possibleLengths": {"national": "[5-9]"},"exampleNumber": "1612345","nationalNumberPattern": "16\\d{3,7}"},"tollFree": {"possibleLengths": {"national": "7,10"},"exampleNumber": "1800123456","nationalNumberPattern": "180(?:0\\d{3}|2)\\d{3}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1900123456","nationalNumberPattern": "190[0-26]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "6,8,10"},"exampleNumber": "1300123456","nationalNumberPattern": "13(?:00\\d{3}|45[0-4])\\d{3}|13\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "550123456","nationalNumberPattern": "(?:14(?:5(?:1[0458]|[23][458])|71\\d)|550\\d\\d)\\d{4}"}},{"id": "AW","countryCode": "297","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[25-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[25-79]\\d\\d|800)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "5212345","nationalNumberPattern": "5(?:2\\d|8[1-9])\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "5601234","nationalNumberPattern": "(?:290|5[69]\\d|6(?:[03]0|22|4[0-2]|[69]\\d)|7(?:[34]\\d|7[07])|9(?:6[45]|9[4-8]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9001234","nationalNumberPattern": "900\\d{4}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "5011234","nationalNumberPattern": "(?:28\\d|501)\\d{4}"}},{"id": "AX","countryCode": "358","leadingDigits": "18","preferredInternationalPrefix": "00","internationalPrefix": "00|99(?:[01469]|5(?:[14]1|3[23]|5[59]|77|88|9[09]))","nationalPrefix": "0","generalDesc": {"nationalNumberPattern": "2\\d{4,9}|35\\d{4,5}|(?:60\\d\\d|800)\\d{4,6}|7\\d{5,11}|(?:[14]\\d|3[0-46-9]|50)\\d{4,8}"},"fixedLine": {"possibleLengths": {"national": "[6-9]"},"exampleNumber": "181234567","nationalNumberPattern": "18[1-8]\\d{3,6}"},"mobile": {"possibleLengths": {"national": "[6-10]"},"exampleNumber": "412345678","nationalNumberPattern": "(?:4[0-8]|50)\\d{4,8}"},"tollFree": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{4,6}"},"premiumRate": {"possibleLengths": {"national": "8,9"},"exampleNumber": "600123456","nationalNumberPattern": "[67]00\\d{5,6}"},"uan": {"possibleLengths": {"national": "[5-12]"},"exampleNumber": "10112345","nationalNumberPattern": "20\\d{4,8}|60[12]\\d{5,6}|7(?:099\\d{4,5}|5[03-9]\\d{3,7})|20[2-59]\\d\\d|(?:606|7(?:0[78]|1|3\\d))\\d{7}|(?:10|29|3[09]|70[1-5]\\d)\\d{4,8}"}},{"id": "AZ","countryCode": "994","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "[1-9]","format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "90","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": ["[12]|365","[12]|365","[12]|365(?:[0-46-9]|5[0-35-9])"],"format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "365\\d{6}|(?:[124579]\\d|60|88)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "123123456","nationalNumberPattern": "365(?:[0-46-9]\\d|5[0-35-9])\\d{4}|(?:1[28]\\d|2(?:[045]2|1[24]|2[2-4]|33|6[23]))\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "401234567","nationalNumberPattern": "(?:36554|99[2-9]\\d\\d)\\d{4}|(?:4[04]|5[015]|60|7[07])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "881234567","nationalNumberPattern": "88\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900200123","nationalNumberPattern": "900200\\d{3}"}},{"id": "BA","countryCode": "387","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "[2-9]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6[1-3]|[7-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-5]|6[56]","format": "$1 $2-$3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "6\\d{8}|(?:[35689]\\d|49|70)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6"},"exampleNumber": "30212345","nationalNumberPattern": "(?:3(?:[05-79][2-9]|1[4579]|[23][24-9]|4[2-4689]|8[2457-9])|49[2-579]|5(?:0[2-49]|[13][2-9]|[268][2-4679]|4[4689]|5[2-79]|7[2-69]|9[2-4689]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8,9"},"exampleNumber": "61123456","nationalNumberPattern": "6040[0-4]\\d{4}|6(?:03|[1-356]|44|7\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "8[08]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "9[0246]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "82123456","nationalNumberPattern": "8[12]\\d{6}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "70341234","nationalNumberPattern": "70(?:3[0146]|[56]0)\\d{4}"}},{"id": "BB","countryCode": "1","leadingDigits": "246","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-9]\\d{6})$","nationalPrefixTransformRule": "246$1","generalDesc": {"nationalNumberPattern": "(?:246|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2464123456","nationalNumberPattern": "246(?:2(?:2[78]|7[0-4])|4(?:1[024-6]|2\\d|3[2-9])|5(?:20|[34]\\d|54|7[1-3])|6(?:2\\d|38)|7[35]7|9(?:1[89]|63))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2462501234","nationalNumberPattern": "246(?:2(?:[356]\\d|4[0-57-9]|8[0-79])|45\\d|69[5-7]|8(?:[2-5]\\d|83))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "9002123456","nationalNumberPattern": "(?:246976|900[2-9]\\d\\d)\\d{4}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"voip": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2463101234","nationalNumberPattern": "24631\\d{5}"},"uan": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2464301234","nationalNumberPattern": "246(?:292|367|4(?:1[7-9]|3[01]|44|67)|7(?:36|53))\\d{4}"}},{"id": "BD","countryCode": "880","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{4,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "31[5-7]|[459]1","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{3,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3(?:[67]|8[013-9])|4(?:6[168]|7|[89][18])|5(?:6[128]|9)|6(?:28|4[14]|5)|7[2-589]|8(?:0[014-9]|[12])|9[358]|(?:3[2-5]|4[235]|5[2-578]|6[0389]|76|8[3-7]|9[24])1|(?:44|66)[01346-9]","format": "$1-$2"},{"pattern": "(\\d{4})(\\d{3,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[13-9]","format": "$1-$2"},{"pattern": "(\\d)(\\d{7,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1-$2"}]},"generalDesc": {"nationalNumberPattern": "[13469]\\d{9}|8[0-79]\\d{7,8}|[2-7]\\d{8}|[2-9]\\d{7}|[3-689]\\d{6}|[57-9]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "[6-10]"},"exampleNumber": "27111234","nationalNumberPattern": "(?:3(?:03[56]|224)|4(?:22[25]|653))\\d{3,4}|(?:4(?:31\\d\\d|[46]23)|5(?:222|32[37]))\\d{3}(?:\\d{2})?|(?:3(?:42[47]|529|823)|4(?:027|525|658)|(?:56|73)2|6257|9[35]1)\\d{3}|(?:3(?:02[348]|22[35]|324|422)|4(?:22[67]|32[236-9]|6(?:2[46]|5[57])|953)|5526|6(?:024|6655)|81)\\d{4,5}|(?:2(?:7(?:1[0-267]|2[0-289]|3[0-29]|4[01]|5[1-3]|6[013]|7[0178]|91)|8(?:0[125]|1[1-6]|2[0157-9]|3[1-69]|41|6[1-35]|7[1-5]|8[1-8]|9[0-6])|9(?:0[0-2]|1[0-4]|2[568]|3[3-6]|5[5-7]|6[01367]|7[15]|8[014-9]))|3(?:0(?:2[025-79]|3[2-4])|22[12]|32[2356]|824)|4(?:02[09]|22[348]|32[045]|523|6(?:27|54))|666(?:22|53)|8(?:4[12]|[5-7]2)|9(?:[024]2|81))\\d{4}|(?:2[45]\\d\\d|3(?:1(?:2[5-7]|[5-7])|425|822)|4(?:033|1\\d|[257]1|332|4(?:2[246]|5[25])|6(?:25|56|62)|8(?:23|54)|92[2-5])|5(?:02[03489]|22[457]|32[569]|42[46]|6(?:[18]|53)|724|826)|6(?:023|2(?:2[2-5]|5[3-5]|8)|32[3478]|42[34]|52[47]|6(?:[18]|6(?:2[34]|5[24]))|[78]2[2-5]|92[2-6])|7(?:02|21\\d|[3-589]1|6[12]|72[24])|8(?:0|217|3[12]|[5-7]1)|9[24]1)\\d{5}|(?:(?:3[2-8]|5[2-57-9]|6[03-589])1|4[4689][18])\\d{5}|[59]1\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "1812345678","nationalNumberPattern": "(?:1[13-9]\\d|644)\\d{7}|(?:3[78]|44|66)[02-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "80[03]\\d{7}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "9604123456","nationalNumberPattern": "96(?:0[469]|1[0-47]|3[389]|6[69]|7[78])\\d{6}"}},{"id": "BE","countryCode": "32","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:80|9)0","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[239]|4[23]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[15-8]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "4","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "4\\d{8}|[1-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "12345678","nationalNumberPattern": "80[2-8]\\d{5}|(?:1[0-69]|[23][2-8]|4[23]|5\\d|6[013-57-9]|71|8[1-79]|9[2-4])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "470123456","nationalNumberPattern": "4[5-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800[1-9]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "(?:70(?:2[0-57]|3[0457]|44|69|7[0579])|90(?:0[0-35-8]|1[36]|2[0-3568]|3[0135689]|4[2-68]|5[1-68]|6[0-378]|7[23568]|9[34679]))\\d{4}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "78791234","nationalNumberPattern": "7879\\d{4}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "78102345","nationalNumberPattern": "78(?:0[57]|1[0458]|2[25]|3[5-8]|48|[56]0|7[078])\\d{4}"}},{"id": "BF","countryCode": "226","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[025-7]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[025-7]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "20491234","nationalNumberPattern": "2(?:0(?:49|5[23]|6[56]|9[016-9])|4(?:4[569]|5[4-6]|6[56]|7[0179])|5(?:[34]\\d|50|6[5-7]))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "70123456","nationalNumberPattern": "(?:0[17]|5[1-8]|[67]\\d)\\d{6}"}},{"id": "BG","countryCode": "359","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{6})","leadingDigits": "1","format": "$1","intlFormat": "NA"},{"pattern": "(\\d)(\\d)(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "43[1-6]|70[1-9]","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[356]|4[124-7]|7[1-9]|8[1-6]|9[1-7]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:70|8)0","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "43[1-7]|7","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[48]|9[08]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2-7]\\d{6,7}|[89]\\d{6,8}|2\\d{5}"},"fixedLine": {"possibleLengths": {"national": "[6-8]","localOnly": "4,5"},"exampleNumber": "2123456","nationalNumberPattern": "2\\d{5,7}|(?:43[1-6]|70[1-9])\\d{4,5}|(?:[36]\\d|4[124-7]|[57][1-9]|8[1-6]|9[1-7])\\d{5,6}"},"mobile": {"possibleLengths": {"national": "8,9"},"exampleNumber": "48123456","nationalNumberPattern": "43[07-9]\\d{5}|(?:48|8[7-9]\\d|9(?:8\\d|9[69]))\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "90\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "700\\d{5}"}},{"id": "BH","countryCode": "973","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[13679]|8[047]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[136-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "17001234","nationalNumberPattern": "(?:1(?:3[1356]|6[0156]|7\\d)\\d|6(?:1[16]\\d|500|6(?:0\\d|3[12]|44|7[7-9]|88)|9[69][69])|7(?:1(?:11|78)|7\\d\\d))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "36001234","nationalNumberPattern": "(?:3(?:[1-79]\\d|8[0-47-9])\\d|6(?:3(?:00|33|6[16])|6(?:3[03-9]|[69]\\d|7[0-6])))\\d{4}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "80\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "(?:87|9[014578])\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "84123456","nationalNumberPattern": "84\\d{6}"}},{"id": "BI","countryCode": "257","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2367]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:[267]\\d|31)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22201234","nationalNumberPattern": "22\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "79561234","nationalNumberPattern": "(?:29|31|6[1289]|7[125-9])\\d{6}"}},{"id": "BJ","countryCode": "229","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2689]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[2689]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "20211234","nationalNumberPattern": "2(?:02|1[037]|2[45]|3[68])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "90011234","nationalNumberPattern": "(?:6\\d|9[013-9])\\d{6}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "85751234","nationalNumberPattern": "857[58]\\d{4}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "81123456","nationalNumberPattern": "81\\d{6}"}},{"id": "BL","countryCode": "590","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:590|69\\d|976)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "590271234","nationalNumberPattern": "590(?:2[7-9]|5[12]|87)\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "690001234","nationalNumberPattern": "69(?:0\\d\\d|1(?:2[29]|3[0-5]))\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "976012345","nationalNumberPattern": "976[01]\\d{5}"}},{"id": "BM","countryCode": "1","leadingDigits": "441","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-8]\\d{6})$","nationalPrefixTransformRule": "441$1","generalDesc": {"nationalNumberPattern": "(?:441|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "4412345678","nationalNumberPattern": "441(?:2(?:02|23|[3479]\\d|61)|[46]\\d\\d|5(?:4\\d|60|89)|824)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "4413701234","nationalNumberPattern": "441(?:[37]\\d|5[0-39])\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "BN","countryCode": "673","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-578]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[2-578]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2345678","nationalNumberPattern": "22[0-7]\\d{4}|(?:2[013-9]|[34]\\d|5[0-25-9])\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7123456","nationalNumberPattern": "(?:22[89]|[78]\\d\\d)\\d{4}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "5345678","nationalNumberPattern": "5[34]\\d{5}"}},{"id": "BO","countryCode": "591","internationalPrefix": "00(?:1\\d)?","nationalPrefix": "0","nationalPrefixForParsing": "0(1\\d)?","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{7})","carrierCodeFormattingRule": "$NP$CC $FG","leadingDigits": "[23]|4[46]","format": "$1 $2"},{"pattern": "(\\d{8})","carrierCodeFormattingRule": "$NP$CC $FG","leadingDigits": "[67]","format": "$1"},{"pattern": "(\\d{3})(\\d{2})(\\d{4})","carrierCodeFormattingRule": "$NP$CC $FG","leadingDigits": "8","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[2-467]\\d\\d|8001)\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "9"},"nationalNumberPattern": "8001[07]\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "22123456","nationalNumberPattern": "(?:2(?:2\\d\\d|5(?:11|[258]\\d|9[67])|6(?:12|2\\d|9[34])|8(?:2[34]|39|62))|3(?:3\\d\\d|4(?:6\\d|8[24])|8(?:25|42|5[257]|86|9[25])|9(?:[27]\\d|3[2-4]|4[248]|5[24]|6[2-6]))|4(?:4\\d\\d|6(?:11|[24689]\\d|72)))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "71234567","nationalNumberPattern": "[67]\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800171234","nationalNumberPattern": "8001[07]\\d{4}"}},{"id": "BQ","countryCode": "599","leadingDigits": "[347]","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "(?:[34]1|7\\d)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "7151234","nationalNumberPattern": "(?:318[023]|41(?:6[023]|70)|7(?:1[578]|50)\\d)\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "3181234","nationalNumberPattern": "(?:31(?:8[14-8]|9[14578])|416[14-9]|7(?:0[01]|7[07]|8\\d|9[056])\\d)\\d{3}"}},{"id": "BR","countryCode": "55","internationalPrefix": "00(?:1[245]|2[1-35]|31|4[13]|[56]5|99)","nationalPrefix": "0","nationalPrefixForParsing": "0(?:(1[245]|2[1-35]|31|4[13]|[56]5|99)(\\d{10,11}))?","nationalPrefixTransformRule": "$2","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3,6})","leadingDigits": "1(?:1[25-8]|2[357-9]|3[02-68]|4[12568]|5|6[0-8]|8[015]|9[0-47-9])|321|610","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": ["300|4(?:0[02]|37)","4(?:02|37)0|[34]00"],"format": "$1-$2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": ["[2-57]","[2357]|4(?:[0-24-9]|3(?:[0-689]|7[1-9]))"],"format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{2,3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:[358]|90)0","format": "$1 $2 $3"},{"pattern": "(\\d{5})(\\d{4})","leadingDigits": "9","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "($FG)","carrierCodeFormattingRule": "$NP $CC ($FG)","leadingDigits": "(?:[14689][1-9]|2[12478]|3[1-578]|5[13-5]|7[13-579])[2-57]","format": "$1 $2-$3"},{"pattern": "(\\d{2})(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "($FG)","carrierCodeFormattingRule": "$NP $CC ($FG)","leadingDigits": "[16][1-9]|[2-57-9]","format": "$1 $2-$3"}]},"generalDesc": {"nationalNumberPattern": "(?:[1-46-9]\\d\\d|5(?:[0-46-9]\\d|5[0-24679]))\\d{8}|[1-9]\\d{9}|[3589]\\d{8}|[34]\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "4020\\d{4}|[34]00\\d{5}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "8"},"exampleNumber": "1123456789","nationalNumberPattern": "(?:[14689][1-9]|2[12478]|3[1-578]|5[13-5]|7[13-579])[2-5]\\d{7}"},"mobile": {"possibleLengths": {"national": "10,11","localOnly": "8,9"},"exampleNumber": "11961234567","nationalNumberPattern": "(?:[14689][1-9]|2[12478]|3[1-578]|5[13-5]|7[13-579])(?:7|9\\d)\\d{7}"},"tollFree": {"possibleLengths": {"national": "9,10"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6,7}"},"premiumRate": {"possibleLengths": {"national": "9,10"},"exampleNumber": "300123456","nationalNumberPattern": "300\\d{6}|[59]00\\d{6,7}"},"sharedCost": {"possibleLengths": {"national": "8,10"},"exampleNumber": "40041234","nationalNumberPattern": "300\\d{7}|[34]00\\d{5}|4(?:02|37)0\\d{4}"}},{"id": "BS","countryCode": "1","leadingDigits": "242","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([3-8]\\d{6})$","nationalPrefixTransformRule": "242$1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:242|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2423456789","nationalNumberPattern": "242(?:3(?:02|[236][1-9]|4[0-24-9]|5[0-68]|7[347]|8[0-4]|9[2-467])|461|502|6(?:0[1-4]|12|2[013]|[45]0|7[67]|8[78]|9[89])|7(?:02|88))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2423591234","nationalNumberPattern": "242(?:3(?:5[79]|7[56]|95)|4(?:[23][1-9]|4[1-35-9]|5[1-8]|6[2-8]|7\\d|81)|5(?:2[45]|3[35]|44|5[1-46-9]|65|77)|6[34]6|7(?:27|38)|8(?:0[1-9]|1[02-9]|2\\d|[89]9))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8002123456","nationalNumberPattern": "242300\\d{4}|8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "2422250123","nationalNumberPattern": "242225[0-46-9]\\d{3}"}},{"id": "BT","countryCode": "975","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "[2-7]","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d)(\\d{3})(\\d{3})","leadingDigits": "[2-68]|7[246]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "1[67]|7","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[17]\\d{7}|[2-8]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7","localOnly": "6"},"exampleNumber": "2345678","nationalNumberPattern": "(?:2[3-6]|[34][5-7]|5[236]|6[2-46]|7[246]|8[2-4])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "17123456","nationalNumberPattern": "(?:1[67]|77)\\d{6}"}},{"id": "BW","countryCode": "267","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{5})","leadingDigits": "90","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-6]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "7","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "90\\d{5}|(?:[2-6]|7\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2401234","nationalNumberPattern": "(?:2(?:4[0-48]|6[0-24]|9[0578])|3(?:1[0-35-9]|55|[69]\\d|7[013])|4(?:6[03]|7[1267]|9[0-5])|5(?:3[0389]|4[0489]|7[1-47]|88|9[0-49])|6(?:2[1-35]|5[149]|8[067]))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "71123456","nationalNumberPattern": "77200\\d{3}|7(?:[1-6]\\d|7[014-8])\\d{5}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9012345","nationalNumberPattern": "90\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "79101234","nationalNumberPattern": "79(?:1(?:[01]\\d|20)|2[0-2]\\d)\\d{3}"}},{"id": "BY","countryCode": "375","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","nationalPrefixForParsing": "0|80?","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "800","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{2})(\\d{2,4})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "800","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP 0$FG","leadingDigits": ["1(?:5[169]|6[3-5]|7[179])|2(?:1[35]|2[34]|3[3-5])","1(?:5[169]|6(?:3[1-3]|4|5[125])|7(?:1[3-9]|7[0-24-6]|9[2-7]))|2(?:1[35]|2[34]|3[3-5])"],"format": "$1 $2-$3"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP 0$FG","leadingDigits": "1(?:[56]|7[467])|2[1-3]","format": "$1 $2-$3-$4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP 0$FG","leadingDigits": "[1-4]","format": "$1 $2-$3-$4"},{"pattern": "(\\d{3})(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[12]\\d|33|44|902)\\d{7}|8(?:0[0-79]\\d{5,7}|[1-7]\\d{9})|8(?:1[0-489]|[5-79]\\d)\\d{7}|8[1-79]\\d{6,7}|8[0-79]\\d{5}|8\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "[6-11]"},"nationalNumberPattern": "800\\d{3,7}|(?:8(?:0[13]|10|20\\d)|902)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "[5-7]"},"exampleNumber": "152450911","nationalNumberPattern": "(?:1(?:5(?:1[1-5]|[24]\\d|6[2-4]|9[1-7])|6(?:[235]\\d|4[1-7])|7\\d\\d)|2(?:1(?:[246]\\d|3[0-35-9]|5[1-9])|2(?:[235]\\d|4[0-8])|3(?:[26]\\d|3[02-79]|4[024-7]|5[03-7])))\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "294911911","nationalNumberPattern": "(?:2(?:5[5-79]|9[1-9])|(?:33|44)\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "[6-11]"},"exampleNumber": "8011234567","nationalNumberPattern": "800\\d{3,7}|8(?:0[13]|20\\d)\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9021234567","nationalNumberPattern": "(?:810|902)\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "249123456","nationalNumberPattern": "249\\d{6}"}},{"id": "BZ","countryCode": "501","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-8]","format": "$1-$2"},{"pattern": "(\\d)(\\d{3})(\\d{4})(\\d{3})","leadingDigits": "0","format": "$1-$2-$3-$4"}]},"generalDesc": {"nationalNumberPattern": "(?:0800\\d|[2-8])\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2221234","nationalNumberPattern": "(?:236|732)\\d{4}|[2-578][02]\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "6221234","nationalNumberPattern": "6[0-35-7]\\d{5}"},"tollFree": {"possibleLengths": {"national": "11"},"exampleNumber": "08001234123","nationalNumberPattern": "0800\\d{7}"}},{"id": "CA","countryCode": "1","internationalPrefix": "011","nationalPrefix": "1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:[2-8]\\d|90)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "5062345678","nationalNumberPattern": "(?:2(?:04|[23]6|[48]9|50)|3(?:06|43|65)|4(?:03|1[68]|3[178]|50)|5(?:06|1[49]|48|79|8[17])|6(?:04|13|39|47)|7(?:0[59]|78|8[02])|8(?:[06]7|19|25|73)|90[25])[2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "5062345678","nationalNumberPattern": "(?:2(?:04|[23]6|[48]9|50)|3(?:06|43|65)|4(?:03|1[68]|3[178]|50)|5(?:06|1[49]|48|79|8[17])|6(?:04|13|39|47)|7(?:0[59]|78|8[02])|8(?:[06]7|19|25|73)|90[25])[2-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "(?:5(?:00|2[12]|33|44|66|77|88)|622)[2-9]\\d{6}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "6002012345","nationalNumberPattern": "600[2-9]\\d{6}"}},{"id": "CC","countryCode": "61","preferredInternationalPrefix": "0011","internationalPrefix": "001[14-689]|14(?:1[14]|34|4[17]|[56]6|7[47]|88)0011","nationalPrefix": "0","nationalPrefixForParsing": "0|([59]\\d{7})$","nationalPrefixTransformRule": "8$1","generalDesc": {"nationalNumberPattern": "1(?:[0-79]\\d|8[0-24-9])\\d{7}|(?:[148]\\d\\d|550)\\d{6}|1\\d{5,7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "8"},"exampleNumber": "891621234","nationalNumberPattern": "8(?:51(?:0(?:02|31|60)|118)|91(?:0(?:1[0-2]|29)|1(?:[28]2|50|79)|2(?:10|64)|3(?:[06]8|22)|4[29]8|62\\d|70[23]|959))\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "412345678","nationalNumberPattern": "483[0-3]\\d{5}|4(?:[0-3]\\d|4[047-9]|5[0-25-9]|6[06-9]|7[02-9]|8[0-2457-9]|9[0-27-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "7,10"},"exampleNumber": "1800123456","nationalNumberPattern": "180(?:0\\d{3}|2)\\d{3}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1900123456","nationalNumberPattern": "190[0-26]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "6,8,10"},"exampleNumber": "1300123456","nationalNumberPattern": "13(?:00\\d{3}|45[0-4])\\d{3}|13\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "550123456","nationalNumberPattern": "(?:14(?:5(?:1[0458]|[23][458])|71\\d)|550\\d\\d)\\d{4}"}},{"id": "CD","countryCode": "243","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "88","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-6]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[189]\\d{8}|[1-68]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7,9"},"exampleNumber": "1234567","nationalNumberPattern": "12\\d{7}|[1-6]\\d{6}"},"mobile": {"possibleLengths": {"national": "7,9"},"exampleNumber": "991234567","nationalNumberPattern": "88\\d{5}|(?:8[0-2459]|9[017-9])\\d{7}"}},{"id": "CF","countryCode": "236","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[278]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:[27]\\d{3}|8776)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21612345","nationalNumberPattern": "2[12]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "7[0257]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "87761234","nationalNumberPattern": "8776\\d{4}"}},{"id": "CG","countryCode": "242","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "801","format": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{4})(\\d{4})","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","leadingDigits": "[02]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "222\\d{6}|(?:0\\d|80)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "222123456","nationalNumberPattern": "222[1-589]\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "061234567","nationalNumberPattern": "0[14-6]\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "80(?:0\\d\\d|11[0-4])\\d{4}"}},{"id": "CH","countryCode": "41","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8[047]|90","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-79]|81","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3 $4 $5"}]},"generalDesc": {"nationalNumberPattern": "8\\d{11}|[2-9]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "212345678","nationalNumberPattern": "(?:2[12467]|3[1-4]|4[134]|5[256]|6[12]|[7-9]1)\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "781234567","nationalNumberPattern": "7[35-9]\\d{7}"},"pager": {"possibleLengths": {"national": "9"},"exampleNumber": "740123456","nationalNumberPattern": "74[0248]\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "90[016]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "840123456","nationalNumberPattern": "84[0248]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "878123456","nationalNumberPattern": "878\\d{6}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "581234567","nationalNumberPattern": "5[18]\\d{7}"},"voicemail": {"possibleLengths": {"national": "12"},"exampleNumber": "860123456789","nationalNumberPattern": "860\\d{9}"}},{"id": "CI","countryCode": "225","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[02-9]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[02-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "(?:2(?:0[023]|1[02357]|[23][045]|4[03-5])|3(?:0[06]|1[069]|[2-4][07]|5[09]|6[08]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "01234567","nationalNumberPattern": "97[0-3]\\d{5}|(?:0[1-9]|[457]\\d|6[014-9]|8[4-9]|95)\\d{6}"}},{"id": "CK","countryCode": "682","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})","leadingDigits": "[2-578]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[2-578]\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "21234","nationalNumberPattern": "(?:2\\d|3[13-7]|4[1-5])\\d{3}"},"mobile": {"possibleLengths": {"national": "5"},"exampleNumber": "71234","nationalNumberPattern": "[578]\\d{4}"}},{"id": "CL","countryCode": "56","internationalPrefix": "(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})","leadingDigits": "1(?:[03-589]|21)|[29]0|78","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "($FG)","leadingDigits": "21","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","leadingDigits": "44","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "($FG)","leadingDigits": "2[23]","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","leadingDigits": "9[2-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($FG)","leadingDigits": "3[2-5]|[47]|5[1-3578]|6[13-57]|8(?:0[1-9]|[1-9])","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","leadingDigits": "60|8","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{2})(\\d{3})","leadingDigits": "60","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "12300\\d{6}|6\\d{9,10}|[2-9]\\d{8}"},"noInternationalDialling": {"possibleLengths": {"national": "10,11"},"nationalNumberPattern": "600\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "221234567","nationalNumberPattern": "(?:2(?:1962|3(?:2\\d\\d|300))|80[1-9]\\d\\d)\\d{4}|(?:22|3[2-5]|[47][1-35]|5[1-3578]|6[13-57]|8[1-9]|9[2-9])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "221234567","nationalNumberPattern": "(?:2(?:1962|3(?:2\\d\\d|300))|80[1-9]\\d\\d)\\d{4}|(?:22|3[2-5]|[47][1-35]|5[1-3578]|6[13-57]|8[1-9]|9[2-9])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9,11"},"exampleNumber": "800123456","nationalNumberPattern": "(?:123|8)00\\d{6}"},"sharedCost": {"possibleLengths": {"national": "10,11"},"exampleNumber": "6001234567","nationalNumberPattern": "600\\d{7,8}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "441234567","nationalNumberPattern": "44\\d{7}"}},{"id": "CM","countryCode": "237","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "88","format": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[26]","format": "$1 $2 $3 $4 $5"}]},"generalDesc": {"nationalNumberPattern": "(?:[26]\\d\\d|88)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "222123456","nationalNumberPattern": "2(?:22|33|4[23])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "671234567","nationalNumberPattern": "6[5-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "88012345","nationalNumberPattern": "88\\d{6}"}},{"id": "CN","countryCode": "86","preferredInternationalPrefix": "00","internationalPrefix": "00|1(?:[12]\\d|79|9[0235-7])\\d\\d00","nationalPrefix": "0","nationalPrefixForParsing": "0|(1(?:[12]\\d|79|9[0235-7])\\d\\d)","availableFormats": {"numberFormat": [{"pattern": "(\\d{5,6})","leadingDigits": "96","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$CC $FG","leadingDigits": ["(?:10|2[0-57-9])[19]","(?:10|2[0-57-9])(?:10|9[56])","(?:10|2[0-57-9])(?:100|9[56])"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": ["[1-9]","1[1-9]|26|[3-9]|(?:10|2[0-57-9])(?:[0-8]|9[0-47-9])","1[1-9]|26|[3-9]|(?:10|2[0-57-9])(?:[02-8]|1(?:0[1-9]|[1-9])|9[0-47-9])"],"format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "16[08]","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$CC $FG","leadingDigits": ["3(?:[157]|35|49|9[1-68])|4(?:[17]|2[179]|6[47-9]|8[23])|5(?:[1357]|2[37]|4[36]|6[1-46]|80)|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])|(?:4[35]|59|85)[1-9]","(?:3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[1-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))[19]","85[23](?:10|95)|(?:3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[14-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))(?:10|9[56])","85[23](?:100|95)|(?:3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[14-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))(?:100|9[56])"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": ["[1-9]","1[1-9]|26|[3-9]|(?:10|2[0-57-9])(?:[0-8]|9[0-47-9])","26|3(?:[0268]|9[079])|4(?:[049]|2[02-68]|[35]0|6[0-356]|8[014-9])|5(?:0|2[0-24-689]|4[0-2457-9]|6[057-9]|90)|6(?:[0-24578]|6[14-79]|9[03-9])|7(?:0[02-9]|2[0135-79]|3[23]|4[0-27-9]|6[1457]|8)|8(?:[046]|1[01459]|2[0-489]|50|8[0-2459]|9[09])|9(?:0[0457]|1[08]|[268]|4[024-9])|(?:34|85[23])[0-8]|(?:1|58)[1-9]|(?:63|95)[06-9]|(?:33|85[23]9)[0-46-9]|(?:10|2[0-57-9]|3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[14-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))(?:[0-8]|9[0-47-9])","26|3(?:[0268]|3[0-46-9]|4[0-8]|9[079])|4(?:[049]|2[02-68]|[35]0|6[0-356]|8[014-9])|5(?:0|2[0-24-689]|4[0-2457-9]|6[057-9]|90)|6(?:[0-24578]|3[06-9]|6[14-79]|9[03-9])|7(?:0[02-9]|2[0135-79]|3[23]|4[0-27-9]|6[1457]|8)|8(?:[046]|1[01459]|2[0-489]|5(?:0|[23](?:[02-8]|1[1-9]|9[0-46-9]))|8[0-2459]|9[09])|9(?:0[0457]|1[08]|[268]|4[024-9]|5[06-9])|(?:1|58|85[23]10)[1-9]|(?:10|2[0-57-9])(?:[0-8]|9[0-47-9])|(?:3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[14-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))(?:[02-8]|1(?:0[1-9]|[1-9])|9[0-47-9])"],"format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "(?:4|80)0","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","carrierCodeFormattingRule": "$CC $FG","leadingDigits": ["10|2(?:[02-57-9]|1[1-9])","10|2(?:[02-57-9]|1[1-9])","10[0-79]|2(?:[02-57-9]|1[1-79])|(?:10|21)8(?:0[1-9]|[1-9])"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "3(?:[3-59]|7[02-68])|4(?:[26-8]|3[3-9]|5[2-9])|5(?:3[03-9]|[468]|7[028]|9[2-46-9])|6|7(?:[0-247]|3[04-9]|5[0-4689]|6[2368])|8(?:[1-358]|9[1-7])|9(?:[013479]|5[1-5])|(?:[34]1|55|79|87)[02-9]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{7,8})","leadingDigits": "9","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "80","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[3-578]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "1[3-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[12]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "1[1279]\\d{8,9}|2\\d{9}(?:\\d{2})?|[12]\\d{6,7}|86\\d{6}|(?:1[03-68]\\d|6)\\d{7,9}|(?:[3-579]\\d|8[0-57-9])\\d{6,9}"},"noInternationalDialling": {"possibleLengths": {"national": "[10-12]"},"nationalNumberPattern": "(?:(?:10|21)8|[48])00\\d{7}|950\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "[7-11]","localOnly": "5,6"},"exampleNumber": "1012345678","nationalNumberPattern": "(?:10(?:[02-79]\\d\\d|[18](?:0[1-9]|[1-9]\\d))|21(?:[18](?:0[1-9]|[1-9]\\d)|[2-79]\\d\\d))\\d{5}|(?:43[35]|754)\\d{7,8}|8(?:078\\d{7}|51\\d{7,8})|(?:10|(?:2|85)1|43[35]|754)(?:100\\d\\d|95\\d{3,4})|(?:2[02-57-9]|3(?:11|7[179])|4(?:[15]1|3[12])|5(?:1\\d|2[37]|3[12]|51|7[13-79]|9[15])|7(?:[39]1|5[57]|6[09])|8(?:71|98))(?:[02-8]\\d{7}|1(?:0(?:0\\d\\d(?:\\d{3})?|[1-9]\\d{5})|[1-9]\\d{6})|9(?:[0-46-9]\\d{6}|5\\d{3}(?:\\d(?:\\d{2})?)?))|(?:3(?:1[02-9]|35|49|5\\d|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|3[46-9]|5[2-9]|6[47-9]|7\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[17]\\d|2[248]|3[04-9]|4[3-6]|5[0-3689]|6[2368]|9[02-9])|8(?:1[236-8]|2[5-7]|3\\d|5[2-9]|7[02-9]|8[36-8]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))(?:[02-8]\\d{6}|1(?:0(?:0\\d\\d(?:\\d{2})?|[1-9]\\d{4})|[1-9]\\d{5})|9(?:[0-46-9]\\d{5}|5\\d{3,5}))"},"mobile": {"possibleLengths": {"national": "11"},"exampleNumber": "13123456789","nationalNumberPattern": "1740[0-5]\\d{6}|1(?:[38]\\d|4[57]|5[0-35-9]|6[25-7]|7[0-35-8]|9[189])\\d{8}"},"tollFree": {"possibleLengths": {"national": "10,12"},"exampleNumber": "8001234567","nationalNumberPattern": "(?:(?:10|21)8|8)00\\d{7}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "16812345","nationalNumberPattern": "16[08]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "[7-11]","localOnly": "5,6"},"exampleNumber": "4001234567","nationalNumberPattern": "400\\d{7}|950\\d{7,8}|(?:10|2[0-57-9]|3(?:[157]\\d|35|49|9[1-68])|4(?:[17]\\d|2[179]|[35][1-9]|6[47-9]|8[23])|5(?:[1357]\\d|2[37]|4[36]|6[1-46]|80|9[1-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]\\d|2[248]|3[014-9]|4[3-6]|6[023689])|8(?:1[236-8]|2[5-7]|[37]\\d|5[14-9]|8[36-8]|9[1-8])|9(?:0[1-3689]|1[1-79]|[379]\\d|4[13]|5[1-5]))96\\d{3,4}"}},{"id": "CO","countryCode": "57","internationalPrefix": "00(?:4(?:[14]4|56)|[579])","nationalPrefix": "0","nationalPrefixForParsing": "0([3579]|4(?:[14]4|56))?","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{7})","nationalPrefixFormattingRule": "($FG)","carrierCodeFormattingRule": "$NP$CC $FG","leadingDigits": "[14][2-9]|[25-8]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{7})","carrierCodeFormattingRule": "$NP$CC $FG","leadingDigits": "3","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1-$2-$3","intlFormat": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:1\\d|3)\\d{9}|[124-8]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "12345678","nationalNumberPattern": "[124-8][2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "3211234567","nationalNumberPattern": "3333(?:0(?:0\\d|1[0-5])|[4-9]\\d\\d)\\d{3}|33(?:00|3[0-24-9])\\d{6}|3(?:0[0-5]|1\\d|2[0-3]|5[01]|70)\\d{7}"},"tollFree": {"possibleLengths": {"national": "11"},"exampleNumber": "18001234567","nationalNumberPattern": "1800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "11"},"exampleNumber": "19001234567","nationalNumberPattern": "19(?:0[01]|4[78])\\d{7}"}},{"id": "CR","countryCode": "506","internationalPrefix": "00","nationalPrefixForParsing": "(19(?:0[0-2468]|1[09]|20|66|77|99))","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{4})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[24-7]|8[3-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[89]","format": "$1-$2-$3"}]},"generalDesc": {"nationalNumberPattern": "(?:8\\d|90)\\d{8}|[24-8]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22123456","nationalNumberPattern": "210[7-9]\\d{4}|2(?:[024-7]\\d|1[1-9])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "83123456","nationalNumberPattern": "6500[01]\\d{3}|5(?:0[01]|7[0-3])\\d{5}|(?:6[0-4]|7[0-3]|8[3-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "90[059]\\d{7}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "40001234","nationalNumberPattern": "(?:210[0-6]|4\\d{3}|5100)\\d{4}"}},{"id": "CU","countryCode": "53","internationalPrefix": "119","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{4,6})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2[1-4]|[34]","format": "$1 $2"},{"pattern": "(\\d)(\\d{6,7})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "7","format": "$1 $2"},{"pattern": "(\\d)(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[27]\\d{6,7}|[34]\\d{5,7}|(?:5|8\\d\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "[6-8],10","localOnly": "4,5"},"exampleNumber": "71234567","nationalNumberPattern": "(?:3[23]|48)\\d{4,6}|(?:31|4[36]|8(?:0[25]|78)\\d)\\d{6}|(?:2[1-4]|4[1257]|7\\d)\\d{5,6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "51234567","nationalNumberPattern": "5\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "8071234567","nationalNumberPattern": "807\\d{7}"}},{"id": "CV","countryCode": "238","internationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "[2-589]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:[2-59]\\d\\d|800)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2211234","nationalNumberPattern": "2(?:2[1-7]|3[0-8]|4[12]|5[1256]|6\\d|7[1-3]|8[1-5])\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "9911234","nationalNumberPattern": "(?:[34][36]|5[1-389]|9\\d)\\d{5}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}"}},{"id": "CW","mainCountryForCode": "true","countryCode": "599","leadingDigits": "[69]","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[3467]","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{4})","leadingDigits": "9[4-8]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[34]1|60|(?:7|9\\d)\\d)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7,8"},"exampleNumber": "94351234","nationalNumberPattern": "9(?:4(?:3[0-5]|4[14]|6\\d)|50\\d|7(?:2[014]|3[02-9]|4[4-9]|6[357]|77|8[7-9])|8(?:3[39]|[46]\\d|7[01]|8[57-9]))\\d{4}"},"mobile": {"possibleLengths": {"national": "7,8"},"exampleNumber": "95181234","nationalNumberPattern": "953[01]\\d{4}|9(?:5[12467]|6[5-9])\\d{5}"},"pager": {"possibleLengths": {"national": "8"},"exampleNumber": "95581234","nationalNumberPattern": "955\\d{5}"},"sharedCost": {"possibleLengths": {"national": "7"},"exampleNumber": "6001234","nationalNumberPattern": "60[0-2]\\d{4}"}},{"id": "CX","countryCode": "61","preferredInternationalPrefix": "0011","internationalPrefix": "001[14-689]|14(?:1[14]|34|4[17]|[56]6|7[47]|88)0011","nationalPrefix": "0","nationalPrefixForParsing": "0|([59]\\d{7})$","nationalPrefixTransformRule": "8$1","generalDesc": {"nationalNumberPattern": "1(?:[0-79]\\d|8[0-24-9])\\d{7}|(?:[148]\\d\\d|550)\\d{6}|1\\d{5,7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "8"},"exampleNumber": "891641234","nationalNumberPattern": "8(?:51(?:0(?:01|30|59)|117)|91(?:00[6-9]|1(?:[28]1|49|78)|2(?:09|63)|3(?:12|26|75)|4(?:56|97)|64\\d|7(?:0[01]|1[0-2])|958))\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "412345678","nationalNumberPattern": "483[0-3]\\d{5}|4(?:[0-3]\\d|4[047-9]|5[0-25-9]|6[06-9]|7[02-9]|8[0-2457-9]|9[0-27-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "7,10"},"exampleNumber": "1800123456","nationalNumberPattern": "180(?:0\\d{3}|2)\\d{3}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1900123456","nationalNumberPattern": "190[0-26]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "6,8,10"},"exampleNumber": "1300123456","nationalNumberPattern": "13(?:00\\d{3}|45[0-4])\\d{3}|13\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "550123456","nationalNumberPattern": "(?:14(?:5(?:1[0458]|[23][458])|71\\d)|550\\d\\d)\\d{4}"}},{"id": "CY","countryCode": "357","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{6})","leadingDigits": "[257-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[279]\\d|[58]0)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22345678","nationalNumberPattern": "2[2-6]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "96123456","nationalNumberPattern": "9[4-79]\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80001234","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "90[09]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80112345","nationalNumberPattern": "80[1-9]\\d{5}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "700\\d{5}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "77123456","nationalNumberPattern": "(?:50|77)\\d{6}"}},{"id": "CZ","countryCode": "420","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[2-8]|9[015-7]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "9","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "9","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[2-578]\\d|60)\\d{7}|9\\d{8,11}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "212345678","nationalNumberPattern": "(?:2\\d|3[1257-9]|4[16-9]|5[13-9])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "601123456","nationalNumberPattern": "(?:60[1-8]|7(?:0[2-5]|[2379]\\d))\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "9(?:0[05689]|76)\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "811234567","nationalNumberPattern": "8[134]\\d{7}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "700123456","nationalNumberPattern": "70[01]\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "910123456","nationalNumberPattern": "9[17]0\\d{6}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "972123456","nationalNumberPattern": "9(?:5\\d|7[2-4])\\d{6}"},"voicemail": {"possibleLengths": {"national": "[9-12]"},"exampleNumber": "93123456789","nationalNumberPattern": "9(?:3\\d{9}|6\\d{7,10})"}},{"id": "DE","countryCode": "49","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3,13})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3[02]|40|[68]9","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3,12})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["2(?:0[1-389]|1[124]|2[18]|3[14])|3(?:[35-9][15]|4[015])|906|(?:2[4-9]|4[2-9]|[579][1-9]|[68][1-8])1","2(?:0[1-389]|12[0-8])|3(?:[35-9][15]|4[015])|906|2(?:[13][14]|2[18])|(?:2[4-9]|4[2-9]|[579][1-9]|[68][1-8])1"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{2,11})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[24-6]|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])|70[2-8]|8(?:0[2-9]|[1-8])|90[7-9]|[79][1-9]","[24-6]|3(?:3(?:0[1-467]|2[127-9]|3[124578]|7[1257-9]|8[1256]|9[145])|4(?:2[135]|4[13578]|9[1346])|5(?:0[14]|2[1-3589]|6[1-4]|7[13468]|8[13568])|6(?:2[1-489]|3[124-6]|6[13]|7[12579]|8[1-356]|9[135])|7(?:2[1-7]|4[145]|6[1-5]|7[1-4])|8(?:21|3[1468]|6|7[1467]|8[136])|9(?:0[12479]|2[1358]|4[134679]|6[1-9]|7[136]|8[147]|9[1468]))|70[2-8]|8(?:0[2-9]|[1-8])|90[7-9]|[79][1-9]|3[68]4[1347]|3(?:47|60)[1356]|3(?:3[46]|46|5[49])[1246]|3[4579]3[1357]"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "138","format": "$1 $2"},{"pattern": "(\\d{5})(\\d{2,10})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5,11})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "181","format": "$1 $2"},{"pattern": "(\\d{3})(\\d)(\\d{4,10})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1(?:3|80)|9","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{7,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[67]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{7,12})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2"},{"pattern": "(\\d{5})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["185","1850","18500"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "18[68]","format": "$1 $2"},{"pattern": "(\\d{5})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "15[0568]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "15[1279]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "18","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{2})(\\d{7,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1(?:6[023]|7)","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "15[279]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "15","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2579]\\d{5,14}|49(?:[05]\\d{10}|[46][1-8]\\d{4,9})|49(?:[0-25]\\d|3[1-689]|7[1-7])\\d{4,8}|49(?:[0-2579]\\d|[34][1-9]|6[0-8])\\d{3}|49\\d{3,4}|(?:1|[368]\\d|4[0-8])\\d{3,13}"},"fixedLine": {"possibleLengths": {"national": "[5-15]","localOnly": "[2-4]"},"exampleNumber": "30123456","nationalNumberPattern": "(?:32|49[4-6]\\d)\\d{9}|49[0-7]\\d{3,9}|(?:[34]0|[68]9)\\d{3,13}|(?:2(?:0[1-689]|[1-3569]\\d|4[0-8]|7[1-7]|8[0-7])|3(?:[3569]\\d|4[0-79]|7[1-7]|8[1-8])|4(?:1[02-9]|[2-48]\\d|5[0-6]|6[0-8]|7[0-79])|5(?:0[2-8]|[124-6]\\d|[38][0-8]|[79][0-7])|6(?:0[02-9]|[1-358]\\d|[47][0-8]|6[1-9])|7(?:0[2-8]|1[1-9]|[27][0-7]|3\\d|[4-6][0-8]|8[0-5]|9[013-7])|8(?:0[2-9]|1[0-79]|2\\d|3[0-46-9]|4[0-6]|5[013-9]|6[1-8]|7[0-8]|8[0-24-6])|9(?:0[6-9]|[1-4]\\d|[589][0-7]|6[0-8]|7[0-467]))\\d{3,12}"},"mobile": {"possibleLengths": {"national": "10,11"},"exampleNumber": "15123456789","nationalNumberPattern": "15[0-25-9]\\d{8}|1(?:6[023]|7\\d)\\d{7,8}"},"pager": {"possibleLengths": {"national": "[4-14]"},"exampleNumber": "16412345","nationalNumberPattern": "16(?:4\\d{1,10}|[89]\\d{1,11})"},"tollFree": {"possibleLengths": {"national": "[10-15]"},"exampleNumber": "8001234567890","nationalNumberPattern": "800\\d{7,12}"},"premiumRate": {"possibleLengths": {"national": "10,11"},"exampleNumber": "9001234567","nationalNumberPattern": "(?:137[7-9]|900(?:[135]|9\\d))\\d{6}"},"sharedCost": {"possibleLengths": {"national": "[7-14]"},"exampleNumber": "18012345","nationalNumberPattern": "180\\d{5,11}|13(?:7[1-6]\\d\\d|8)\\d{4}"},"personalNumber": {"possibleLengths": {"national": "11"},"exampleNumber": "70012345678","nationalNumberPattern": "700\\d{8}"},"uan": {"possibleLengths": {"national": "[8-14]"},"exampleNumber": "18500123456","nationalNumberPattern": "18(?:1\\d{5,11}|[2-9]\\d{8})"},"voicemail": {"possibleLengths": {"national": "12,13"},"exampleNumber": "177991234567","nationalNumberPattern": "1(?:6(?:013|255|399)|7(?:(?:[015]1|[69]3)3|[2-4]55|[78]99))\\d{7,8}|15(?:(?:[03-68]00|113)\\d|2\\d55|7\\d99|9\\d33)\\d{7}"}},{"id": "DJ","countryCode": "253","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[27]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:2\\d|77)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21360003","nationalNumberPattern": "2(?:1[2-5]|7[45])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "77831001","nationalNumberPattern": "77\\d{6}"}},{"id": "DK","countryCode": "45","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2-9]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[2-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "32123456","nationalNumberPattern": "(?:[2-7]\\d|8[126-9]|9[1-46-9])\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "32123456","nationalNumberPattern": "(?:[2-7]\\d|8[126-9]|9[1-46-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "80\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "90\\d{6}"}},{"id": "DM","countryCode": "1","leadingDigits": "767","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-7]\\d{6})$","nationalPrefixTransformRule": "767$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|767|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7674201234","nationalNumberPattern": "767(?:2(?:55|66)|4(?:2[01]|4[0-25-9])|50[0-4]|70[1-3])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7672251234","nationalNumberPattern": "767(?:2(?:[2-4689]5|7[5-7])|31[5-7]|61[1-7])\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "DO","countryCode": "1","leadingDigits": "8[024]9","internationalPrefix": "011","nationalPrefix": "1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8092345678","nationalNumberPattern": "8(?:[04]9[2-9]\\d\\d|29(?:2(?:[0-59]\\d|6[04-9]|7[0-27]|8[0237-9])|3(?:[0-35-9]\\d|4[7-9])|[45]\\d\\d|6(?:[0-27-9]\\d|[3-5][1-9]|6[0135-8])|7(?:0[013-9]|[1-37]\\d|4[1-35689]|5[1-4689]|6[1-57-9]|8[1-79]|9[1-8])|8(?:0[146-9]|1[0-48]|[248]\\d|3[1-79]|5[01589]|6[013-68]|7[124-8]|9[0-8])|9(?:[0-24]\\d|3[02-46-9]|5[0-79]|60|7[0169]|8[57-9]|9[02-9])))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8092345678","nationalNumberPattern": "8[024]9[2-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "DZ","countryCode": "213","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-4]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[5-8]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[1-4]|[5-79]\\d|80)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "12345678","nationalNumberPattern": "9619\\d{5}|(?:1\\d|2[013-79]|3[0-8]|4[0135689])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "551234567","nationalNumberPattern": "(?:5(?:4[0-29]|5\\d|6[01])|6(?:[569]\\d|7[0-6])|7[7-9]\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "808123456","nationalNumberPattern": "80[3-689]1\\d{5}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "801123456","nationalNumberPattern": "80[12]1\\d{5}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "983123456","nationalNumberPattern": "98[23]\\d{6}"}},{"id": "EC","countryCode": "593","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-7]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[2-7]","format": "$1 $2-$3","intlFormat": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3,4})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1800\\d{6,7}|(?:[2-7]|9\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "22123456","nationalNumberPattern": "[2-7][2-7]\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "991234567","nationalNumberPattern": "964[0-2]\\d{5}|9(?:39|[57][89]|6[0-37-9]|[89]\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "10,11"},"exampleNumber": "18001234567","nationalNumberPattern": "1800\\d{6,7}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "28901234","nationalNumberPattern": "[2-7]890\\d{4}"}},{"id": "EE","countryCode": "372","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": ["[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]","[369]|4[3-8]|5(?:[02]|1(?:[0-8]|95)|5[0-478]|6(?:4[0-4]|5[1-589]))|7[1-9]"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{3,4})","leadingDigits": ["[45]|8(?:00|[1-4])","[45]|8(?:00[1-9]|[1-4])"],"format": "$1 $2"},{"pattern": "(\\d{2})(\\d{2})(\\d{4})","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "80","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "8\\d{9}|[4578]\\d{7}|(?:[3-8]\\d\\d|900)\\d{4}"},"noInternationalDialling": {"possibleLengths": {"national": "7"},"nationalNumberPattern": "800[2-9]\\d{3}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "3212345","nationalNumberPattern": "(?:3[23589]|4[3-8]|6\\d|7[1-9]|88)\\d{5}"},"mobile": {"possibleLengths": {"national": "7,8"},"exampleNumber": "51234567","nationalNumberPattern": "(?:5\\d|8[1-4])\\d{6}|5(?:(?:[02]\\d|5[0-478])\\d|1(?:[0-8]\\d|95)|6(?:4[0-4]|5[1-589]))\\d{3}"},"tollFree": {"possibleLengths": {"national": "7,8,10"},"exampleNumber": "80012345","nationalNumberPattern": "800(?:(?:0\\d\\d|1)\\d|[2-9])\\d{3}"},"premiumRate": {"possibleLengths": {"national": "7,8"},"exampleNumber": "9001234","nationalNumberPattern": "(?:40\\d\\d|900)\\d{4}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "70[0-2]\\d{5}"}},{"id": "EG","countryCode": "20","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{7,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[23]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[35]|[4-6]|8[2468]|9[235-7]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[189]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[189]\\d{8,9}|[24-6]\\d{8}|[135]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "234567890","nationalNumberPattern": "(?:15\\d|57[23])\\d{5,6}|(?:13[23]|(?:2[2-4]|3)\\d|4(?:0[2-5]|[578][23]|64)|5(?:0[2-7]|5\\d)|6[24-689]3|8(?:2[2-57]|4[26]|6[237]|8[2-4])|9(?:2[27]|3[24]|52|6[2356]|7[2-4]))\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "1001234567","nationalNumberPattern": "1[0-25]\\d{8}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "900\\d{7}"}},{"id": "EH","countryCode": "212","leadingDigits": "528[89]","internationalPrefix": "00","nationalPrefix": "0","generalDesc": {"nationalNumberPattern": "[5-8]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "528812345","nationalNumberPattern": "528[89]\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "650123456","nationalNumberPattern": "(?:6(?:[0-79]\\d|8[0-247-9])|7(?:0[06-8]|6[1267]|7[0-27]))\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "891234567","nationalNumberPattern": "89\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "592401234","nationalNumberPattern": "592(?:4[0-2]|93)\\d{4}"}},{"id": "ER","countryCode": "291","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d)(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[178]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[178]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7","localOnly": "6"},"exampleNumber": "8370362","nationalNumberPattern": "(?:1(?:1[12568]|[24]0|55|6[146])|8\\d\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7123456","nationalNumberPattern": "(?:17[1-3]|7\\d\\d)\\d{4}"}},{"id": "ES","countryCode": "34","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})","leadingDigits": "905","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{6})","leadingDigits": "[79]9","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[89]00","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[5-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:51|[6-9]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "810123456","nationalNumberPattern": "96906(?:0[0-8]|1[1-9]|[2-9]\\d)\\d\\d|9(?:69(?:0[0-57-9]|[1-9]\\d)|73(?:[0-8]\\d|9[1-9]))\\d{4}|(?:8(?:[1356]\\d|[28][0-8]|[47][1-9])|9(?:[135]\\d|[268][0-8]|4[1-9]|7[124-9]))\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "612345678","nationalNumberPattern": "9(?:6906(?:09|10)|7390\\d\\d)\\d\\d|(?:6\\d|7[1-48])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "[89]00\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "803123456","nationalNumberPattern": "80[367]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "901123456","nationalNumberPattern": "90[12]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "701234567","nationalNumberPattern": "70\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "511234567","nationalNumberPattern": "51\\d{7}"}},{"id": "ET","countryCode": "251","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-59]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:11|[2-59]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "111112345","nationalNumberPattern": "(?:11(?:1(?:1[124]|2[2-57]|3[1-5]|5[5-8]|8[6-8])|2(?:13|3[6-8]|5[89]|7[05-9]|8[2-6])|3(?:2[01]|3[0-289]|4[1289]|7[1-4]|87)|4(?:1[69]|3[2-49]|4[0-3]|6[5-8])|5(?:1[578]|44|5[0-4])|6(?:1[78]|2[69]|39|4[5-7]|5[1-5]|6[0-59]|8[015-8]))|2(?:2(?:11[1-9]|22[0-7]|33\\d|44[1467]|66[1-68])|5(?:11[124-6]|33[2-8]|44[1467]|55[14]|66[1-3679]|77[124-79]|880))|3(?:3(?:11[0-46-8]|(?:22|55)[0-6]|33[0134689]|44[04]|66[01467])|4(?:44[0-8]|55[0-69]|66[0-3]|77[1-5]))|4(?:6(?:119|22[0-24-7]|33[1-5]|44[13-69]|55[14-689]|660|88[1-4])|7(?:(?:11|22)[1-9]|33[13-7]|44[13-6]|55[1-689]))|5(?:7(?:227|55[05]|(?:66|77)[14-8])|8(?:11[149]|22[013-79]|33[0-68]|44[013-8]|550|66[1-5]|77\\d)))\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "911234567","nationalNumberPattern": "9\\d{8}"}},{"id": "FI","mainCountryForCode": "true","countryCode": "358","leadingDigits": "1[03-79]|[2-9]","preferredInternationalPrefix": "00","internationalPrefix": "00|99(?:[01469]|5(?:[14]1|3[23]|5[59]|77|88|9[09]))","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "75[12]","format": "$1","intlFormat": "NA"},{"pattern": "(\\d)(\\d{4,9})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2568][1-8]|3(?:0[1-9]|[1-9])|9","format": "$1 $2"},{"pattern": "(\\d{6})","leadingDigits": "11","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]00|[368]|70[07-9]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{4,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1245]|7[135]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6,10})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[1-35689]\\d{4}|7\\d{10,11}|(?:[124-7]\\d|3[0-46-9])\\d{8}|[1-9]\\d{5,8}"},"noInternationalDialling": {"possibleLengths": {"national": "[5-12]"},"nationalNumberPattern": "20(?:2[023]|9[89])\\d{1,6}|(?:60[12]\\d|7099)\\d{4,5}|(?:606|7(?:0[78]|1|3\\d))\\d{7}|(?:[1-3]00|7(?:0[1-5]\\d\\d|5[03-9]))\\d{3,7}"},"fixedLine": {"possibleLengths": {"national": "[5-9]"},"exampleNumber": "131234567","nationalNumberPattern": "(?:1[3-79][1-8]|[235689][1-8]\\d)\\d{2,6}"},"mobile": {"possibleLengths": {"national": "[6-10]"},"exampleNumber": "412345678","nationalNumberPattern": "(?:4[0-8]|50)\\d{4,8}"},"tollFree": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{4,6}"},"premiumRate": {"possibleLengths": {"national": "8,9"},"exampleNumber": "600123456","nationalNumberPattern": "[67]00\\d{5,6}"},"uan": {"possibleLengths": {"national": "[5-12]"},"exampleNumber": "10112345","nationalNumberPattern": "20\\d{4,8}|60[12]\\d{5,6}|7(?:099\\d{4,5}|5[03-9]\\d{3,7})|20[2-59]\\d\\d|(?:606|7(?:0[78]|1|3\\d))\\d{7}|(?:10|29|3[09]|70[1-5]\\d)\\d{4,8}"}},{"id": "FJ","countryCode": "679","preferredInternationalPrefix": "00","internationalPrefix": "0(?:0|52)","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[235-9]|45","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "45\\d{5}|(?:0800\\d|[235-9])\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "3212345","nationalNumberPattern": "603\\d{4}|(?:3[0-5]|6[25-7]|8[58])\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7012345","nationalNumberPattern": "(?:[279]\\d|45|5[01568]|8[034679])\\d{5}"},"tollFree": {"possibleLengths": {"national": "11"},"exampleNumber": "08001234567","nationalNumberPattern": "0800\\d{7}"}},{"id": "FK","countryCode": "500","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "[2-7]\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "31234","nationalNumberPattern": "[2-47]\\d{4}"},"mobile": {"possibleLengths": {"national": "5"},"exampleNumber": "51234","nationalNumberPattern": "[56]\\d{4}"}},{"id": "FM","countryCode": "691","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[39]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[39]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "3201234","nationalNumberPattern": "(?:3[2357]0[1-9]|9[2-6]\\d\\d)\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "3501234","nationalNumberPattern": "(?:3[2357]0[1-9]|9[2-7]\\d\\d)\\d{3}"}},{"id": "FO","countryCode": "298","internationalPrefix": "00","nationalPrefixForParsing": "(10(?:01|[12]0|88))","availableFormats": {"numberFormat": {"pattern": "(\\d{6})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[2-9]","format": "$1"}},"generalDesc": {"nationalNumberPattern": "(?:[2-8]\\d|90)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "201234","nationalNumberPattern": "(?:20|[34]\\d|8[19])\\d{4}"},"mobile": {"possibleLengths": {"national": "6"},"exampleNumber": "211234","nationalNumberPattern": "(?:[27][1-9]|5\\d)\\d{4}"},"tollFree": {"possibleLengths": {"national": "6"},"exampleNumber": "802123","nationalNumberPattern": "80[257-9]\\d{3}"},"premiumRate": {"possibleLengths": {"national": "6"},"exampleNumber": "901123","nationalNumberPattern": "90(?:[13-5][15-7]|2[125-7]|99)\\d\\d"},"voip": {"possibleLengths": {"national": "6"},"exampleNumber": "601234","nationalNumberPattern": "(?:6[0-36]|88)\\d{4}"}},{"id": "FR","countryCode": "33","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})","leadingDigits": "10","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "1","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "8","format": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-79]","format": "$1 $2 $3 $4 $5"}]},"generalDesc": {"nationalNumberPattern": "[1-9]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "123456789","nationalNumberPattern": "(?:[1-35]\\d|4[1-9])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "612345678","nationalNumberPattern": "700\\d{6}|(?:6\\d|7[3-9])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80[0-5]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "891123456","nationalNumberPattern": "836(?:0[0-36-9]|[1-9]\\d)\\d{4}|8(?:1[2-9]|2[2-47-9]|3[0-57-9]|[569]\\d|8[0-35-9])\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "884012345","nationalNumberPattern": "8(?:1[01]|2[0156]|84)\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "9\\d{8}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "806123456","nationalNumberPattern": "80[6-9]\\d{6}"}},{"id": "GA","countryCode": "241","internationalPrefix": "00","nationalPrefixForParsing": "0(11\\d{6}|6[256]\\d{6}|7[47]\\d{6})","nationalPrefixTransformRule": "$1","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "0$FG","leadingDigits": "[2-7]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "0$FG","leadingDigits": "11|[67]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "0","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[067]\\d|11)\\d{6}|[2-7]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "01441234","nationalNumberPattern": "[01]1\\d{6}"},"mobile": {"possibleLengths": {"national": "7,8"},"exampleNumber": "06031234","nationalNumberPattern": "(?:0[2-7]|6[256]|7[47])\\d{6}|[2-7]\\d{6}"}},{"id": "GB","mainCountryForCode": "true","countryCode": "44","internationalPrefix": "00","nationalPrefix": "0","preferredExtnPrefix": " x","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["800","8001","80011","800111","8001111"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["845","8454","84546","845464"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "800","format": "$1 $2"},{"pattern": "(\\d{5})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["1(?:38|5[23]|69|76|94)","1(?:(?:38|69)7|5(?:24|39)|768|946)","1(?:3873|5(?:242|39[4-6])|(?:697|768)[347]|9467)"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1(?:[2-69][02-9]|[78])","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[25]|7(?:0|6[024-9])","[25]|7(?:0|6(?:[04-9]|2[356]))"],"format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1389]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[1-357-9]\\d{9}|[18]\\d{8}|8\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9,10","localOnly": "[4-8]"},"exampleNumber": "1212345678","nationalNumberPattern": "(?:1(?:(?:1(?:3[0-58]|4[0-5]|5[0-26-9]|6[0-4]|[78][0-49])|3(?:0\\d|1[0-8]|[25][02-9]|3[02-579]|[468][0-46-9]|7[1-35-79]|9[2-578])|4(?:0[03-9]|[137]\\d|[28][02-57-9]|4[02-69]|5[0-8]|[69][0-79])|5(?:0[1-35-9]|[16]\\d|2[024-9]|3[015689]|4[02-9]|5[03-9]|7[0-35-9]|8[0-468]|9[0-57-9])|6(?:0[034689]|1\\d|2[0-35689]|[38][013-9]|4[1-467]|5[0-69]|6[13-9]|7[0-8]|9[0-24578])|7(?:0[0246-9]|2\\d|3[0236-8]|4[03-9]|5[0-46-9]|6[013-9]|7[0-35-9]|8[024-9]|9[02-9])|8(?:0[35-9]|2[1-57-9]|3[02-578]|4[0-578]|5[124-9]|6[2-69]|7\\d|8[02-9]|9[02569])|9(?:0[02-589]|[18]\\d|2[02-689]|3[1-57-9]|4[2-9]|5[0-579]|6[2-47-9]|7[0-24578]|9[2-57]))\\d\\d|2(?:(?:0[024-9]|2[3-9]|3[3-79]|4[1-689]|[58][02-9]|6[0-47-9]|7[013-9]|9\\d)\\d\\d|1(?:[0-7]\\d\\d|80[04589])))|2(?:0[01378]|3[0189]|4[017]|8[0-46-9]|9[0-2])\\d{3})\\d{4}|1(?:2(?:0(?:46[1-4]|87[2-9])|545[1-79]|76(?:2\\d|3[1-8]|6[1-6])|9(?:7(?:2[0-4]|3[2-5])|8(?:2[2-8]|7[0-47-9]|8[3-5])))|3(?:6(?:38[2-5]|47[23])|8(?:47[04-9]|64[0157-9]))|4(?:044[1-7]|20(?:2[23]|8\\d)|6(?:0(?:30|5[2-57]|6[1-8]|7[2-8])|140)|8(?:052|87[1-3]))|5(?:2(?:4(?:3[2-79]|6\\d)|76\\d)|6(?:26[06-9]|686))|6(?:06(?:4\\d|7[4-79])|295[5-7]|35[34]\\d|47(?:24|61)|59(?:5[08]|6[67]|74)|9(?:55[0-4]|77[23]))|7(?:26(?:6[13-9]|7[0-7])|(?:442|688)\\d|50(?:2[0-3]|[3-68]2|76))|8(?:27[56]\\d|37(?:5[2-5]|8[239])|843[2-58])|9(?:0(?:0(?:6[1-8]|85)|52\\d)|3583|4(?:66[1-8]|9(?:2[01]|81))|63(?:23|3[1-4])|9561))\\d{3}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7400123456","nationalNumberPattern": "7(?:457[0-57-9]|700[01]|911[028])\\d{5}|7(?:[1-3]\\d\\d|4(?:[0-46-9]\\d|5[0-689])|5(?:0[0-8]|[13-9]\\d|2[0-35-9])|7(?:0[1-9]|[1-7]\\d|8[02-9]|9[0-689])|8(?:[014-9]\\d|[23][0-8])|9(?:[024-9]\\d|1[02-9]|3[0-689]))\\d{6}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "7640123456","nationalNumberPattern": "76(?:0[0-2]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}"},"tollFree": {"possibleLengths": {"national": "7,9,10"},"exampleNumber": "8001234567","nationalNumberPattern": "80[08]\\d{7}|800\\d{6}|8001111"},"premiumRate": {"possibleLengths": {"national": "7,10"},"exampleNumber": "9012345678","nationalNumberPattern": "(?:8(?:4[2-5]|7[0-3])|9(?:[01]\\d|8[2-49]))\\d{7}|845464\\d"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "7012345678","nationalNumberPattern": "70\\d{8}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5612345678","nationalNumberPattern": "56\\d{8}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "(?:3[0347]|55)\\d{8}"}},{"id": "GD","countryCode": "1","leadingDigits": "473","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-9]\\d{6})$","nationalPrefixTransformRule": "473$1","generalDesc": {"nationalNumberPattern": "(?:473|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "4732691234","nationalNumberPattern": "473(?:2(?:3[0-2]|69)|3(?:2[89]|86)|4(?:[06]8|3[5-9]|4[0-49]|5[5-79]|73|90)|63[68]|7(?:58|84)|800|938)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "4734031234","nationalNumberPattern": "473(?:4(?:0[2-79]|1[04-9]|2[0-5]|58)|5(?:2[01]|3[3-8])|901)\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "GE","countryCode": "995","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "70","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "32","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[57]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[348]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[3-57]\\d\\d|800)\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "9"},"nationalNumberPattern": "706\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "6,7"},"exampleNumber": "322123456","nationalNumberPattern": "(?:3(?:[256]\\d|4[124-9]|7[0-4])|4(?:1\\d|2[2-7]|3[1-79]|4[2-8]|7[239]|9[1-7]))\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "555123456","nationalNumberPattern": "5(?:0555[5-9]|757(?:7[7-9]|8[01]))\\d{3}|5(?:000\\d|(?:52|75)00|8(?:58[89]|888))\\d{4}|5(?:0050|1111|2222|3333)[0-4]\\d{3}|(?:5(?:[14]4|5[0157-9]|68|7[0147-9]|9[1-35-9])|790)\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "706123456","nationalNumberPattern": "706\\d{6}"}},{"id": "GF","countryCode": "594","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[569]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:[56]94|976)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "594101234","nationalNumberPattern": "594(?:[023]\\d|1[01]|4[03-9]|5[6-9]|6[0-3]|80|9[014])\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "694201234","nationalNumberPattern": "694(?:[0-249]\\d|3[0-48])\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "976012345","nationalNumberPattern": "976\\d{6}"}},{"id": "GG","countryCode": "44","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0|([25-9]\\d{5})$","nationalPrefixTransformRule": "1481$1","generalDesc": {"nationalNumberPattern": "(?:1481|[357-9]\\d{3})\\d{6}|8\\d{6}(?:\\d{2})?"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "6"},"exampleNumber": "1481256789","nationalNumberPattern": "1481[25-9]\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7781123456","nationalNumberPattern": "7(?:(?:781|839)\\d|911[17])\\d{5}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "7640123456","nationalNumberPattern": "76(?:0[0-2]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}"},"tollFree": {"possibleLengths": {"national": "7,9,10"},"exampleNumber": "8001234567","nationalNumberPattern": "80[08]\\d{7}|800\\d{6}|8001111"},"premiumRate": {"possibleLengths": {"national": "7,10"},"exampleNumber": "9012345678","nationalNumberPattern": "(?:8(?:4[2-5]|7[0-3])|9(?:[01]\\d|8[0-3]))\\d{7}|845464\\d"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "7012345678","nationalNumberPattern": "70\\d{8}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5612345678","nationalNumberPattern": "56\\d{8}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "(?:3[0347]|55)\\d{8}"}},{"id": "GH","countryCode": "233","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[237]|80","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[235]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[235]\\d{3}|800)\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "800\\d{5}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "302345678","nationalNumberPattern": "3(?:[167]2[0-6]|22[0-5]|32[0-3]|4(?:2[013-9]|3[01])|52[0-7]|82[0-2])\\d{5}|3(?:[0-8]8|9[28])0\\d{5}|3(?:0[237]|[1-9]7)\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "231234567","nationalNumberPattern": "(?:2[0346-8]\\d|5(?:[0457]\\d|6[01]|9[1-6]))\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"}},{"id": "GI","countryCode": "350","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{5})","leadingDigits": "2","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[256]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "20012345","nationalNumberPattern": "21(?:6[24-7]\\d|90[0-2])\\d{3}|2(?:00|2[25])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "57123456","nationalNumberPattern": "(?:5[146-8]\\d|6(?:06|29))\\d{5}"}},{"id": "GL","countryCode": "299","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "19|[2-689]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:19|[2-689]\\d)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "321000","nationalNumberPattern": "(?:19|3[1-7]|6[14689]|8[14-79]|9\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "6"},"exampleNumber": "221234","nationalNumberPattern": "(?:[25][1-9]|4[2-9])\\d{4}"},"tollFree": {"possibleLengths": {"national": "6"},"exampleNumber": "801234","nationalNumberPattern": "80\\d{4}"},"voip": {"possibleLengths": {"national": "6"},"exampleNumber": "381234","nationalNumberPattern": "3[89]\\d{4}"}},{"id": "GM","countryCode": "220","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "[2-9]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "5661234","nationalNumberPattern": "(?:4(?:[23]\\d\\d|4(?:1[024679]|[6-9]\\d))|5(?:54[0-7]|6[67]\\d|7(?:1[04]|2[035]|3[58]|48))|8\\d{3})\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "3012345","nationalNumberPattern": "(?:[23679]\\d|5[0-3])\\d{5}"}},{"id": "GN","countryCode": "224","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "3","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[67]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:30|6\\d\\d|722)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "30241234","nationalNumberPattern": "30(?:24|3[12]|4[1-35-7]|5[13]|6[189]|[78]1|9[1478])\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "601123456","nationalNumberPattern": "6[02356]\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "722123456","nationalNumberPattern": "722\\d{6}"}},{"id": "GP","mainCountryForCode": "true","countryCode": "590","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[569]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:590|69\\d|976)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "590201234","nationalNumberPattern": "590(?:0[1-68]|1[0-2]|2[0-68]|3[1289]|4[0-24-9]|5[3-579]|6[0189]|7[08]|8[0-689]|9\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "690001234","nationalNumberPattern": "69(?:0\\d\\d|1(?:2[29]|3[0-5]))\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "976012345","nationalNumberPattern": "976[01]\\d{5}"}},{"id": "GQ","countryCode": "240","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[235]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{6})","leadingDigits": "[89]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "222\\d{6}|(?:3\\d|55|[89]0)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "333091234","nationalNumberPattern": "33[0-24-9]\\d[46]\\d{4}|3(?:33|5\\d)\\d[7-9]\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "222123456","nationalNumberPattern": "(?:222|55[015])\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "80\\d[1-9]\\d{5}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "90\\d[1-9]\\d{5}"}},{"id": "GR","countryCode": "30","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{4})(\\d{4})","leadingDigits": "21|7","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{6})","leadingDigits": "2(?:2|3[2-57-9]|4[2-469]|5[2-59]|6[2-9]|7[2-69]|8[2-49])|5","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "[2689]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "5005000\\d{3}|(?:[2689]\\d|70)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10"},"exampleNumber": "2123456789","nationalNumberPattern": "2(?:1\\d\\d|2(?:2[1-46-9]|[36][1-8]|4[1-7]|5[1-4]|7[1-5]|[89][1-9])|3(?:1\\d|2[1-57]|[35][1-3]|4[13]|7[1-7]|8[124-6]|9[1-79])|4(?:1\\d|2[1-8]|3[1-4]|4[13-5]|6[1-578]|9[1-5])|5(?:1\\d|[29][1-4]|3[1-5]|4[124]|5[1-6])|6(?:1\\d|[269][1-6]|3[1245]|4[1-7]|5[13-9]|7[14]|8[1-5])|7(?:1\\d|2[1-5]|3[1-6]|4[1-7]|5[1-57]|6[135]|9[125-7])|8(?:1\\d|2[1-5]|[34][1-4]|9[1-57]))\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "6912345678","nationalNumberPattern": "68[57-9]\\d{7}|(?:69|94)\\d{8}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9091234567","nationalNumberPattern": "90[19]\\d{7}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "8011234567","nationalNumberPattern": "8(?:0[16]|12|25)\\d{7}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "7012345678","nationalNumberPattern": "70\\d{8}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "5005000123","nationalNumberPattern": "5005000\\d{3}"}},{"id": "GT","countryCode": "502","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[2-7]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:1\\d{3}|[2-7])\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22456789","nationalNumberPattern": "[267][2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "51234567","nationalNumberPattern": "[3-5]\\d{7}"},"tollFree": {"possibleLengths": {"national": "11"},"exampleNumber": "18001112222","nationalNumberPattern": "18[01]\\d{8}"},"premiumRate": {"possibleLengths": {"national": "11"},"exampleNumber": "19001112222","nationalNumberPattern": "19\\d{9}"}},{"id": "GU","countryCode": "1","leadingDigits": "671","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([3-9]\\d{6})$","nationalPrefixTransformRule": "671$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|671|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6713001234","nationalNumberPattern": "671(?:3(?:00|3[39]|4[349]|55|6[26])|4(?:00|56|7[1-9]|8[0236-9])|5(?:55|6[2-5]|88)|6(?:3[2-578]|4[24-9]|5[34]|78|8[235-9])|7(?:[0479]7|2[0167]|3[45]|8[7-9])|8(?:[2-57-9]8|6[48])|9(?:2[29]|6[79]|7[1279]|8[7-9]|9[78]))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6713001234","nationalNumberPattern": "671(?:3(?:00|3[39]|4[349]|55|6[26])|4(?:00|56|7[1-9]|8[0236-9])|5(?:55|6[2-5]|88)|6(?:3[2-578]|4[24-9]|5[34]|78|8[235-9])|7(?:[0479]7|2[0167]|3[45]|8[7-9])|8(?:[2-57-9]8|6[48])|9(?:2[29]|6[79]|7[1279]|8[7-9]|9[78]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "GW","countryCode": "245","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "40","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[49]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[49]\\d{8}|4\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "443201234","nationalNumberPattern": "443\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "955012345","nationalNumberPattern": "9(?:5\\d|6[569]|77)\\d{6}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "4012345","nationalNumberPattern": "40\\d{5}"}},{"id": "GY","countryCode": "592","internationalPrefix": "001","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-46-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:862\\d|9008)\\d{3}|(?:[2-46]\\d|77)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2201234","nationalNumberPattern": "(?:2(?:1[6-9]|2[0-35-9]|3[1-4]|5[3-9]|6\\d|7[0-24-79])|3(?:2[25-9]|3\\d)|4(?:4[0-24]|5[56])|77[1-57])\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "6091234","nationalNumberPattern": "6\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "2891234","nationalNumberPattern": "(?:289|862)\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9008123","nationalNumberPattern": "9008\\d{3}"}},{"id": "HK","countryCode": "852","preferredInternationalPrefix": "00","internationalPrefix": "00(?:30|5[09]|[126-9]?)","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2,5})","leadingDigits": ["900","9003"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[2-7]|8[1-4]|9(?:0[1-9]|[1-8])","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "9","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "8[0-46-9]\\d{6,7}|9\\d{4}(?:\\d(?:\\d(?:\\d{4})?)?)?|(?:[235-79]\\d|46)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "(?:384[0-24]|58(?:0[1-8]|1[2-9]))\\d{4}|(?:2(?:[13-8]\\d|2[013-9]|9[0-24-9])|3(?:[1569][0-24-9]|4[0-246-9]|7[0-24-69]|89))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "51234567","nationalNumberPattern": "(?:46(?:0[0-6]|1[0-2]|4[0-57-9])|5730|(?:626|848)[01]|707[1-5]|929[03-9])\\d{4}|(?:5(?:[1-59][0-46-9]|6[0-4689]|7[0-2469])|6(?:0[1-9]|[13-59]\\d|[268][0-57-9]|7[0-79])|9(?:0[1-9]|1[02-9]|[2358][0-8]|[467]\\d))\\d{5}"},"pager": {"possibleLengths": {"national": "8"},"exampleNumber": "71123456","nationalNumberPattern": "7(?:1(?:0[0-38]|1[0-3679]|3[013]|69|9[136])|2(?:[02389]\\d|1[18]|7[27-9])|3(?:[0-38]\\d|7[0-369]|9[2357-9])|47\\d|5(?:[178]\\d|5[0-5])|6(?:0[0-7]|2[236-9]|[35]\\d)|7(?:[27]\\d|8[7-9])|8(?:[23689]\\d|7[1-9])|9(?:[025]\\d|6[0-246-8]|7[0-36-9]|8[238]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "[5-8],11"},"exampleNumber": "90012345678","nationalNumberPattern": "900(?:[0-24-9]\\d{7}|3\\d{1,4})"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "81123456","nationalNumberPattern": "8(?:1[0-4679]\\d|2(?:[0-36]\\d|7[0-4])|3(?:[034]\\d|2[09]|70))\\d{4}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "30161234","nationalNumberPattern": "30(?:0[1-9]|[15-7]\\d|2[047]|89)\\d{4}"}},{"id": "HN","countryCode": "504","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[237-9]","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","leadingDigits": "8","format": "$1 $2 $3","intlFormat": "NA"}]},"generalDesc": {"nationalNumberPattern": "8\\d{10}|[237-9]\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "11"},"nationalNumberPattern": "8002\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22123456","nationalNumberPattern": "2(?:2(?:0[019]|1[1-36]|[23]\\d|4[04-6]|5[57]|6[24]|7[0135689]|8[01346-9]|9[0-2])|4(?:07|2[3-59]|3[13-689]|4[0-68]|5[1-35])|5(?:0[78]|16|4[03-5]|5\\d|6[014-6]|74|80)|6(?:[056]\\d|17|2[07]|3[04]|4[0-378]|[78][0-8]|9[01])|7(?:6[46-9]|7[02-9]|8[034]|91)|8(?:79|8[0-357-9]|9[1-57-9]))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "91234567","nationalNumberPattern": "[37-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "11"},"exampleNumber": "80021234567","nationalNumberPattern": "8002\\d{7}"}},{"id": "HR","countryCode": "385","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6[01]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[67]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-5]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[24-69]\\d|3[0-79])\\d{7}|80\\d{5,7}|[1-79]\\d{7}|6\\d{5,6}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "12345678","nationalNumberPattern": "1\\d{7}|(?:2[0-3]|3[1-5]|4[02-47-9]|5[1-3])\\d{6,7}"},"mobile": {"possibleLengths": {"national": "8,9"},"exampleNumber": "921234567","nationalNumberPattern": "9(?:751\\d{5}|8\\d{6,7})|9(?:0[1-9]|[1259]\\d|7[0679])\\d{6}"},"tollFree": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "800123456","nationalNumberPattern": "80[01]\\d{4,6}"},"premiumRate": {"possibleLengths": {"national": "[6-8]"},"exampleNumber": "611234","nationalNumberPattern": "6[01459]\\d{6}|6[01]\\d{4,5}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "74123456","nationalNumberPattern": "7[45]\\d{6}"},"uan": {"possibleLengths": {"national": "8,9"},"exampleNumber": "62123456","nationalNumberPattern": "62\\d{6,7}|72\\d{6}"}},{"id": "HT","countryCode": "509","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{4})","leadingDigits": "[2-489]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[2-489]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22453300","nationalNumberPattern": "2(?:2\\d|5[1-5]|81|9[149])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "34101234","nationalNumberPattern": "[34]\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "8\\d{7}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "98901234","nationalNumberPattern": "9(?:[67][0-4]|8[0-3589]|9\\d)\\d{5}"}},{"id": "HU","countryCode": "36","internationalPrefix": "00","nationalPrefix": "06","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP $FG)","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "($NP $FG)","leadingDigits": "[27][2-9]|3[2-7]|4[24-9]|5[2-79]|6|8[2-57-9]|9[2-69]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "[2-57-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2357]\\d{8}|[1-9]\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "[48]0\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6,7"},"exampleNumber": "12345678","nationalNumberPattern": "(?:1\\d|[27][2-9]|3[2-7]|4[24-9]|5[2-79]|6[23689]|8[2-57-9]|9[2-69])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "201234567","nationalNumberPattern": "(?:[257]0|3[01])\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "[48]0\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "9[01]\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "211234567","nationalNumberPattern": "21\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "381234567","nationalNumberPattern": "38\\d{7}"}},{"id": "ID","countryCode": "62","internationalPrefix": "00[189]","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{3})","leadingDigits": "15","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{5,9})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2[124]|[36]1","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "800","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5,8})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[2-79]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3,4})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8[1-35-9]","format": "$1-$2-$3"},{"pattern": "(\\d{3})(\\d{6,8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "804","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d)(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "80","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{4})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3 $4","intlFormat": "NA"}]},"generalDesc": {"nationalNumberPattern": "(?:(?:007803|8\\d{4})\\d|[1-36])\\d{6}|[1-9]\\d{8,10}|[2-9]\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "10,13"},"nationalNumberPattern": "(?:007803\\d|8071)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "[7-11]","localOnly": "5,6"},"exampleNumber": "218350123","nationalNumberPattern": "2[124]\\d{7,8}|619\\d{8}|2(?:1(?:14|500)|2\\d{3})\\d{3}|61\\d{5,8}|(?:2(?:[35][1-4]|6[0-8]|7[1-6]|8\\d|9[1-8])|3(?:1|[25][1-8]|3[1-68]|4[1-3]|6[1-3568]|7[0-469]|8\\d)|4(?:0[1-589]|1[01347-9]|2[0-36-8]|3[0-24-68]|43|5[1-378]|6[1-5]|7[134]|8[1245])|5(?:1[1-35-9]|2[25-8]|3[124-9]|4[1-3589]|5[1-46]|6[1-8])|6(?:[25]\\d|3[1-69]|4[1-6])|7(?:02|[125][1-9]|[36]\\d|4[1-8]|7[0-36-9])|9(?:0[12]|1[013-8]|2[0-479]|5[125-8]|6[23679]|7[159]|8[01346]))\\d{5,8}"},"mobile": {"possibleLengths": {"national": "[9-12]"},"exampleNumber": "812345678","nationalNumberPattern": "8[1-35-9]\\d{7,10}"},"tollFree": {"possibleLengths": {"national": "[8-11],13"},"exampleNumber": "8001234567","nationalNumberPattern": "007803\\d{7}|(?:177\\d|800)\\d{5,7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "8091234567","nationalNumberPattern": "809\\d{7}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "8041234567","nationalNumberPattern": "804\\d{7}"},"uan": {"possibleLengths": {"national": "7,10"},"exampleNumber": "8071123456","nationalNumberPattern": "(?:1500|8071\\d{3})\\d{3}"}},{"id": "IE","countryCode": "353","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2[24-9]|47|58|6[237-9]|9[35-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[45]0","format": "$1 $2"},{"pattern": "(\\d)(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[2569]|4[1-69]|7[14]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "70","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "81","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[78]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "4","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:1\\d|[2569])\\d{6,8}|4\\d{6,9}|7\\d{8}|8\\d{8,9}"},"noInternationalDialling": {"possibleLengths": {"national": "10"},"nationalNumberPattern": "18[59]0\\d{6}"},"fixedLine": {"possibleLengths": {"national": "[7-10]","localOnly": "5,6"},"exampleNumber": "2212345","nationalNumberPattern": "(?:1\\d|21)\\d{6,7}|(?:2[24-9]|4(?:0[24]|5\\d|7)|5(?:0[45]|1\\d|8)|6(?:1\\d|[237-9])|9(?:1\\d|[35-9]))\\d{5}|(?:23|4(?:[1-469]|8\\d)|5[23679]|6[4-6]|7[14]|9[04])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "850123456","nationalNumberPattern": "8(?:22|[35-9]\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "1800123456","nationalNumberPattern": "1800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1520123456","nationalNumberPattern": "15(?:1[2-8]|[2-8]0|9[089])\\d{6}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "1850123456","nationalNumberPattern": "18[59]0\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "700123456","nationalNumberPattern": "700\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "761234567","nationalNumberPattern": "76\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "818123456","nationalNumberPattern": "818\\d{6}"},"voicemail": {"possibleLengths": {"national": "10"},"exampleNumber": "8551234567","nationalNumberPattern": "88210[1-9]\\d{4}|8(?:[35-79]5\\d\\d|8(?:[013-9]\\d\\d|2(?:[01][1-9]|[2-9]\\d)))\\d{5}"}},{"id": "IL","countryCode": "972","internationalPrefix": "0(?:0|1[2-9])","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{3})","leadingDigits": "125","format": "$1-$2"},{"pattern": "(\\d{4})(\\d{2})(\\d{2})","leadingDigits": "121","format": "$1-$2-$3"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-489]","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[57]","format": "$1-$2-$3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "12","format": "$1-$2-$3"},{"pattern": "(\\d{4})(\\d{6})","leadingDigits": "159","format": "$1-$2"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "1[7-9]","format": "$1-$2-$3-$4"},{"pattern": "(\\d{3})(\\d{1,2})(\\d{3})(\\d{4})","leadingDigits": "15","format": "$1-$2 $3-$4"}]},"generalDesc": {"nationalNumberPattern": "1\\d{6}(?:\\d{3,5})?|[57]\\d{8}|[1-489]\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "10"},"nationalNumberPattern": "1700\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8,11,12","localOnly": "7"},"exampleNumber": "21234567","nationalNumberPattern": "153\\d{8,9}|[2-489]\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "502345678","nationalNumberPattern": "5(?:(?:[0-389][2-9]|4[1-9]|6\\d)\\d|5(?:01|2[2-7]|3[23]|4[45]|5[05689]|6[6-8]|7[0-267]|8[7-9]|9[1-9]))\\d{5}"},"tollFree": {"possibleLengths": {"national": "7,10"},"exampleNumber": "1800123456","nationalNumberPattern": "1(?:255|80[019]\\d{3})\\d{3}"},"premiumRate": {"possibleLengths": {"national": "8,10"},"exampleNumber": "1919123456","nationalNumberPattern": "1212\\d{4}|1(?:200|9(?:0[01]|19))\\d{6}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "1700123456","nationalNumberPattern": "1700\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "771234567","nationalNumberPattern": "78(?:33|55|77|81)\\d{5}|7(?:18|2[23]|3[237]|47|6[58]|7\\d|82|9[235-9])\\d{6}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "1599123456","nationalNumberPattern": "1599\\d{6}"},"voicemail": {"possibleLengths": {"national": "11,12"},"exampleNumber": "15112340000","nationalNumberPattern": "151\\d{8,9}"}},{"id": "IM","countryCode": "44","leadingDigits": "74576|(?:16|7[56])24","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0|([5-8]\\d{5})$","nationalPrefixTransformRule": "1624$1","generalDesc": {"nationalNumberPattern": "1624\\d{6}|(?:[3578]\\d|90)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "6"},"exampleNumber": "1624756789","nationalNumberPattern": "1624[5-8]\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7924123456","nationalNumberPattern": "76245[06]\\d{4}|7(?:4576|[59]24\\d|624[0-4689])\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8081624567","nationalNumberPattern": "808162\\d{4}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9016247890","nationalNumberPattern": "8(?:440[49]06|72299\\d)\\d{3}|(?:8(?:45|70)|90[0167])624\\d{4}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "7012345678","nationalNumberPattern": "70\\d{8}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5612345678","nationalNumberPattern": "56\\d{8}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "3440[49]06\\d{3}|(?:3(?:08162|3\\d{4}|45624|7(?:0624|2299))|55\\d{4})\\d{4}"}},{"id": "IN","countryCode": "91","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{7})","leadingDigits": "575","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{8})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["5(?:0|2[23]|3[03]|[67]1|88)","5(?:0|2(?:21|3)|3(?:0|3[23])|616|717|888)","5(?:0|2(?:21|3)|3(?:0|3[23])|616|717|8888)"],"format": "$1"},{"pattern": "(\\d{4})(\\d{4,5})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["180","1800"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "140","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["11|2[02]|33|4[04]|79[1-7]|80[2-46]","11|2[02]|33|4[04]|79(?:[1-6]|7[19])|80(?:[2-4]|6[0-589])","11|2[02]|33|4[04]|79(?:[124-6]|3(?:[02-9]|1[0-24-9])|7(?:1|9[1-6]))|80(?:[2-4]|6[0-589])"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["1(?:2[0-249]|3[0-25]|4[145]|[68]|7[1257])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|5[12]|6[0-26-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:1[025]|22|[36][25]|4[28]|5[12]|[78]1)|6(?:12|[2-4]1|5[17]|6[13]|80)|7(?:12|3[134]|4[47]|61|88)|8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)|(?:43|59|75)[15]|(?:1[59]|29|67|72)[14]","1(?:2[0-24]|3[0-25]|4[145]|[59][14]|6[1-9]|7[1257]|8[1-57-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[058]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[0-26-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:1[025]|22|[36][25]|4[28]|[578]1|9[15])|674|7(?:(?:2[14]|3[34]|5[15])[2-6]|61[346]|88[0-8])|8(?:70[2-6]|84[235-7]|91[3-7])|(?:1(?:29|60|8[06])|261|552|6(?:12|[2-47]1|5[17]|6[13]|80)|7(?:12|31|4[47])|8(?:16|2[014]|3[126]|6[136]|7[78]|83))[2-7]","1(?:2[0-24]|3[0-25]|4[145]|[59][14]|6[1-9]|7[1257]|8[1-57-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[058]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[0-26-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:1[025]|22|[36][25]|4[28]|[578]1|9[15])|6(?:12(?:[2-6]|7[0-8])|74[2-7])|7(?:(?:2[14]|5[15])[2-6]|3171|61[346]|88(?:[2-7]|82))|8(?:70[2-6]|84(?:[2356]|7[19])|91(?:[3-6]|7[19]))|73[134][2-6]|(?:74[47]|8(?:16|2[014]|3[126]|6[136]|7[78]|83))(?:[2-6]|7[19])|(?:1(?:29|60|8[06])|261|552|6(?:[2-4]1|5[17]|6[13]|7(?:1|4[0189])|80)|7(?:12|88[01]))[2-7]"],"format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["1(?:[2-479]|5[0235-9])|[2-5]|6(?:1[1358]|2[2457-9]|3[2-5]|4[235-7]|5[2-689]|6[24578]|7[235689]|8[1-6])|7(?:1[013-9]|28|3[129]|4[1-35689]|5[29]|6[02-5]|70)|807","1(?:[2-479]|5[0235-9])|[2-5]|6(?:1[1358]|2(?:[2457]|84|95)|3(?:[2-4]|55)|4[235-7]|5[2-689]|6[24578]|7[235689]|8[1-6])|7(?:1(?:[013-8]|9[6-9])|28[6-8]|3(?:17|2[0-49]|9[2-57])|4(?:1[2-4]|[29][0-7]|3[0-8]|[56]|8[0-24-7])|5(?:2[1-3]|9[0-6])|6(?:0[5689]|2[5-9]|3[02-8]|4|5[0-367])|70[13-7])|807[19]","1(?:[2-479]|5(?:[0236-9]|5[013-9]))|[2-5]|6(?:2(?:84|95)|355|83)|73179|807(?:1|9[1-3])|(?:1552|6(?:1[1358]|2[2457]|3[2-4]|4[235-7]|5[2-689]|6[24578]|7[235689]|8[124-6])\\d|7(?:1(?:[013-8]\\d|9[6-9])|28[6-8]|3(?:2[0-49]|9[2-57])|4(?:1[2-4]|[29][0-7]|3[0-8]|[56]\\d|8[0-24-7])|5(?:2[1-3]|9[0-6])|6(?:0[5689]|2[5-9]|3[02-8]|4\\d|5[0-367])|70[13-7]))[2-7]"],"format": "$1 $2 $3"},{"pattern": "(\\d{5})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[6-9]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{2,4})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["1(?:6|8[06])","1(?:6|8[06]0)"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3 $4","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})(\\d{3})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "18","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:000800|[2-9]\\d\\d)\\d{7}|1\\d{7,12}"},"noInternationalDialling": {"possibleLengths": {"national": "[8-13]"},"nationalNumberPattern": "1(?:600\\d{6}|800\\d{4,9})|(?:000800|18(?:03\\d\\d|6(?:0|[12]\\d\\d)))\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "[6-8]"},"exampleNumber": "7410410123","nationalNumberPattern": "2717(?:[2-7]\\d|95)\\d{4}|(?:271[0-689]|782[0-6])[2-7]\\d{5}|(?:170[24]|2(?:(?:[02][2-79]|90)\\d|80[13468])|(?:3(?:23|80)|683|79[1-7])\\d|4(?:20[24]|72[2-8])|552[1-7])\\d{6}|(?:11|33|4[04]|80)[2-7]\\d{7}|(?:342|674|788)(?:[0189][2-7]|[2-7]\\d)\\d{5}|(?:1(?:2[0-249]|3[0-25]|4[145]|[59][14]|6[014]|7[1257]|8[01346])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[13]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[0-26-9]|7[014-9]|8[013-57]|9[014-7])|5(?:1[025]|22|[36][25]|4[28]|[578]1|9[15])|6(?:12|[2-47]1|5[17]|6[13]|80)|7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1)|8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91))[2-7]\\d{6}|(?:1(?:2[35-8]|3[346-9]|4[236-9]|[59][0235-9]|6[235-9]|7[34689]|8[257-9])|2(?:1[134689]|3[24-8]|4[2-8]|5[25689]|6[2-4679]|7[3-79]|8[2-479]|9[235-9])|3(?:01|1[79]|2[1245]|4[5-8]|5[125689]|6[235-7]|7[157-9]|8[2-46-8])|4(?:1[14578]|2[5689]|3[2-467]|5[4-7]|6[35]|73|8[2689]|9[2389])|5(?:[16][146-9]|2[14-8]|3[1346]|4[14-69]|5[46]|7[2-4]|8[2-8]|9[246])|6(?:1[1358]|2[2457]|3[2-4]|4[235-7]|5[2-689]|6[24578]|7[235689]|8[124-6])|7(?:1[013-9]|2[0235-9]|3[2679]|4[1-35689]|5[2-46-9]|[67][02-9]|8[013-7]|9[089])|8(?:1[1357-9]|2[235-8]|3[03-57-9]|4[0-24-9]|5\\d|6[2457-9]|7[1-6]|8[1256]|9[2-4]))\\d[2-7]\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "8123456789","nationalNumberPattern": "(?:61279|7(?:887[02-9]|9(?:313|79[07-9]))|8(?:079[04-9]|(?:84|91)7[02-8]))\\d{5}|(?:6(?:12|[2-47]1|5[17]|6[13]|80)[0189]|7(?:1(?:2[0189]|9[0-5])|2(?:[14][017-9]|8[0-59])|3(?:2[5-8]|[34][017-9]|9[016-9])|4(?:1[015-9]|[29][89]|39|8[389])|5(?:[15][017-9]|2[04-9]|9[7-9])|6(?:0[0-47]|1[0-257-9]|2[0-4]|3[19]|5[4589])|70[0289]|88[089]|97[02-8])|8(?:0(?:6[67]|7[02-8])|70[017-9]|84[01489]|91[0-289]))\\d{6}|(?:7(?:31|4[47])|8(?:16|2[014]|3[126]|6[136]|7[78]|83))(?:[0189]\\d|7[02-8])\\d{5}|(?:6(?:[09]\\d|1[04679]|2[03689]|3[05-9]|4[0489]|50|6[069]|7[07]|8[7-9])|7(?:0\\d|2[0235-79]|3[05-8]|40|5[0346-8]|6[6-9]|7[1-9]|8[0-79]|9[089])|8(?:0[01589]|1[0-57-9]|2[235-9]|3[03-57-9]|[45]\\d|6[02457-9]|7[1-69]|8[0-25-9]|9[02-9])|9\\d\\d)\\d{7}|(?:6(?:(?:1[1358]|2[2457]|3[2-4]|4[235-7]|5[2-689]|6[24578]|8[124-6])\\d|7(?:[235689]\\d|4[0189]))|7(?:1(?:[013-8]\\d|9[6-9])|28[6-8]|3(?:2[0-49]|9[2-5])|4(?:1[2-4]|[29][0-7]|3[0-8]|[56]\\d|8[0-24-7])|5(?:2[1-3]|9[0-6])|6(?:0[5689]|2[5-9]|3[02-8]|4\\d|5[0-367])|70[13-7]|881))[0189]\\d{5}"},"tollFree": {"possibleLengths": {"national": "[8-13]"},"exampleNumber": "1800123456","nationalNumberPattern": "000800\\d{7}|1(?:600\\d{6}|80(?:0\\d{4,9}|3\\d{9}))"},"premiumRate": {"possibleLengths": {"national": "13"},"exampleNumber": "1861123456789","nationalNumberPattern": "186[12]\\d{9}"},"sharedCost": {"possibleLengths": {"national": "11"},"exampleNumber": "18603451234","nationalNumberPattern": "1860\\d{7}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "1409305260","nationalNumberPattern": "140\\d{7}"}},{"id": "IO","countryCode": "246","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "3","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "3\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "3709100","nationalNumberPattern": "37\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "3801234","nationalNumberPattern": "38\\d{5}"}},{"id": "IQ","countryCode": "964","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-6]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:1|7\\d\\d)\\d{7}|[2-6]\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "12345678","nationalNumberPattern": "1\\d{7}|(?:2[13-5]|3[02367]|4[023]|5[03]|6[026])\\d{6,7}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7912345678","nationalNumberPattern": "7[3-9]\\d{8}"}},{"id": "IR","countryCode": "98","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "96","format": "$1"},{"pattern": "(\\d{2})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:1[137]|2[13-68]|3[1458]|4[145]|5[1468]|6[16]|7[1467]|8[13467])[12689]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-8]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[1-9]\\d{9}|(?:[1-8]\\d\\d|9)\\d{3,4}"},"noInternationalDialling": {"possibleLengths": {"national": "4,5,10"},"nationalNumberPattern": "9(?:4440\\d{5}|6(?:0[12]|2[16-8]|3(?:08|[14]5|[23]|66)|4(?:0|80)|5[01]|6[89]|86|9[19]))"},"fixedLine": {"possibleLengths": {"national": "6,7,10","localOnly": "4,5,8"},"exampleNumber": "2123456789","nationalNumberPattern": "(?:1[137]|2[13-68]|3[1458]|4[145]|5[1468]|6[16]|7[1467]|8[13467])(?:[03-57]\\d{7}|[16]\\d{3}(?:\\d{4})?|[289]\\d{3}(?:\\d(?:\\d{3})?)?)|94(?:000[09]|2(?:121|[2689]0\\d)|30[0-2]\\d|4(?:111|40\\d))\\d{4}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "9123456789","nationalNumberPattern": "9(?:(?:0(?:[1-35]\\d|44)|(?:[13]\\d|2[0-2])\\d)\\d|9(?:(?:[0-2]\\d|44)\\d|5[15]0|8(?:1\\d|88)|9(?:0[013]|1[0134]|21|77|9[6-9])))\\d{5}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "9932123456","nationalNumberPattern": "993\\d{7}"},"uan": {"possibleLengths": {"national": "4,5"},"exampleNumber": "9601","nationalNumberPattern": "96(?:0[12]|2[16-8]|3(?:08|[14]5|[23]|66)|4(?:0|80)|5[01]|6[89]|86|9[19])"}},{"id": "IS","countryCode": "354","preferredInternationalPrefix": "00","internationalPrefix": "00|1(?:0(?:01|[12]0)|100)","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[4-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "3","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:38\\d|[4-9])\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "4101234","nationalNumberPattern": "(?:4(?:1[0-24-69]|2[0-7]|[37][0-8]|4[0-245]|5[0-68]|6\\d|8[0-36-8])|5(?:05|[156]\\d|2[02578]|3[0-579]|4[03-7]|7[0-2578]|8[0-35-9]|9[013-689])|872)\\d{4}"},"mobile": {"possibleLengths": {"national": "7,9"},"exampleNumber": "6111234","nationalNumberPattern": "(?:38[589]\\d\\d|6(?:1[1-8]|2[0-6]|3[027-9]|4[014679]|5[0159]|6[0-69]|70|8[06-8]|9\\d)|7(?:5[057]|[6-9]\\d)|8(?:2[0-59]|[3-69]\\d|8[28]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "80[08]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9001234","nationalNumberPattern": "90(?:0\\d|1[5-79]|2[015-79]|3[135-79]|4[125-7]|5[25-79]|7[1-37]|8[0-35-7])\\d{3}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "4921234","nationalNumberPattern": "49[0-24-79]\\d{4}"},"uan": {"possibleLengths": {"national": "7"},"exampleNumber": "8091234","nationalNumberPattern": "809\\d{4}"},"voicemail": {"possibleLengths": {"national": "7"},"exampleNumber": "6891234","nationalNumberPattern": "(?:689|8(?:7[18]|80)|95[48])\\d{4}"}},{"id": "IT","mainCountryForCode": "true","countryCode": "39","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4,5})","leadingDigits": ["1(?:0|9[246])","1(?:0|9(?:2[2-9]|[46]))"],"format": "$1","intlFormat": "NA"},{"pattern": "(\\d{6})","leadingDigits": "1(?:1|92)","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{4,6})","leadingDigits": "0[26]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3,6})","leadingDigits": ["0[13-57-9][0159]|8(?:03|4[17]|9[245])","0[13-57-9][0159]|8(?:03|4[17]|9(?:2|[45][0-4]))"],"format": "$1 $2"},{"pattern": "(\\d{4})(\\d{2,6})","leadingDigits": "0(?:[13-579][2-46-8]|8[236-8])","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "894","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3,4})(\\d{4})","leadingDigits": "0[26]|5","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","leadingDigits": "1[4679]|[38]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3,4})(\\d{4})","leadingDigits": "0[13-57-9][0159]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{5})","leadingDigits": "0[26]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{4,5})","leadingDigits": "3","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "0\\d{5,10}|3[0-8]\\d{7,10}|55\\d{8}|8\\d{5}(?:\\d{2,4})?|(?:1\\d|39)\\d{7,8}"},"noInternationalDialling": {"possibleLengths": {"national": "9"},"nationalNumberPattern": "848\\d{6}"},"fixedLine": {"possibleLengths": {"national": "[6-11]"},"exampleNumber": "0212345678","nationalNumberPattern": "0669[0-79]\\d{1,6}|0(?:1(?:[0159]\\d|[27][1-5]|31|4[1-4]|6[1356]|8[2-57])|2\\d\\d|3(?:[0159]\\d|2[1-4]|3[12]|[48][1-6]|6[2-59]|7[1-7])|4(?:[0159]\\d|[23][1-9]|4[245]|6[1-5]|7[1-4]|81)|5(?:[0159]\\d|2[1-5]|3[2-6]|4[1-79]|6[4-6]|7[1-578]|8[3-8])|6(?:[0-57-9]\\d|6[0-8])|7(?:[0159]\\d|2[12]|3[1-7]|4[2-46]|6[13569]|7[13-6]|8[1-59])|8(?:[0159]\\d|2[3-578]|3[1-356]|[6-8][1-5])|9(?:[0159]\\d|[238][1-5]|4[12]|6[1-8]|7[1-6]))\\d{2,7}"},"mobile": {"possibleLengths": {"national": "9,10"},"exampleNumber": "3123456789","nationalNumberPattern": "3[1-9]\\d{8}|3[2-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "6,9"},"exampleNumber": "800123456","nationalNumberPattern": "80(?:0\\d{3}|3)\\d{3}"},"premiumRate": {"possibleLengths": {"national": "6,[8-10]"},"exampleNumber": "899123456","nationalNumberPattern": "(?:0878\\d\\d|89(?:2|4[5-9]\\d))\\d{3}|89[45][0-4]\\d\\d|(?:1(?:44|6[346])|89(?:5[5-9]|9))\\d{6}"},"sharedCost": {"possibleLengths": {"national": "6,9"},"exampleNumber": "848123456","nationalNumberPattern": "84(?:[08]\\d{3}|[17])\\d{3}"},"personalNumber": {"possibleLengths": {"national": "9,10"},"exampleNumber": "1781234567","nationalNumberPattern": "1(?:78\\d|99)\\d{6}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "55\\d{8}"},"voicemail": {"possibleLengths": {"national": "11,12"},"exampleNumber": "33101234501","nationalNumberPattern": "3[2-8]\\d{9,10}"}},{"id": "JE","countryCode": "44","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0|([0-24-8]\\d{5})$","nationalPrefixTransformRule": "1534$1","generalDesc": {"nationalNumberPattern": "1534\\d{6}|(?:[3578]\\d|90)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "6"},"exampleNumber": "1534456789","nationalNumberPattern": "1534[0-24-8]\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7797712345","nationalNumberPattern": "7(?:(?:(?:50|82)9|937)\\d|7(?:00[378]|97[7-9]))\\d{5}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "7640123456","nationalNumberPattern": "76(?:0[0-2]|2[356]|4[0134]|5[49]|6[0-369]|77|81|9[39])\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8007354567","nationalNumberPattern": "80(?:07(?:35|81)|8901)\\d{4}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9018105678","nationalNumberPattern": "(?:8(?:4(?:4(?:4(?:05|42|69)|703)|5(?:041|800))|7(?:0002|1206))|90(?:066[59]|1810|71(?:07|55)))\\d{4}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "7015115678","nationalNumberPattern": "701511\\d{4}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5612345678","nationalNumberPattern": "56\\d{8}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "(?:3(?:0(?:07(?:35|81)|8901)|3\\d{4}|4(?:4(?:4(?:05|42|69)|703)|5(?:041|800))|7(?:0002|1206))|55\\d{4})\\d{4}"}},{"id": "JM","countryCode": "1","leadingDigits": "658|876","internationalPrefix": "011","nationalPrefix": "1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|658|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8765230123","nationalNumberPattern": "(?:658(?:2(?:[0-8]\\d|9[0-46-9])|[3-9]\\d\\d)|876(?:5(?:02|1[0-468]|2[35]|63)|6(?:0[1-3579]|1[0237-9]|[23]\\d|40|5[06]|6[2-589]|7[05]|8[04]|9[4-9])|7(?:0[2-689]|[1-6]\\d|8[056]|9[45])|9(?:0[1-8]|1[02378]|[2-8]\\d|9[2-468])))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8762101234","nationalNumberPattern": "(?:658295|876(?:(?:2[14-9]|[348]\\d)\\d|5(?:0[13-9]|17|[2-57-9]\\d|6[0-24-9])|7(?:0[07]|7\\d|8[1-47-9]|9[0-36-9])|9(?:[01]9|9[0579])))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "JO","countryCode": "962","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[2356]|87","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "70","format": "$1 $2"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "900\\d{5}|(?:(?:[268]|7\\d)\\d|32|53)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "62001234","nationalNumberPattern": "(?:2(?:6(?:2[0-35-9]|3[0-578]|4[24-7]|5[0-24-8]|[6-8][023]|9[0-3])|7(?:0[1-79]|10|2[014-7]|3[0-689]|4[019]|5[0-3578]))|32(?:0[1-69]|1[1-35-7]|2[024-7]|3\\d|4[0-3]|[57][023]|6[03])|53(?:0[0-3]|[13][023]|2[0-59]|49|5[0-35-9]|6[15]|7[45]|8[1-6]|9[0-36-9])|6(?:2(?:[05]0|22)|3(?:00|33)|4(?:0[0-25]|1[2-7]|2[0569]|[38][07-9]|4[025689]|6[0-589]|7\\d|9[0-2])|5(?:[01][056]|2[034]|3[0-57-9]|4[178]|5[0-69]|6[0-35-9]|7[1-379]|8[0-68]|9[0239]))|87(?:[029]0|7[08]))\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "790123456","nationalNumberPattern": "7(?:55[0-49]|(?:7[025-9]|8[0-25-9]|9\\d)\\d)\\d{5}"},"pager": {"possibleLengths": {"national": "9"},"exampleNumber": "746612345","nationalNumberPattern": "74(?:66|77)\\d{5}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "80\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "900\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "85012345","nationalNumberPattern": "85\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "700123456","nationalNumberPattern": "70\\d{7}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "88101234","nationalNumberPattern": "8(?:10|8\\d)\\d{5}"}},{"id": "JP","countryCode": "81","internationalPrefix": "010","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{4})","leadingDigits": ["007","0077","00777","00777[01]"],"format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:12|57|99)0","format": "$1-$2-$3"},{"pattern": "(\\d{4})(\\d)(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["1(?:26|3[79]|4[56]|5[4-68]|6[3-5])|499|5(?:76|97)|746|8(?:3[89]|47|51|63)|9(?:49|80|9[16])","1(?:267|3(?:7[247]|9[278])|466|5(?:47|58|64)|6(?:3[245]|48|5[4-68]))|499[2468]|5(?:76|97)9|7468|8(?:3(?:8[78]|96)|477|51[24]|636)|9(?:496|802|9(?:1[23]|69))|1(?:45|58)[67]","1(?:267|3(?:7[247]|9[278])|466|5(?:47|58|64)|6(?:3[245]|48|5[4-68]))|499[2468]|5(?:769|979[2-69])|7468|8(?:3(?:8[78]|96[2457-9])|477|51[24]|636[2-57-9])|9(?:496|802|9(?:1[23]|69))|1(?:45|58)[67]"],"format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "60","format": "$1-$2-$3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[36]|4(?:2[09]|7[01])","[36]|4(?:2(?:0|9[02-69])|7(?:0[019]|1))"],"format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["1(?:1|5[45]|77|88|9[69])|2(?:2[1-37]|3[0-269]|4[59]|5|6[24]|7[1-358]|8[1369]|9[0-38])|4(?:[28][1-9]|3[0-57]|[45]|6[248]|7[2-579]|9[29])|5(?:2|3[045]|4[0-369]|5[29]|8[02389]|9[0-389])|7(?:2[02-46-9]|34|[58]|6[0249]|7[57]|9[2-6])|8(?:2[124589]|3[279]|49|6[0-24-689]|7[0-468]|8[68]|9[019])|9(?:[23][1-9]|4[15]|5[138]|6[1-3]|7[156]|8[189]|9[1-489])","1(?:1|5(?:4[018]|5[017])|77|88|9[69])|2(?:2(?:[127]|3[014-9])|3[0-269]|4[59]|5(?:[0468][01]|[1-3]|5[0-69]|9[19])|62|7(?:[1-35]|8[0189])|8(?:[16]|3[0134]|9[0-5])|9(?:[028]|17))|4(?:2(?:[13-79]|2[01]|8[014-6])|3[0-57]|[45]|6[248]|7[2-47]|8[1-9])|5(?:2|3[045]|4[0-369]|8[02389]|9[0-3])|7(?:2[02-46-9]|34|[58]|6[0249]|7[57]|9(?:[23]|4[0-59]|5[01569]|6[0167]))|8(?:2(?:[1258]|4[0-39]|9[0-2469])|49|6(?:[0-24]|5[0-3589]|9[01459])|7[0-468]|8[68])|9(?:[23][1-9]|4[15]|5[138]|6[1-3]|7[156]|8[189]|9(?:[1289]|3[34]|4[0178]))|(?:49|55|83)[29]|(?:264|837)[016-9]|2(?:57|93)[015-9]|(?:47[59]|59[89]|8(?:6[68]|9))[019]","1(?:1|5(?:4[018]|5[017])|77|88|9[69])|2(?:2[127]|3[0-269]|4[59]|5(?:[0468][01]|[1-3]|5[0-69]|9(?:17|99))|6(?:2|4[016-9])|7(?:[1-35]|8[0189])|8(?:[16]|3[0134]|9[0-5])|9(?:[028]|17))|4(?:2(?:[13-79]|2[01]|8[014-6])|3[0-57]|[45]|6[248]|7[2-47]|9[29])|5(?:2|3[045]|4[0-369]|5[29]|8[02389]|9[0-3])|7(?:2[02-46-9]|34|[58]|6[0249]|7[57]|9(?:[23]|4[0-59]|5[01569]|6[0167]))|8(?:2(?:[1258]|4[0-39]|9[0169])|3(?:[29]|7(?:[017-9]|6[6-8]))|49|6(?:[0-24]|5(?:[0-389]|5[23])|6(?:[01]|9[178])|9[0145])|7[0-468]|8[68])|9(?:4[15]|5[138]|7[156]|8[189]|9(?:[1289]|3(?:31|4[357])|4[0178]))|(?:8294|96)[1-3]|2(?:57|93)[015-9]|(?:223|8699)[014-9]|(?:48|8292|9[23])[1-9]|(?:47[59]|59[89]|8(?:68|9))[019]","1(?:1|5(?:4[018]|5[017])|77|88|9[69])|2(?:2[127]|3[0-269]|4[59]|5(?:[0468][01]|[1-3]|5[0-69]|7[015-9]|9(?:17|99))|6(?:2|4[016-9])|7(?:[1-35]|8[0189])|8(?:[16]|3[0134]|9[0-5])|9(?:[028]|17|3[015-9]))|4(?:2(?:[13-79]|2[01]|8[014-6])|3[0-57]|[45]|6[248]|7[2-47]|9[29])|5(?:2|3[045]|4[0-369]|5[29]|8[02389]|9[0-3])|7(?:2[02-46-9]|34|[58]|6[0249]|7[57]|9(?:[23]|4[0-59]|5[01569]|6[0167]))|8(?:2(?:[1258]|4[0-39]|9(?:[019]|4[1-3]|6(?:[0-47-9]|5[01346-9])))|3(?:[29]|7(?:[017-9]|6[6-8]))|49|6(?:[0-24]|5(?:[0-389]|5[23])|6(?:[01]|9[178])|9[0145])|7[0-468]|8[68])|9(?:4[15]|5[138]|6[1-3]|7[156]|8[189]|9(?:[1289]|3(?:31|4[357])|4[0178]))|(?:223|8699)[014-9]|(?:48|829(?:2|66)|9[23])[1-9]|(?:47[59]|59[89]|8(?:68|9))[019]"],"format": "$1-$2-$3"},{"pattern": "(\\d{3})(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[14]|[29][2-9]|5[3-9]|7[2-4679]|8(?:[246-9]|3[3-8]|5[2-9])","[14]|[29][2-9]|5[3-9]|7[2-4679]|8(?:[246-9]|3(?:[3-6][2-9]|7|8[2-5])|5[2-9])"],"format": "$1-$2-$3"},{"pattern": "(\\d{4})(\\d{2})(\\d{3,4})","leadingDigits": "007","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{2})(\\d{4})","leadingDigits": "008","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "800","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2579]|80","format": "$1-$2-$3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3,4})","leadingDigits": "0","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4})(\\d{4,5})","leadingDigits": "0","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{5})(\\d{5,6})","leadingDigits": "0","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{6})(\\d{6,7})","leadingDigits": "0","format": "$1-$2-$3","intlFormat": "NA"}]},"generalDesc": {"nationalNumberPattern": "00[1-9]\\d{6,14}|[257-9]\\d{9}|(?:00|[1-9]\\d\\d)\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "[8-17]"},"nationalNumberPattern": "00(?:777(?:[01]|(?:5|8\\d)\\d)|882[1245]\\d\\d)\\d\\d|00(?:37|66)\\d{6,13}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "312345678","nationalNumberPattern": "(?:1(?:1[235-8]|2[3-6]|3[3-9]|4[2-6]|[58][2-8]|6[2-7]|7[2-9]|9[1-9])|(?:2[2-9]|[36][1-9])\\d|4(?:[2-578]\\d|6[02-8]|9[2-59])|5(?:[2-589]\\d|6[1-9]|7[2-8])|7(?:[25-9]\\d|3[4-9]|4[02-9])|8(?:[2679]\\d|3[2-9]|4[5-9]|5[1-9]|8[03-9])|9(?:[2-58]\\d|[679][1-9]))\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "9012345678","nationalNumberPattern": "[7-9]0[1-9]\\d{7}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "2012345678","nationalNumberPattern": "20\\d{8}"},"tollFree": {"possibleLengths": {"national": "[8-17]"},"exampleNumber": "120123456","nationalNumberPattern": "00(?:(?:37|66)\\d{6,13}|(?:777(?:[01]|(?:5|8\\d)\\d)|882[1245]\\d\\d)\\d\\d)|(?:120|800\\d)\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "990123456","nationalNumberPattern": "990\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "601234567","nationalNumberPattern": "60\\d{7}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5012345678","nationalNumberPattern": "50[1-9]\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "570123456","nationalNumberPattern": "570\\d{6}"}},{"id": "KE","countryCode": "254","internationalPrefix": "000","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{5,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[24-6]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[17]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[17]\\d\\d|900)\\d{6}|(?:2|80)0\\d{6,7}|[4-6]\\d{6,8}"},"fixedLine": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "202012345","nationalNumberPattern": "(?:4[245]|5[2-79]|6[01457-9])\\d{5,7}|(?:4[136]|5[08]|62)\\d{7}|(?:[24]0|51|66)\\d{6,7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712123456","nationalNumberPattern": "(?:1(?:0[0-2]|1[01])|7\\d\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "9,10"},"exampleNumber": "800223456","nationalNumberPattern": "800[24-8]\\d{5,6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900223456","nationalNumberPattern": "900[02-9]\\d{5}"}},{"id": "KG","countryCode": "996","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3(?:1[346]|[24-79])","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[235-79]|88","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d)(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "8\\d{9}|(?:[235-8]\\d|99)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "5,6"},"exampleNumber": "312123456","nationalNumberPattern": "312(?:5[0-79]\\d|9(?:[0-689]\\d|7[0-24-9]))\\d{3}|(?:3(?:1(?:2[0-46-8]|3[1-9]|47|[56]\\d)|2(?:22|3[0-479]|6[0-7])|4(?:22|5[6-9]|6\\d)|5(?:22|3[4-7]|59|6\\d)|6(?:22|5[35-7]|6\\d)|7(?:22|3[468]|4[1-9]|59|[67]\\d)|9(?:22|4[1-8]|6\\d))|6(?:09|12|2[2-4])\\d)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "700123456","nationalNumberPattern": "(?:312(?:58\\d|973)|8801\\d\\d)\\d{3}|(?:2(?:0[0-35]|2\\d)|5[0-24-7]\\d|7(?:[07]\\d|55)|99[05-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "9,10"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6,7}"}},{"id": "KH","countryCode": "855","internationalPrefix": "00[14-9]","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-9]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{9}|[1-9]\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "23756789","nationalNumberPattern": "23(?:4(?:[2-4]|[56]\\d)|[568]\\d\\d)\\d{4}|23[236-9]\\d{5}|(?:2[4-6]|3[2-6]|4[2-4]|[5-7][2-5])(?:(?:[237-9]|4[56]|5\\d)\\d{5}|6\\d{5,6})"},"mobile": {"possibleLengths": {"national": "8,9"},"exampleNumber": "91234567","nationalNumberPattern": "(?:(?:1[28]|3[18]|9[67])\\d|6[016-9]|7(?:[07-9]|[16]\\d)|8(?:[013-79]|8\\d))\\d{6}|(?:1\\d|9[0-57-9])\\d{6}|(?:2[3-6]|3[2-6]|4[2-4]|[5-7][2-5])48\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "1800123456","nationalNumberPattern": "1800(?:1\\d|2[019])\\d{4}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1900123456","nationalNumberPattern": "1900(?:1\\d|2[09])\\d{4}"}},{"id": "KI","countryCode": "686","internationalPrefix": "00","nationalPrefix": "0","generalDesc": {"nationalNumberPattern": "(?:[37]\\d|6[0-79])\\d{6}|(?:[2-48]\\d|50)\\d{3}"},"fixedLine": {"possibleLengths": {"national": "5,8"},"exampleNumber": "31234","nationalNumberPattern": "(?:[24]\\d|3[1-9]|50|65(?:02[12]|12[56]|22[89]|[3-5]00)|7(?:27\\d\\d|3100|5(?:02[12]|12[56]|22[89]|[34](?:00|81)|500))|8[0-5])\\d{3}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "72001234","nationalNumberPattern": "73140\\d{3}|(?:630[01]|730[0-5])\\d{4}|[67]200[01]\\d{3}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "30010000","nationalNumberPattern": "30(?:0[01]\\d\\d|12(?:11|20))\\d\\d"}},{"id": "KM","countryCode": "269","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "[3478]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[3478]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7","localOnly": "4"},"exampleNumber": "7712345","nationalNumberPattern": "7[4-7]\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "3212345","nationalNumberPattern": "[34]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "8\\d{6}"}},{"id": "KN","countryCode": "1","leadingDigits": "869","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-7]\\d{6})$","nationalPrefixTransformRule": "869$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8692361234","nationalNumberPattern": "869(?:2(?:29|36)|302|4(?:6[015-9]|70))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8697652917","nationalNumberPattern": "869(?:5(?:5[6-8]|6[5-7])|66\\d|76[02-7])\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "KP","countryCode": "850","internationalPrefix": "00|99","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "85\\d{6}|(?:19\\d|2)\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "238[02-9]\\d{4}|2(?:[0-24-9]\\d|3[0-79])\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6,7"},"exampleNumber": "21234567","nationalNumberPattern": "(?:2\\d|85)\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "1921234567","nationalNumberPattern": "19[1-3]\\d{7}"}},{"id": "KR","countryCode": "82","internationalPrefix": "00(?:[125689]|3(?:[46]5|91)|7(?:00|27|3|55|6[126]))","nationalPrefix": "0","nationalPrefixForParsing": "0(8(?:[1-46-8]|5\\d\\d))?","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["1[016-9]1","1[016-9]11","1[016-9]114"],"format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "(?:3[1-3]|[46][1-4]|5[1-5])1","format": "$1-$2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "1","format": "$1-$2"},{"pattern": "(\\d)(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "2","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "60|8","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "[1346]|5[1-5]","format": "$1-$2-$3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "[57]","format": "$1-$2-$3"},{"pattern": "(\\d{5})(\\d{3})(\\d{3})","leadingDigits": ["003","0030"],"format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$NP$CC-$FG","leadingDigits": "5","format": "$1-$2-$3"},{"pattern": "(\\d{5})(\\d{3,4})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{5})(\\d{2})(\\d{3})(\\d{4})","leadingDigits": "0","format": "$1 $2 $3 $4","intlFormat": "NA"}]},"generalDesc": {"nationalNumberPattern": "00[1-9]\\d{8,11}|(?:[12]|5\\d{3})\\d{7}|[13-6]\\d{9}|(?:[1-6]\\d|80)\\d{7}|[3-6]\\d{4,5}|(?:00|7)0\\d{8}"},"noInternationalDialling": {"possibleLengths": {"national": "[11-14]"},"nationalNumberPattern": "00(?:3(?:08\\d{6,7}|68\\d{7})|798\\d{7,9})"},"fixedLine": {"possibleLengths": {"national": "5,6,[8-10]","localOnly": "3,4,7"},"exampleNumber": "22123456","nationalNumberPattern": "(?:2|3[1-3]|[46][1-4]|5[1-5])[1-9]\\d{6,7}|(?:3[1-3]|[46][1-4]|5[1-5])1\\d{2,3}"},"mobile": {"possibleLengths": {"national": "9,10"},"exampleNumber": "1020000000","nationalNumberPattern": "1(?:05(?:[0-8]\\d|9[1-5])|22[13]\\d)\\d{4,5}|1(?:0[1-46-9]|[16-9]\\d|2[013-9])\\d{6,7}"},"pager": {"possibleLengths": {"national": "9,10"},"exampleNumber": "1523456789","nationalNumberPattern": "15\\d{7,8}"},"tollFree": {"possibleLengths": {"national": "9,[11-14]"},"exampleNumber": "801234567","nationalNumberPattern": "00(?:308\\d{6,7}|798\\d{7,9})|(?:00368|80)\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "602345678","nationalNumberPattern": "60[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10,11"},"exampleNumber": "5012345678","nationalNumberPattern": "50\\d{8,9}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "7012345678","nationalNumberPattern": "70\\d{8}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "15441234","nationalNumberPattern": "1(?:5(?:22|44|66|77|88|99)|6(?:[07]0|44|6[16]|88)|8(?:00|33|55|77|99))\\d{4}"}},{"id": "KW","countryCode": "965","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{3,4})","leadingDigits": "[169]|2(?:[235]|4[1-35-9])|52","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5})","leadingDigits": "[25]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:18|[2569]\\d\\d)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22345678","nationalNumberPattern": "2(?:[23]\\d\\d|4(?:[1-35-9]\\d|44)|5(?:0[034]|[2-46]\\d|5[1-3]|7[1-7]))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "50012345","nationalNumberPattern": "(?:5(?:2(?:22|5[25])|88[58])|6(?:222|444|70[013-9]|888|93[039])|9(?:11[01]|333|500))\\d{4}|(?:5(?:[05]\\d|1[0-7]|6[56])|6(?:0[034679]|5[015-9]|6\\d|7[67]|9[069])|9(?:0[09]|22|[4679]\\d|55|8[057-9]))\\d{5}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "1801234","nationalNumberPattern": "18\\d{5}"}},{"id": "KY","countryCode": "1","leadingDigits": "345","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-9]\\d{6})$","nationalPrefixTransformRule": "345$1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:345|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "3452221234","nationalNumberPattern": "345(?:2(?:22|44)|444|6(?:23|38|40)|7(?:4[35-79]|6[6-9]|77)|8(?:00|1[45]|25|[48]8)|9(?:14|4[035-9]))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "3453231234","nationalNumberPattern": "345(?:32[1-9]|5(?:1[67]|2[5-79]|4[6-9]|50|76)|649|9(?:1[67]|2[2-9]|3[689]))\\d{4}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "3458491234","nationalNumberPattern": "345849\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "(?:345976|900[2-9]\\d\\d)\\d{4}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "KZ","countryCode": "7","leadingDigits": "33|7","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","generalDesc": {"nationalNumberPattern": "33622\\d{5}|(?:7\\d|80)\\d{8}"},"noInternationalDialling": {"possibleLengths": {"national": "10"},"nationalNumberPattern": "751\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "5,6"},"exampleNumber": "7123456789","nationalNumberPattern": "(?:33622|7(?:1(?:0(?:[23]\\d|4[0-3]|59|63)|1(?:[23]\\d|4[0-79]|59)|2(?:[23]\\d|59)|3(?:2\\d|3[0-79]|4[0-35-9]|59)|4(?:[24]\\d|3[013-9]|5[1-9])|5(?:2\\d|3[1-9]|4[0-7]|59)|6(?:[2-4]\\d|5[19]|61)|72\\d|8(?:[27]\\d|3[1-46-9]|4[0-5]))|2(?:1(?:[23]\\d|4[46-9]|5[3469])|2(?:2\\d|3[0679]|46|5[12679])|3(?:[2-4]\\d|5[139])|4(?:2\\d|3[1-35-9]|59)|5(?:[23]\\d|4[0-246-8]|59|61)|6(?:2\\d|3[1-9]|4[0-4]|59)|7(?:[2379]\\d|40|5[279])|8(?:[23]\\d|4[0-3]|59)|9(?:2\\d|3[124578]|59))))\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "7710009998","nationalNumberPattern": "7(?:0[0-25-8]|47|6[02-4]|7[15-8]|85)\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "8091234567","nationalNumberPattern": "809\\d{7}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "8081234567","nationalNumberPattern": "808\\d{7}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "7511234567","nationalNumberPattern": "751\\d{7}"}},{"id": "LA","countryCode": "856","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2[13]|3[14]|[4-8]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:2\\d|3)\\d{8}|(?:[235-8]\\d|41)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6"},"exampleNumber": "21212862","nationalNumberPattern": "(?:2[13]|[35-7][14]|41|8[1468])\\d{6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "2023123456","nationalNumberPattern": "20(?:[29]\\d|5[24-689]|7[6-8])\\d{6}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "301234567","nationalNumberPattern": "30\\d{7}"}},{"id": "LB","countryCode": "961","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[13-69]|7(?:[2-57]|62|8[0-7]|9[04-9])|8[02-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "[7-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[7-9]\\d{7}|[13-9]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "1123456","nationalNumberPattern": "(?:(?:[14-69]\\d|8[02-9])\\d|7(?:[2-57]\\d|62|8[0-7]|9[04-9]))\\d{4}"},"mobile": {"possibleLengths": {"national": "7,8"},"exampleNumber": "71123456","nationalNumberPattern": "793(?:[01]\\d|2[0-4])\\d{3}|(?:(?:3|81)\\d|7(?:[01]\\d|6[013-9]|8[89]|9[12]))\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "9[01]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "80\\d{6}"}},{"id": "LC","countryCode": "1","leadingDigits": "758","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-7]\\d{6})$","nationalPrefixTransformRule": "758$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|758|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7584305678","nationalNumberPattern": "758(?:4(?:30|5\\d|6[2-9]|8[0-2])|57[0-2]|638)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7582845678","nationalNumberPattern": "758(?:28[4-7]|384|4(?:6[01]|8[4-9])|5(?:1[89]|20|84)|7(?:1[2-9]|2\\d|3[01]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "LI","countryCode": "423","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0|(1001)","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{2})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[237-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "69","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "6","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "90\\d{5}|(?:[2378]|6\\d\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2345678","nationalNumberPattern": "(?:2(?:01|1[27]|22|3\\d|6[02-578]|96)|3(?:33|40|7[0135-7]|8[048]|9[0269]))\\d{4}"},"mobile": {"possibleLengths": {"national": "7,9"},"exampleNumber": "660234567","nationalNumberPattern": "(?:6(?:4(?:89|9\\d)|5[0-3]\\d|6(?:0[0-7]|10|2[06-9]|39))\\d|7(?:[37-9]\\d|42|56))\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8002222","nationalNumberPattern": "80(?:02[28]|9\\d\\d)\\d\\d"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9002222","nationalNumberPattern": "90(?:02[258]|1(?:23|3[14])|66[136])\\d\\d"},"uan": {"possibleLengths": {"national": "7"},"exampleNumber": "8702812","nationalNumberPattern": "870(?:28|87)\\d\\d"},"voicemail": {"possibleLengths": {"national": "9"},"exampleNumber": "697861234","nationalNumberPattern": "697(?:42|56|[78]\\d)\\d{4}"}},{"id": "LK","countryCode": "94","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-689]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[1-7]\\d|[89]1)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "112345678","nationalNumberPattern": "(?:[189]1|2[13-7]|3[1-8]|4[157]|5[12457]|6[35-7])[2-57]\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712345678","nationalNumberPattern": "7[0-25-8]\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "197312345","nationalNumberPattern": "1973\\d{5}"}},{"id": "LR","countryCode": "231","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[45]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3578]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:2|33|5\\d|77|88)\\d{7}|[45]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "21234567","nationalNumberPattern": "(?:2\\d{3}|33333)\\d{4}"},"mobile": {"possibleLengths": {"national": "7,9"},"exampleNumber": "770123456","nationalNumberPattern": "(?:(?:330|555|(?:77|88)\\d)\\d|4[67])\\d{5}|5\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "332021234","nationalNumberPattern": "332(?:02|[34]\\d)\\d{4}"}},{"id": "LS","countryCode": "266","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[2568]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[256]\\d\\d|800)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22123456","nationalNumberPattern": "2\\d{7}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "50123456","nationalNumberPattern": "[56]\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80021234","nationalNumberPattern": "800[256]\\d{4}"}},{"id": "LT","countryCode": "370","internationalPrefix": "00","nationalPrefix": "8","nationalPrefixForParsing": "[08]","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP-$FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "52[0-79]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP $FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[7-9]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "($NP-$FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "37|4(?:[15]|6[1-8])","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "($NP-$FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[3-6]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:[3469]\\d|52|[78]0)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "31234567","nationalNumberPattern": "(?:3[1478]|4[124-6]|52)\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "61234567","nationalNumberPattern": "6\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "9(?:0[0239]|10)\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80812345","nationalNumberPattern": "808\\d{5}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "700\\d{5}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "70712345","nationalNumberPattern": "70[67]\\d{5}"}},{"id": "LU","countryCode": "352","internationalPrefix": "00","nationalPrefixForParsing": "(15(?:0[06]|1[12]|[35]5|4[04]|6[26]|77|88|99)\\d)","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "2(?:0[2-689]|[2-9])|[3-57]|8(?:0[2-9]|[13-9])|9(?:0[89]|[2-579])","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "2(?:0[2-689]|[2-9])|[3-57]|8(?:0[2-9]|[13-9])|9(?:0[89]|[2-579])","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "20[2-689]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "2(?:[0367]|4[3-8])","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "80[01]|90[015]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "20","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "6","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "2(?:[0367]|4[3-8])","format": "$1 $2 $3 $4 $5"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{1,5})","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[3-57]|8[13-9]|9(?:0[89]|[2-579])|(?:2|80)[2-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "35[013-9]\\d{4,8}|6\\d{8}|35\\d{2,4}|(?:[2457-9]\\d|3[0-46-9])\\d{2,9}"},"fixedLine": {"possibleLengths": {"national": "[4-11]"},"exampleNumber": "27123456","nationalNumberPattern": "(?:35[013-9]|80[2-9]|90[89])\\d{1,8}|(?:2[2-9]|3[0-46-9]|[457]\\d|8[13-9]|9[2-579])\\d{2,9}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "628123456","nationalNumberPattern": "6(?:[269][18]|5[158]|7[189]|81)\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "90[015]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80112345","nationalNumberPattern": "801\\d{5}"},"voip": {"possibleLengths": {"national": "[4-10]"},"exampleNumber": "20201234","nationalNumberPattern": "20(?:1\\d{5}|[2-689]\\d{1,7})"}},{"id": "LV","countryCode": "371","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "[269]|8[01]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:[268]\\d|90)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "63123456","nationalNumberPattern": "6\\d{7}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "2\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "80\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "90\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "81123456","nationalNumberPattern": "81\\d{6}"}},{"id": "LY","countryCode": "218","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-9]","format": "$1-$2"}},"generalDesc": {"nationalNumberPattern": "[2-9]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "212345678","nationalNumberPattern": "(?:2(?:0[56]|[1-6]\\d|7[124579]|8[124])|3(?:1\\d|2[2356])|4(?:[17]\\d|2[1-357]|5[2-4]|8[124])|5(?:[1347]\\d|2[1-469]|5[13-5]|8[1-4])|6(?:[1-479]\\d|5[2-57]|8[1-5])|7(?:[13]\\d|2[13-79])|8(?:[124]\\d|5[124]|84))\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "9[1-6]\\d{7}"}},{"id": "MA","mainCountryForCode": "true","countryCode": "212","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["5(?:29|38)","5(?:29|38)[89]"],"format": "$1-$2"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5[45]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{4})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5(?:2[2-489]|3[5-9]|9)|892","format": "$1-$2"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[5-7]","format": "$1-$2"}]},"generalDesc": {"nationalNumberPattern": "[5-8]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "520123456","nationalNumberPattern": "5(?:29|38)[89]0\\d{4}|5(?:2(?:[015-7]\\d|2[02-9]|3[2-578]|4[2-46-8]|8[235-7]|90)|3(?:[0-4]\\d|[57][2-9]|6[2-8]|80|9[3-9])|(?:4[067]|5[03])\\d)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "650123456","nationalNumberPattern": "(?:6(?:[0-79]\\d|8[0-247-9])|7(?:0[06-8]|6[1267]|7[0-27]))\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "891234567","nationalNumberPattern": "89\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "592401234","nationalNumberPattern": "592(?:4[0-2]|93)\\d{4}"}},{"id": "MC","countryCode": "377","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{2})","leadingDigits": "8","format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "4","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[39]","format": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6","format": "$1 $2 $3 $4 $5"}]},"generalDesc": {"nationalNumberPattern": "870\\d{5}|(?:[349]|6\\d)\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "870\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "99123456","nationalNumberPattern": "(?:870|9[2-47-9]\\d)\\d{5}"},"mobile": {"possibleLengths": {"national": "8,9"},"exampleNumber": "612345678","nationalNumberPattern": "4(?:4\\d|5[1-9])\\d{5}|(?:3|6\\d)\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "90123456","nationalNumberPattern": "90\\d{6}"}},{"id": "MD","countryCode": "373","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "22|3","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[25-7]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[235-7]\\d|[89]0)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22212345","nationalNumberPattern": "(?:(?:2[1-9]|3[1-79])\\d|5(?:33|5[257]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "62112345","nationalNumberPattern": "562\\d{5}|(?:6\\d|7[16-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "90[056]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80812345","nationalNumberPattern": "808\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "30123456","nationalNumberPattern": "3[08]\\d{6}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "80312345","nationalNumberPattern": "803\\d{5}"}},{"id": "ME","countryCode": "382","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-9]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:20|[3-79]\\d)\\d{6}|80\\d{6,7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6"},"exampleNumber": "30234567","nationalNumberPattern": "(?:20[2-8]|3(?:[0-2][2-7]|3[24-7])|4(?:0[2-467]|1[2467])|5(?:[01][2467]|2[2-467]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "67622901","nationalNumberPattern": "6(?:00|3[024]|6[0-25]|[7-9]\\d)\\d{5}"},"tollFree": {"possibleLengths": {"national": "8,9"},"exampleNumber": "80080002","nationalNumberPattern": "80(?:[0-2578]|9\\d)\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "94515151","nationalNumberPattern": "9(?:4[1568]|5[178])\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "78108780","nationalNumberPattern": "78[1-49]\\d{5}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "77273012","nationalNumberPattern": "77[1-9]\\d{5}"}},{"id": "MF","countryCode": "590","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:590|69\\d|976)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "590271234","nationalNumberPattern": "590(?:0[079]|[14]3|[27][79]|30|5[0-268]|87)\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "690001234","nationalNumberPattern": "69(?:0\\d\\d|1(?:2[29]|3[0-5]))\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "976012345","nationalNumberPattern": "976[01]\\d{5}"}},{"id": "MG","countryCode": "261","internationalPrefix": "00","nationalPrefix": "0","nationalPrefixForParsing": "0|([24-9]\\d{6})$","nationalPrefixTransformRule": "20$1","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[23]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[23]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "202123456","nationalNumberPattern": "2072[29]\\d{4}|20(?:2\\d|4[47]|5[3467]|6[279]|7[35]|8[268]|9[245])\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "321234567","nationalNumberPattern": "3[2-49]\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "221234567","nationalNumberPattern": "22\\d{7}"}},{"id": "MH","countryCode": "692","internationalPrefix": "011","nationalPrefix": "1","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-6]","format": "$1-$2"}},"generalDesc": {"nationalNumberPattern": "329\\d{4}|(?:[256]\\d|45)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2471234","nationalNumberPattern": "(?:247|528|625)\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "2351234","nationalNumberPattern": "(?:(?:23|54)5|329|45[56])\\d{4}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "6351234","nationalNumberPattern": "635\\d{4}"}},{"id": "MK","countryCode": "389","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[347]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d)(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[58]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[2-578]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6,7"},"exampleNumber": "22012345","nationalNumberPattern": "(?:2(?:[23]\\d|5[0-24578]|6[01]|82)|3(?:1[3-68]|[23][2-68]|4[23568])|4(?:[23][2-68]|4[3-68]|5[2568]|6[25-8]|7[24-68]|8[4-68]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "72345678","nationalNumberPattern": "7(?:(?:[0-25-8]\\d|3[2-4]|9[23])\\d|4(?:21|60))\\d{4}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "50012345","nationalNumberPattern": "5[02-9]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80123456","nationalNumberPattern": "8(?:0[1-9]|[1-9]\\d)\\d{5}"}},{"id": "ML","countryCode": "223","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})","leadingDigits": ["67[057-9]|74[045]","67(?:0[09]|[59]9|77|8[89])|74(?:0[02]|44|55)"],"format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[24-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[246-9]\\d|50)\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "80\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "20212345","nationalNumberPattern": "2(?:07[0-8]|12[67])\\d{4}|(?:2(?:02|1[4-689])|4(?:0[0-4]|4[1-39]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "65012345","nationalNumberPattern": "2(?:079|17\\d)\\d{4}|(?:50|[679]\\d|8[239])\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "80\\d{6}"}},{"id": "MM","countryCode": "95","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "16|2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[45]|6(?:0[23]|[1-689]|7[235-7])|7(?:[0-4]|5[2-7])|8[1-6]","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[4-7]|8[1-35]","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{4,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9(?:2[0-4]|[35-9]|4[137-9])","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "92","format": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{5,7}|95\\d{6}|(?:[4-7]|9[0-46-9])\\d{6,8}|(?:2|8\\d)\\d{5,8}"},"fixedLine": {"possibleLengths": {"national": "[6-9]","localOnly": "5"},"exampleNumber": "1234567","nationalNumberPattern": "(?:1(?:(?:2\\d|3[56]|[89][0-6])\\d|4(?:2[2-469]|39|46|6[25]|7[0-3]|83)|6)|2(?:2(?:00|8[34])|4(?:0\\d|2[246]|39|46|62|7[0-3]|83)|51\\d\\d)|4(?:2(?:2\\d\\d|48[0-3])|3(?:20\\d|4(?:70|83)|56)|420\\d|5470)|6(?:0(?:[23]|88\\d)|(?:124|[56]2\\d)\\d|247[23]|3(?:20\\d|470)|4(?:2[04]\\d|47[23])|7(?:(?:3\\d|8[01459])\\d|4(?:39|60|7[013]))))\\d{4}|5(?:2(?:2\\d{5,6}|47[023]\\d{4})|(?:347[23]|4(?:2(?:1|86)|470)|522\\d|6(?:20\\d|483)|7(?:20\\d|48[0-2])|8(?:20\\d|47[02])|9(?:20\\d|47[01]))\\d{4})|7(?:(?:0470|4(?:25\\d|470)|5(?:202|470|96\\d))\\d{4}|1(?:20\\d{4,5}|4(?:70|83)\\d{4}))|8(?:1(?:2\\d{5,6}|4(?:10|7[01]\\d)\\d{3})|2(?:2\\d{5,6}|(?:320|490\\d)\\d{3})|(?:3(?:2\\d\\d|470)|4[24-7]|5(?:2\\d|4[1-9]|51)\\d|6[23])\\d{4})|(?:1[2-6]\\d|4(?:2[24-8]|3[2-7]|[46][2-6]|5[3-5])|5(?:[27][2-8]|3[2-68]|4[24-8]|5[23]|6[2-4]|8[24-7]|9[2-7])|6(?:[19]20|42[03-6]|(?:52|7[45])\\d)|7(?:[04][24-8]|[15][2-7]|22|3[2-4])|8(?:1[2-689]|2[2-8]|[35]2\\d))\\d{4}|25\\d{5,6}|(?:2[2-9]|6(?:1[2356]|[24][2-6]|3[24-6]|5[2-4]|6[2-8]|7[235-7]|8[245]|9[24])|8(?:3[24]|5[245]))\\d{4}"},"mobile": {"possibleLengths": {"national": "[7-10]"},"exampleNumber": "92123456","nationalNumberPattern": "(?:17[01]|9(?:2(?:[0-4]|[56]\\d\\d)|(?:3(?:[0-36]|4\\d)|6(?:6[0-2]|[7-9]\\d)|7(?:3|[5-9]\\d)|8(?:8[4-9]|9\\d)|9[5-8]\\d)\\d|4(?:(?:[0245]\\d|[1379])\\d|88)|5[0-6])\\d)\\d{4}|9[69]1\\d{6}|9(?:[68]\\d|9[089])\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8008001234","nationalNumberPattern": "80080(?:[01][1-9]|2\\d)\\d{3}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "13331234","nationalNumberPattern": "1333\\d{4}|[12]468\\d{4}"}},{"id": "MN","countryCode": "976","internationalPrefix": "001","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]1","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[57-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]2[1-3]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[12](?:27|3[2-8]|4[2-68]|5[1-4689])","[12](?:27|3[2-8]|4[2-68]|5[1-4689])[0-3]"],"format": "$1 $2"},{"pattern": "(\\d{5})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[12]\\d{7,9}|[57-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "[8-10]","localOnly": "[4-6]"},"exampleNumber": "50123456","nationalNumberPattern": "[12](?:3[2-8]|4[2-68]|5[1-4689])\\d{6,7}|(?:11(?:3\\d|4[568])|(?:(?:21|5[0568])\\d|70[0-5])\\d)\\d{4}|[12]2(?:[1-3]\\d{5,6}|7\\d{6})"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "88123456","nationalNumberPattern": "(?:8(?:[05689]\\d|3[01])|9(?:[014-9]\\d|20|3[0-4]))\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "75153456","nationalNumberPattern": "7(?:100|5(?:0[0579]|1[015]|[389]5|[57][57])|(?:6[0167]|7\\d|8[01])\\d)\\d{4}"}},{"id": "MO","countryCode": "853","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[268]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:28|[68]\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "28212345","nationalNumberPattern": "(?:28[2-57-9]|8(?:11|[2-57-9]\\d))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "66123456","nationalNumberPattern": "6(?:[2356]\\d\\d|8(?:[02][5-9]|[1478]\\d|[356][0-4]))\\d{4}"}},{"id": "MP","countryCode": "1","leadingDigits": "670","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-9]\\d{6})$","nationalPrefixTransformRule": "670$1","generalDesc": {"nationalNumberPattern": "[58]\\d{9}|(?:67|90)0\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6702345678","nationalNumberPattern": "670(?:2(?:3[3-7]|56|8[5-8])|32[1-38]|4(?:33|8[348])|5(?:32|55|88)|6(?:64|70|82)|78[3589]|8[3-9]8|989)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6702345678","nationalNumberPattern": "670(?:2(?:3[3-7]|56|8[5-8])|32[1-38]|4(?:33|8[348])|5(?:32|55|88)|6(?:64|70|82)|78[3589]|8[3-9]8|989)\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "MQ","countryCode": "596","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[569]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "69\\d{7}|(?:59|97)6\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "596301234","nationalNumberPattern": "596(?:0[0-7]|10|2[7-9]|3[05-9]|4[0-46-8]|[5-7]\\d|8[09]|9[4-8])\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "696201234","nationalNumberPattern": "69(?:6(?:[0-47-9]\\d|5[0-6]|6[0-4])|727)\\d{4}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "976612345","nationalNumberPattern": "976(?:6[1-9]|7[0-367])\\d{4}"}},{"id": "MR","countryCode": "222","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2-48]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:[2-4]\\d\\d|800)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "35123456","nationalNumberPattern": "(?:25[08]|35\\d|45[1-7])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "22123456","nationalNumberPattern": "[2-4][0-46-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"}},{"id": "MS","countryCode": "1","leadingDigits": "664","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|(4\\d{6})$","nationalPrefixTransformRule": "664$1","generalDesc": {"nationalNumberPattern": "66449\\d{5}|(?:[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6644912345","nationalNumberPattern": "664491\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6644923456","nationalNumberPattern": "66449[2-6]\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "MT","countryCode": "356","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[2357-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "3550\\d{4}|(?:[2579]\\d\\d|800)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21001234","nationalNumberPattern": "2(?:0(?:[19]\\d|3[1-4]|6[059])|[1-357]\\d\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "96961234","nationalNumberPattern": "(?:7(?:210|[79]\\d\\d)|9(?:2(?:1[01]|31)|69[67]|8(?:1[1-3]|89|97)|9\\d\\d))\\d{4}"},"pager": {"possibleLengths": {"national": "8"},"exampleNumber": "71171234","nationalNumberPattern": "7117\\d{4}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80071234","nationalNumberPattern": "800[3467]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "50037123","nationalNumberPattern": "5(?:0(?:0(?:37|43)|(?:6\\d|70|9[0168])\\d)|[12]\\d0[1-5])\\d{3}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "35501234","nationalNumberPattern": "3550\\d{4}"},"uan": {"possibleLengths": {"national": "8"},"exampleNumber": "50112345","nationalNumberPattern": "501\\d{5}"}},{"id": "MU","countryCode": "230","preferredInternationalPrefix": "020","internationalPrefix": "0(?:0|[24-7]0|3[03])","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-46]|8[013]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "5","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:[2-468]|5\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7,8"},"exampleNumber": "54480123","nationalNumberPattern": "(?:2(?:[03478]\\d|1[0-7]|6[0-79])|4(?:[013568]\\d|2[4-7])|54(?:[34]\\d|71)|6\\d\\d|8(?:14|3[129]))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "52512345","nationalNumberPattern": "5(?:4(?:2[1-389]|7[1-9])|87[15-8])\\d{4}|5(?:2[589]|4[3489]|7\\d|8[0-689]|9[0-8])\\d{5}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "80[0-2]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "3012345","nationalNumberPattern": "30\\d{5}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "3201234","nationalNumberPattern": "3(?:20|9\\d)\\d{4}"}},{"id": "MV","countryCode": "960","preferredInternationalPrefix": "00","internationalPrefix": "0(?:0|19)","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[3467]|9[13-9]","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:800|9[0-57-9]\\d)\\d{7}|[34679]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "6701234","nationalNumberPattern": "(?:3(?:0[0-3]|3[0-59])|6(?:[57][02468]|6[024-68]|8[024689]))\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7712345","nationalNumberPattern": "46[46]\\d{4}|(?:7[2-9]|9[13-9])\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "900\\d{7}"},"uan": {"possibleLengths": {"national": "7"},"exampleNumber": "4001234","nationalNumberPattern": "4[05]0\\d{4}"}},{"id": "MW","countryCode": "265","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[2-9]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[17-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "1\\d{6}(?:\\d{2})?|(?:[23]1|77|88|99)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "7,9"},"exampleNumber": "1234567","nationalNumberPattern": "(?:1[2-9]|21\\d\\d)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "991234567","nationalNumberPattern": "111\\d{6}|(?:77|88|99)\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "310123456","nationalNumberPattern": "31\\d{7}"}},{"id": "MX","countryCode": "52","preferredInternationalPrefix": "00","internationalPrefix": "0[09]","nationalPrefix": "01","nationalPrefixForParsing": "0(?:[12]|4[45])|1","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})","leadingDigits": "53","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "33|5[56]|81","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[2-9]","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{2})(\\d{4})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "1(?:33|5[56]|81)","format": "$2 $3 $4"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "1","format": "$2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:1(?:[01467]\\d|[2359][1-9]|8[1-79])|[2-9]\\d)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7,8"},"exampleNumber": "2001234567","nationalNumberPattern": "(?:2(?:0[01]|2[1-9]|3[1-35-8]|4[13-9]|7[1-689]|8[1-578]|9[467])|3(?:1[1-79]|[2458][1-9]|3\\d|7[1-8]|9[1-5])|4(?:1[1-57-9]|[24-7][1-9]|3[1-8]|8[1-35-9]|9[2-689])|5(?:[56]\\d|88|9[1-79])|6(?:1[2-68]|[2-4][1-9]|5[1-3689]|6[1-57-9]|7[1-7]|8[67]|9[4-8])|7(?:[1-467][1-9]|5[13-9]|8[1-69]|9[17])|8(?:1\\d|2[13-689]|3[1-6]|4[124-6]|6[1246-9]|7[1-378]|9[12479])|9(?:1[346-9]|2[1-4]|3[2-46-8]|5[1348]|[69][1-9]|7[12]|8[1-8]))\\d{7}"},"mobile": {"possibleLengths": {"national": "10,11","localOnly": "7,8"},"exampleNumber": "12221234567","nationalNumberPattern": "(?:1(?:2(?:2[1-9]|3[1-35-8]|4[13-9]|7[1-689]|8[1-578]|9[467])|3(?:1[1-79]|[2458][1-9]|3\\d|7[1-8]|9[1-5])|4(?:1[1-57-9]|[24-7][1-9]|3[1-8]|8[1-35-9]|9[2-689])|5(?:[56]\\d|88|9[1-79])|6(?:1[2-68]|[2-4][1-9]|5[1-3689]|6[1-57-9]|7[1-7]|8[67]|9[4-8])|7(?:[1-467][1-9]|5[13-9]|8[1-69]|9[17])|8(?:1\\d|2[13-689]|3[1-6]|4[124-6]|6[1246-9]|7[1-378]|9[12479])|9(?:1[346-9]|2[1-4]|3[2-46-8]|5[1348]|[69][1-9]|7[12]|8[1-8]))|2(?:2[1-9]|3[1-35-8]|4[13-9]|7[1-689]|8[1-578]|9[467])|3(?:1[1-79]|[2458][1-9]|3\\d|7[1-8]|9[1-5])|4(?:1[1-57-9]|[24-7][1-9]|3[1-8]|8[1-35-9]|9[2-689])|5(?:[56]\\d|88|9[1-79])|6(?:1[2-68]|[2-4][1-9]|5[1-3689]|6[1-57-9]|7[1-7]|8[67]|9[4-8])|7(?:[1-467][1-9]|5[13-9]|8[1-69]|9[17])|8(?:1\\d|2[13-689]|3[1-6]|4[124-6]|6[1246-9]|7[1-378]|9[12479])|9(?:1[346-9]|2[1-4]|3[2-46-8]|5[1348]|[69][1-9]|7[12]|8[1-8]))\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "8(?:00|88)\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "900\\d{7}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "3001234567","nationalNumberPattern": "300\\d{7}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5001234567","nationalNumberPattern": "500\\d{7}"}},{"id": "MY","countryCode": "60","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[4-79]","format": "$1-$2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1(?:[02469]|[37][2-9]|8[1-9])|8","format": "$1-$2 $3"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3","format": "$1-$2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{2})(\\d{4})","leadingDigits": "1[36-8]","format": "$1-$2-$3-$4"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "15","format": "$1-$2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1-$2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{8,9}|(?:3\\d|[4-9])\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "323856789","nationalNumberPattern": "(?:3(?:2[0-36-9]|3[0-368]|4[0-278]|5[0-24-8]|6[0-467]|7[1246-9]|8\\d|9[0-57])\\d|4(?:2[0-689]|[3-79]\\d|8[1-35689])|5(?:2[0-589]|[3468]\\d|5[0-489]|7[1-9]|9[23])|6(?:2[2-9]|3[1357-9]|[46]\\d|5[0-6]|7[0-35-9]|85|9[015-8])|7(?:[2579]\\d|3[03-68]|4[0-8]|6[5-9]|8[0-35-9])|8(?:[24][2-8]|3[2-5]|5[2-7]|6[2-589]|7[2-578]|[89][2-9])|9(?:0[57]|13|[25-7]\\d|[3489][0-8]))\\d{5}"},"mobile": {"possibleLengths": {"national": "9,10"},"exampleNumber": "123456789","nationalNumberPattern": "1(?:4400|8(?:47|8[27])[0-4])\\d{4}|1(?:0(?:[23568]\\d|4[0-6]|7[016-9]|9[0-8])|1(?:[1-5]\\d\\d|6(?:0[5-9]|[1-9]\\d)|7(?:0[3-9]|1[01]))|(?:[2379][2-9]|4[235-9]|(?:59|6)\\d)\\d|8(?:1[23]|[236]\\d|4[06]|5[7-9]|7[016-9]|8[01]|9[0-8]))\\d{5}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "1300123456","nationalNumberPattern": "1[378]00\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1600123456","nationalNumberPattern": "1600\\d{6}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "1546012345","nationalNumberPattern": "154(?:6(?:0\\d|1[0-3])|8(?:[25]1|4[0189]|7[0-4679]))\\d{4}"}},{"id": "MZ","countryCode": "258","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","leadingDigits": "2|8[2-7]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "8","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:2|8\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21123456","nationalNumberPattern": "2(?:[1346]\\d|5[0-2]|[78][12]|93)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "821234567","nationalNumberPattern": "8[2-7]\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"}},{"id": "NA","countryCode": "264","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "88","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "87","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[68]\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "61221234","nationalNumberPattern": "6(?:1(?:[02-4]\\d\\d|17)|2(?:17|54\\d|69|70)|3(?:17|2[0237]\\d|34|6[289]|7[01]|81)|4(?:17|(?:27|41|5[25])\\d|69|7[01])|5(?:17|2[236-8]\\d|69|7[01])|6(?:17|26\\d|38|42|69|7[01])|7(?:17|(?:2[2-4]|30)\\d|6[89]|7[01]))\\d{4}|6(?:1(?:2[2-7]|3[01378]|4[0-4]|69|7[014])|25[0-46-8]|32\\d|4(?:2[0-27]|4[016]|5[0-357])|52[02-9]|62[56]|7(?:2[2-69]|3[013]))\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "811234567","nationalNumberPattern": "(?:60|8[1245])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "80\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "870123456","nationalNumberPattern": "8701\\d{5}"},"voip": {"possibleLengths": {"national": "8,9"},"exampleNumber": "88612345","nationalNumberPattern": "8(?:3\\d\\d|86)\\d{5}"}},{"id": "NC","countryCode": "687","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})","leadingDigits": "5[6-8]","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2-57-9]","format": "$1.$2.$3"}]},"generalDesc": {"nationalNumberPattern": "[2-57-9]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "201234","nationalNumberPattern": "(?:2[03-9]|3[0-5]|4[1-7]|88)\\d{4}"},"mobile": {"possibleLengths": {"national": "6"},"exampleNumber": "751234","nationalNumberPattern": "(?:5[0-4]|[79]\\d|8[0-79])\\d{4}"},"premiumRate": {"possibleLengths": {"national": "6"},"exampleNumber": "366711","nationalNumberPattern": "36\\d{4}"}},{"id": "NE","countryCode": "227","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "08","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[089]|2[01]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[0289]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "20201234","nationalNumberPattern": "2(?:0(?:20|3[1-8]|4[13-5]|5[14]|6[14578]|7[1-578])|1(?:4[145]|5[14]|6[14-68]|7[169]|88))\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "93123456","nationalNumberPattern": "(?:8[014589]|9\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "08123456","nationalNumberPattern": "08\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "09123456","nationalNumberPattern": "09\\d{6}"}},{"id": "NF","countryCode": "672","internationalPrefix": "00","nationalPrefixForParsing": "([0-258]\\d{4})$","nationalPrefixTransformRule": "3$1","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{4})","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d)(\\d{5})","leadingDigits": "3","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[13]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6","localOnly": "5"},"exampleNumber": "106609","nationalNumberPattern": "(?:1(?:06|17|28|39)|3[0-2]\\d)\\d{3}"},"mobile": {"possibleLengths": {"national": "6","localOnly": "5"},"exampleNumber": "381234","nationalNumberPattern": "3[58]\\d{4}"}},{"id": "NG","countryCode": "234","internationalPrefix": "009","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "78","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12]|9(?:0[3-9]|[1-9])","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-7]|8[2-9]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[7-9]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[78]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{5})(\\d{5,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[78]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[124-7]|9\\d{3})\\d{6}|[1-9]\\d{7}|[78]\\d{9,13}"},"fixedLine": {"possibleLengths": {"national": "7,8","localOnly": "5,6"},"exampleNumber": "18040123","nationalNumberPattern": "(?:(?:[1-356]\\d|4[02-8]|7[0-79]|8[2-9])\\d|9(?:0[3-9]|[1-9]\\d))\\d{5}|(?:[12]\\d|4[147]|5[14579]|6[1578]|7[0-3578])\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "8021234567","nationalNumberPattern": "(?:707[0-3]|8(?:01|19)[01])\\d{6}|(?:70[1-689]|8(?:0[2-9]|1[0-8])|90[1-35-9])\\d{7}"},"tollFree": {"possibleLengths": {"national": "[10-14]"},"exampleNumber": "80017591759","nationalNumberPattern": "800\\d{7,11}"},"uan": {"possibleLengths": {"national": "[10-14]"},"exampleNumber": "7001234567","nationalNumberPattern": "700\\d{7,11}"}},{"id": "NI","countryCode": "505","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[125-8]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:1800|[25-8]\\d{3})\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "2\\d{7}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "81234567","nationalNumberPattern": "(?:5(?:5[0-7]|[78]\\d)|6(?:20|3[035]|4[045]|5[05]|77|8[1-9]|9[059])|(?:7[5-8]|8\\d)\\d)\\d{5}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "18001234","nationalNumberPattern": "1800\\d{4}"}},{"id": "NL","countryCode": "31","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})","leadingDigits": "1[238]|[34]","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{3,4})","leadingDigits": "14","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{6})","leadingDigits": "1","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{4,7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]0","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "66","format": "$1 $2"},{"pattern": "(\\d)(\\d{8})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "6","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[16-8]|2[259]|3[124]|4[17-9]|5[124679]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-57-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[124-7]\\d\\d|3(?:[02-9]\\d|1[0-8]))\\d{6}|[89]\\d{6,9}|1\\d{4,5}"},"noInternationalDialling": {"possibleLengths": {"national": "5,6"},"nationalNumberPattern": "140(?:1[035]|2[0346]|3[03568]|4[0356]|5[0358]|8[458])|140(?:1[16-8]|2[259]|3[124]|4[17-9]|5[124679]|7)\\d"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "101234567","nationalNumberPattern": "(?:1(?:[035]\\d|1[13-578]|6[124-8]|7[24]|8[0-467])|2(?:[0346]\\d|2[2-46-9]|5[125]|9[479])|3(?:[03568]\\d|1[3-8]|2[01]|4[1-8])|4(?:[0356]\\d|1[1-368]|7[58]|8[15-8]|9[23579])|5(?:[0358]\\d|[19][1-9]|2[1-57-9]|4[13-8]|6[126]|7[0-3578])|7\\d\\d)\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "612345678","nationalNumberPattern": "6[1-58]\\d{7}"},"pager": {"possibleLengths": {"national": "9"},"exampleNumber": "662345678","nationalNumberPattern": "66\\d{7}"},"tollFree": {"possibleLengths": {"national": "[7-10]"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4,7}"},"premiumRate": {"possibleLengths": {"national": "[7-10]"},"exampleNumber": "9061234","nationalNumberPattern": "90[069]\\d{4,7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "851234567","nationalNumberPattern": "(?:85|91)\\d{7}"},"uan": {"possibleLengths": {"national": "5,6,9"},"exampleNumber": "14020","nationalNumberPattern": "140(?:1[035]|2[0346]|3[03568]|4[0356]|5[0358]|8[458])|(?:140(?:1[16-8]|2[259]|3[124]|4[17-9]|5[124679]|7)|8[478]\\d{6})\\d"}},{"id": "NO","mainCountryForCode": "true","countryCode": "47","leadingDigits": "[02-689]|7[0-8]","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{3})","leadingDigits": "[489]|5[89]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[235-7]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:0|[2-9]\\d{3})\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "(?:2[1-4]|3[1-3578]|5[1-35-7]|6[1-4679]|7[0-8])\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "40612345","nationalNumberPattern": "(?:4[015-8]|5[89]|9\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "80[01]\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "82012345","nationalNumberPattern": "82[09]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "81021234","nationalNumberPattern": "810(?:0[0-6]|[2-8]\\d)\\d{3}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "88012345","nationalNumberPattern": "880\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "85012345","nationalNumberPattern": "85[0-5]\\d{5}"},"uan": {"possibleLengths": {"national": "5,8"},"exampleNumber": "02000","nationalNumberPattern": "(?:0[2-9]|81(?:0(?:0[7-9]|1\\d)|5\\d\\d))\\d{3}"},"voicemail": {"possibleLengths": {"national": "8"},"exampleNumber": "81212345","nationalNumberPattern": "81[23]\\d{5}"}},{"id": "NP","countryCode": "977","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[2-6]","format": "$1-$2"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-8]|9(?:[1-579]|6[2-6])","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{7})","leadingDigits": "9","format": "$1-$2"}]},"generalDesc": {"nationalNumberPattern": "9\\d{9}|[1-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6,7"},"exampleNumber": "14567890","nationalNumberPattern": "1[0-6]\\d{6}|(?:2[13-79]|3[135-8]|4[146-9]|5[135-7]|6[13-9]|7[15-9]|8[1-46-9]|9[1-79])[2-6]\\d{5}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "9841234567","nationalNumberPattern": "9(?:6[0-3]|7[245]|8[0-24-68])\\d{7}"}},{"id": "NR","countryCode": "674","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[458]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:444|55\\d|888)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "4441234","nationalNumberPattern": "(?:444|888)\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "5551234","nationalNumberPattern": "55[4-9]\\d{4}"}},{"id": "NU","countryCode": "683","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "8","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[47]|888\\d)\\d{3}"},"fixedLine": {"possibleLengths": {"national": "4"},"exampleNumber": "7012","nationalNumberPattern": "[47]\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "8884012","nationalNumberPattern": "888[4-9]\\d{3}"}},{"id": "NZ","countryCode": "64","preferredInternationalPrefix": "00","internationalPrefix": "0(?:0|161)","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]0","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "24|[346]|7[2-57-9]|9[2-9]","format": "$1-$2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2(?:10|74)|[59]|80","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2[028]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2(?:[169]|7[0-35-9])|7|86","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[28]\\d{7,9}|[346]\\d{7}|(?:508|[79]\\d)\\d{6,7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "32345678","nationalNumberPattern": "24099\\d{3}|(?:3[2-79]|[49][2-9]|6[235-9]|7[2-57-9])\\d{6}"},"mobile": {"possibleLengths": {"national": "[8-10]"},"exampleNumber": "211234567","nationalNumberPattern": "2[0-28]\\d{8}|2[0-27-9]\\d{7}|21\\d{6}"},"pager": {"possibleLengths": {"national": "8,9"},"exampleNumber": "26123456","nationalNumberPattern": "[28]6\\d{6,7}"},"tollFree": {"possibleLengths": {"national": "[8-10]"},"exampleNumber": "800123456","nationalNumberPattern": "508\\d{6,7}|80\\d{6,8}"},"premiumRate": {"possibleLengths": {"national": "8,9"},"exampleNumber": "900123456","nationalNumberPattern": "90\\d{6,7}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "701234567","nationalNumberPattern": "70\\d{7}"}},{"id": "OM","countryCode": "968","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4,6})","leadingDigits": "[58]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6})","leadingDigits": "2","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[79]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:[279]\\d{3}|500)\\d{4}|8007\\d{4,5}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "23123456","nationalNumberPattern": "2[2-6]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "92123456","nationalNumberPattern": "90[1-9]\\d{5}|(?:7[1289]|9[1-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "80071234","nationalNumberPattern": "500\\d{4}|8007\\d{4,5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "900\\d{5}"}},{"id": "PA","countryCode": "507","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[1-57-9]","format": "$1-$2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "6","format": "$1-$2"}]},"generalDesc": {"nationalNumberPattern": "(?:[1-57-9]|6\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2001234","nationalNumberPattern": "(?:1(?:0\\d|1[479]|2[37]|3[0137]|4[17]|5[05]|[68][58]|7[0167]|9[39])|2(?:[0235-79]\\d|1[0-7]|4[013-9]|8[026-9])|3(?:[089]\\d|1[014-7]|2[0-35]|33|4[0-579]|55|6[068]|7[06-8])|4(?:00|3[0-579]|4\\d|7[0-57-9])|5(?:[01]\\d|2[0-7]|[56]0|79)|7(?:0[09]|2[0-26-8]|3[03]|4[04]|5[05-9]|6[05]|7[0-24-9]|8[7-9]|90)|8(?:09|2[89]|3\\d|4[0-24-689]|5[014]|8[02])|9(?:0[5-9]|1[0135-8]|2[036-9]|3[35-79]|40|5[0457-9]|6[05-9]|7[04-9]|8[35-8]|9\\d))\\d{4}"},"mobile": {"possibleLengths": {"national": "7,8"},"exampleNumber": "61234567","nationalNumberPattern": "(?:1[16]1|21[89]|6(?:[02-9]\\d|1[0-6])\\d|8(?:1[01]|7[23]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "8601234","nationalNumberPattern": "(?:8(?:22|55|60|7[78]|86)|9(?:00|81))\\d{4}"}},{"id": "PE","countryCode": "51","internationalPrefix": "19(?:1[124]|77|90)00","nationalPrefix": "0","preferredExtnPrefix": " Anexo ","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "80","format": "$1 $2"},{"pattern": "(\\d)(\\d{7})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[4-8]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "9","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[14-8]|9\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6,7"},"exampleNumber": "11234567","nationalNumberPattern": "19(?:[02-68]\\d|1[035-9]|7[0-689]|9[1-9])\\d{4}|(?:1[0-8]|4[1-4]|5[1-46]|6[1-7]|7[2-46]|8[2-4])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "9\\d{8}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "80512345","nationalNumberPattern": "805\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "80112345","nationalNumberPattern": "801\\d{5}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "80212345","nationalNumberPattern": "80[24]\\d{5}"}},{"id": "PF","countryCode": "689","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "44","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[48]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[48]\\d{7}|4\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "6"},"nationalNumberPattern": "44\\d{4}"},"fixedLine": {"possibleLengths": {"national": "6,8"},"exampleNumber": "40412345","nationalNumberPattern": "4(?:[09][4-689]\\d|4)\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "87123456","nationalNumberPattern": "8[7-9]\\d{6}"}},{"id": "PG","countryCode": "675","preferredInternationalPrefix": "00","internationalPrefix": "00|140[1-3]","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "18|[2-69]|85","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[78]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:180|[78]\\d{3})\\d{4}|(?:[2-589]\\d|64)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7,8"},"exampleNumber": "3123456","nationalNumberPattern": "(?:64[1-9]|7730|85[02-46-9])\\d{4}|(?:3[0-2]|4[257]|5[34]|77[0-24]|9[78])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "70123456","nationalNumberPattern": "775\\d{5}|(?:7[0-689]|81)\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "1801234","nationalNumberPattern": "180\\d{4}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "2751234","nationalNumberPattern": "2(?:0[0-47]|7[568])\\d{4}"}},{"id": "PH","countryCode": "63","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{4,6})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": ["3(?:23|39|46)|4(?:2[3-6]|[35]9|4[26]|76)|544|88[245]|(?:52|64|86)2","3(?:230|397|461)|4(?:2(?:35|[46]4|51)|396|4(?:22|63)|59[347]|76[15])|5(?:221|446)|642[23]|8(?:622|8(?:[24]2|5[13]))"],"format": "$1 $2"},{"pattern": "(\\d{5})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": ["346|4(?:27|9[35])|883","3469|4(?:279|9(?:30|56))|8834"],"format": "$1 $2"},{"pattern": "(\\d)(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[3-7]|8[2-8]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{1,2})(\\d{3})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "1800\\d{7,9}|(?:2|[89]\\d{4})\\d{5}|[2-8]\\d{8}|[28]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "6,[8-10]","localOnly": "4,5,7"},"exampleNumber": "21234567","nationalNumberPattern": "(?:(?:2[3-8]|3[2-68]|4[2-9]|5[2-6]|6[2-58]|7[24578])\\d{3}|88(?:22\\d\\d|42))\\d{4}|2\\d{5}(?:\\d{2})?|8[2-8]\\d{7}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "9051234567","nationalNumberPattern": "(?:81[37]|9(?:0[5-9]|1[0-24-9]|2[0-35-9]|[35]\\d|4[235-9]|6[0-25-8]|7[1-9]|8[19]|9[4-9]))\\d{7}"},"tollFree": {"possibleLengths": {"national": "[11-13]"},"exampleNumber": "180012345678","nationalNumberPattern": "1800\\d{7,9}"}},{"id": "PK","countryCode": "92","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]0","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{5})","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{7,8})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{6,7})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": ["2(?:3[2358]|4[2-4]|9[2-8])|45[3479]|54[2-467]|60[468]|72[236]|8(?:2[2-689]|3[23578]|4[3478]|5[2356])|9(?:2[2-8]|3[27-9]|4[2-6]|6[3569]|9[25-8])","9(?:2[3-8]|98)|(?:2(?:3[2358]|4[2-4]|9[2-8])|45[3479]|54[2-467]|60[468]|72[236]|8(?:2[2-689]|3[23578]|4[3478]|5[2356])|9(?:22|3[27-9]|4[2-6]|6[3569]|9[25-7]))[2-9]"],"format": "$1 $2"},{"pattern": "(\\d{5})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "58","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "3","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[24-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "122\\d{6}|[24-8]\\d{10,11}|9(?:[013-9]\\d{8,10}|2(?:[01]\\d\\d|2(?:[025-8]\\d|1[01]))\\d{7})|(?:[2-8]\\d{3}|92(?:[0-7]\\d|8[1-9]))\\d{6}|[24-9]\\d{8}|[89]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9,10","localOnly": "[5-8]"},"exampleNumber": "2123456789","nationalNumberPattern": "(?:(?:21|42)[2-9]|58[126])\\d{7}|(?:2[25]|4[0146-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]\\d{6}|(?:2(?:3[2358]|4[2-4]|9[2-8])|45[3479]|54[2-467]|60[468]|72[236]|8(?:2[2-689]|3[23578]|4[3478]|5[2356])|9(?:2[2-8]|3[27-9]|4[2-6]|6[3569]|9[25-8]))[2-9]\\d{5,6}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "3012345678","nationalNumberPattern": "3(?:[014]\\d|2[0-5]|3[0-7]|55|64)\\d{7}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90012345","nationalNumberPattern": "900\\d{5}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "122044444","nationalNumberPattern": "122\\d{6}"},"uan": {"possibleLengths": {"national": "11,12"},"exampleNumber": "21111825888","nationalNumberPattern": "(?:2(?:[125]|3[2358]|4[2-4]|9[2-8])|4(?:[0-246-9]|5[3479])|5(?:[1-35-7]|4[2-467])|6(?:0[468]|[1-8])|7(?:[14]|2[236])|8(?:[16]|2[2-689]|3[23578]|4[3478]|5[2356])|9(?:1|22|3[27-9]|4[2-6]|6[3569]|9[2-7]))111\\d{6}"}},{"id": "PL","countryCode": "48","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})","leadingDigits": "19","format": "$1"},{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "11|64","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{2})(\\d{3})","leadingDigits": ["(?:1[2-8]|2[2-69]|3[2-4]|4[1-468]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145])1","(?:1[2-8]|2[2-69]|3[2-4]|4[1-468]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145])19"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{2,3})","leadingDigits": "64","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "39|45|5[0137]|6[0469]|7[02389]|8[08]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "1[2-8]|[2-8]|9[145]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[1-57-9]\\d{6}(?:\\d{2})?|6\\d{5,8}"},"fixedLine": {"possibleLengths": {"national": "7,9"},"exampleNumber": "123456789","nationalNumberPattern": "(?:1[2-8]|2[2-69]|3[2-4]|4[1-468]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145])(?:[02-9]\\d{6}|1(?:[0-8]\\d{5}|9\\d{3}(?:\\d{2})?))"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "512345678","nationalNumberPattern": "(?:45|5[0137]|6[069]|7[2389]|88)\\d{7}"},"pager": {"possibleLengths": {"national": "[6-9]"},"exampleNumber": "641234567","nationalNumberPattern": "64\\d{4,7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "701234567","nationalNumberPattern": "70[01346-8]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "801\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "391234567","nationalNumberPattern": "39\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "804123456","nationalNumberPattern": "804\\d{6}"}},{"id": "PM","countryCode": "508","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[45]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[45]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "430123","nationalNumberPattern": "(?:4[1-3]|50)\\d{4}"},"mobile": {"possibleLengths": {"national": "6"},"exampleNumber": "551234","nationalNumberPattern": "(?:4[02-4]|5[05])\\d{4}"}},{"id": "PR","countryCode": "1","leadingDigits": "787|939","internationalPrefix": "011","nationalPrefix": "1","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "(?:[589]\\d\\d|787)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7872345678","nationalNumberPattern": "(?:787|939)[2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7872345678","nationalNumberPattern": "(?:787|939)[2-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "PS","countryCode": "970","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2489]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2489]2\\d{6}|(?:1\\d|5)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "22234567","nationalNumberPattern": "(?:22[2-47-9]|42[45]|82[01458]|92[369])\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "599123456","nationalNumberPattern": "5[69]\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "1800123456","nationalNumberPattern": "1800\\d{6}"},"sharedCost": {"possibleLengths": {"national": "10"},"exampleNumber": "1700123456","nationalNumberPattern": "1700\\d{6}"}},{"id": "PT","countryCode": "351","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{4})","leadingDigits": "2[12]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[236-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[26-9]\\d|30)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "212345678","nationalNumberPattern": "2(?:[12]\\d|[35][1-689]|4[1-59]|6[1-35689]|7[1-9]|8[1-69]|9[1256])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "6[356]9230\\d{3}|(?:6[036]93|9(?:[1-36]\\d\\d|480))\\d{5}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "80[02]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "760123456","nationalNumberPattern": "(?:6(?:0[178]|4[68])\\d|76(?:0[1-57]|1[2-47]|2[237]))\\d{5}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "808123456","nationalNumberPattern": "80(?:8\\d|9[1579])\\d{5}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "884123456","nationalNumberPattern": "884[0-4689]\\d{5}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "301234567","nationalNumberPattern": "30\\d{7}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "707123456","nationalNumberPattern": "70(?:7\\d|8[17])\\d{5}"},"voicemail": {"possibleLengths": {"national": "9"},"exampleNumber": "600110000","nationalNumberPattern": "600\\d{6}"}},{"id": "PW","countryCode": "680","internationalPrefix": "01[12]","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[25-8]\\d\\d|345|488|900)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2771234","nationalNumberPattern": "(?:2(?:55|77)|345|488|5(?:35|44|87)|6(?:22|54|79)|7(?:33|47)|8(?:24|55|76)|900)\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "6201234","nationalNumberPattern": "(?:6[2-4689]0|77\\d|88[0-4])\\d{4}"}},{"id": "PY","countryCode": "595","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-9]0","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[26]1|3[289]|4[1246-8]|7[1-3]|8[1-36]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4,5})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "2[279]|3[13-5]|4[359]|5|6[347]|7[46-8]|85","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[26]1|3[289]|4[1246-8]|7[1-3]|8[1-36]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","leadingDigits": "87","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-8]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "59\\d{4,6}|(?:[2-46-9]\\d|5[0-8])\\d{4,7}"},"fixedLine": {"possibleLengths": {"national": "[7-9]","localOnly": "5,6"},"exampleNumber": "212345678","nationalNumberPattern": "(?:[26]1|3[289]|4[1246-8]|7[1-3]|8[1-36])\\d{5,7}|(?:2(?:2[4-68]|7[15]|9[1-5])|3(?:18|3[167]|4[2357]|51)|4(?:3[12]|5[13]|9[1-47])|5(?:[1-4]\\d|5[02-4])|6(?:3[1-3]|44|7[1-46-8])|7(?:4[0-4]|6[1-578]|75|8[0-8])|858)\\d{5,6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "961456789","nationalNumberPattern": "9(?:51|6[129]|[78][1-6]|9[1-5])\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "870012345","nationalNumberPattern": "8700[0-4]\\d{4}"},"uan": {"possibleLengths": {"national": "[6-9]"},"exampleNumber": "201234567","nationalNumberPattern": "[2-9]0\\d{4,7}"}},{"id": "QA","countryCode": "974","internationalPrefix": "00","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "2[126]|8","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[2-7]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[2-7]\\d{7}|(?:2\\d\\d|800)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "44123456","nationalNumberPattern": "4[04]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "33123456","nationalNumberPattern": "(?:28|[35-7]\\d)\\d{6}"},"pager": {"possibleLengths": {"national": "7"},"exampleNumber": "2123456","nationalNumberPattern": "2(?:[12]\\d|61)\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}"}},{"id": "RE","mainCountryForCode": "true","countryCode": "262","leadingDigits": "26[23]|69|[89]","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2689]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "9769\\d{5}|(?:26|[68]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "262161234","nationalNumberPattern": "26(?:2\\d\\d|30[01])\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "692123456","nationalNumberPattern": "(?:69(?:2\\d\\d|3(?:0[0-46]|1[013]|2[0-2]|3[0-39]|4\\d|5[05]|6[0-26]|7[0-27]|8[03-8]|9[0-479]))|9769\\d)\\d{4}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "891123456","nationalNumberPattern": "89[1-37-9]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "810123456","nationalNumberPattern": "8(?:1[019]|2[0156]|84|90)\\d{6}"}},{"id": "RO","countryCode": "40","internationalPrefix": "00","nationalPrefix": "0","preferredExtnPrefix": " int ","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["2[3-6]","2[3-6]\\d9"],"format": "$1 $2"},{"pattern": "(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "219|31","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[23]1","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[237-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[237]\\d|[89]0)\\d{7}|[23]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6,9"},"exampleNumber": "211234567","nationalNumberPattern": "[23][13-6]\\d{7}|(?:2(?:19\\d|[3-6]\\d9)|31\\d\\d)\\d\\d"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712034567","nationalNumberPattern": "7120\\d{5}|7(?:[02-7]\\d|1[01]|8[03-8]|9[09])\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "90[036]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "801123456","nationalNumberPattern": "801\\d{6}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "372123456","nationalNumberPattern": "37\\d{7}"}},{"id": "RS","countryCode": "381","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3,9})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "(?:2[389]|39)0|[7-9]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{5,10})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-36]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "38[02-9]\\d{6,9}|6\\d{7,9}|90\\d{4,8}|38\\d{5,6}|(?:7\\d\\d|800)\\d{3,9}|(?:[12]\\d|3[0-79])\\d{5,10}"},"fixedLine": {"possibleLengths": {"national": "[7-12]","localOnly": "[4-6]"},"exampleNumber": "10234567","nationalNumberPattern": "(?:11[1-9]\\d|(?:2[389]|39)(?:0[2-9]|[2-9]\\d))\\d{3,8}|(?:1[02-9]|2[0-24-7]|3[0-8])[2-9]\\d{4,9}"},"mobile": {"possibleLengths": {"national": "[8-10]"},"exampleNumber": "601234567","nationalNumberPattern": "6(?:[0-689]|7\\d)\\d{6,7}"},"tollFree": {"possibleLengths": {"national": "[6-12]"},"exampleNumber": "80012345","nationalNumberPattern": "800\\d{3,9}"},"premiumRate": {"possibleLengths": {"national": "[6-10]"},"exampleNumber": "90012345","nationalNumberPattern": "(?:78\\d|90[0169])\\d{3,7}"},"uan": {"possibleLengths": {"national": "[6-12]"},"exampleNumber": "700123456","nationalNumberPattern": "7[06]\\d{4,10}"}},{"id": "RU","mainCountryForCode": "true","countryCode": "7","leadingDigits": "3[04-689]|[489]","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "[0-79]","format": "$1-$2-$3","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP ($FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["7(?:1[0-8]|2[1-9])","7(?:1(?:[0-6]2|7|8[27])|2(?:1[23]|[2-9]2))","7(?:1(?:[0-6]2|7|8[27])|2(?:13[03-69]|62[013-9]))|72[1-57-9]2"],"format": "$1 $2 $3 $4"},{"pattern": "(\\d{5})(\\d)(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP ($FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["7(?:1[0-68]|2[1-9])","7(?:1(?:[06][3-6]|[18]|2[35]|[3-5][3-5])|2(?:[13][3-5]|[24-689]|7[457]))","7(?:1(?:0(?:[356]|4[023])|[18]|2(?:3[013-9]|5)|3[45]|43[013-79]|5(?:3[1-8]|4[1-7]|5)|6(?:3[0-35-9]|[4-6]))|2(?:1(?:3[178]|[45])|[24-689]|3[35]|7[457]))|7(?:14|23)4[0-8]|71(?:33|45)[1-79]"],"format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP ($FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP ($FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[3489]","format": "$1 $2-$3-$4"}]},"generalDesc": {"nationalNumberPattern": "[347-9]\\d{9}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "3011234567","nationalNumberPattern": "(?:3(?:0[12]|4[1-35-79]|5[1-3]|65|8[1-58]|9[0145])|4(?:01|1[1356]|2[13467]|7[1-5]|8[1-7]|9[1-689])|8(?:1[1-8]|2[01]|3[13-6]|4[0-8]|5[15]|6[1-35-79]|7[1-37-9]))\\d{7}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "9123456789","nationalNumberPattern": "9\\d{9}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "80[04]\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "8091234567","nationalNumberPattern": "80[39]\\d{7}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "8081234567","nationalNumberPattern": "808\\d{7}"}},{"id": "RW","countryCode": "250","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "0","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[7-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:06|[27]\\d\\d|[89]00)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "250123456","nationalNumberPattern": "(?:06|2[258]\\d)\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "720123456","nationalNumberPattern": "7[238]\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "900\\d{6}"}},{"id": "SA","countryCode": "966","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{5})","leadingDigits": "9","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "5","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "81","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "8","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "92\\d{7}|(?:[15]|8\\d)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "7"},"exampleNumber": "112345678","nationalNumberPattern": "1(?:1\\d|2[24-8]|3[35-8]|4[3-68]|6[2-5]|7[235-7])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "512345678","nationalNumberPattern": "5(?:[013-689]\\d|7[0-36-8])\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "925012345","nationalNumberPattern": "925\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "920012345","nationalNumberPattern": "920\\d{6}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "8110123456","nationalNumberPattern": "811\\d{7}"}},{"id": "SB","countryCode": "677","internationalPrefix": "0[01]","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{5})","leadingDigits": "7|8[4-9]|9(?:[1-8]|9[0-8])","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[1-6]|[7-9]\\d\\d)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "40123","nationalNumberPattern": "(?:1[4-79]|[23]\\d|4[0-2]|5[03]|6[0-37])\\d{3}"},"mobile": {"possibleLengths": {"national": "5,7"},"exampleNumber": "7421234","nationalNumberPattern": "48\\d{3}|(?:(?:7[1-9]|8[4-9])\\d|9(?:1[2-9]|2[013-9]|3[0-2]|[46]\\d|5[0-46-9]|7[0-689]|8[0-79]|9[0-8]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "5"},"exampleNumber": "18123","nationalNumberPattern": "1[38]\\d{3}"},"voip": {"possibleLengths": {"national": "5"},"exampleNumber": "51123","nationalNumberPattern": "5[12]\\d{3}"}},{"id": "SC","countryCode": "248","preferredInternationalPrefix": "00","internationalPrefix": "010|0[0-2]","availableFormats": {"numberFormat": {"pattern": "(\\d)(\\d{3})(\\d{3})","leadingDigits": "[246]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "8000\\d{3}|(?:[249]\\d|64)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "4217123","nationalNumberPattern": "4[2-46]\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "2510123","nationalNumberPattern": "2[5-8]\\d{5}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8000000","nationalNumberPattern": "8000\\d{3}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "6412345","nationalNumberPattern": "971\\d{4}|(?:64|95)\\d{5}"}},{"id": "SD","countryCode": "249","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[19]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[19]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "153123456","nationalNumberPattern": "1(?:5[3-7]|8[35-7])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "911231234","nationalNumberPattern": "(?:1[0-2]|9[0-3569])\\d{7}"}},{"id": "SE","countryCode": "46","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2,3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "20","format": "$1-$2 $3","intlFormat": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9(?:00|39|44)","format": "$1-$2","intlFormat": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[12][136]|3[356]|4[0246]|6[03]|90[1-9]","format": "$1-$2 $3","intlFormat": "$1 $2 $3"},{"pattern": "(\\d)(\\d{2,3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2,3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[2457]|2(?:[247-9]|5[0138])|3[0247-9]|4[1357-9]|5[0-35-9]|6(?:[125689]|4[02-57]|7[0-2])|9(?:[125-8]|3[02-5]|4[0-3])","format": "$1-$2 $3","intlFormat": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2,3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9(?:00|39|44)","format": "$1-$2 $3","intlFormat": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2,3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1[013689]|2[0136]|3[1356]|4[0246]|54|6[03]|90[1-9]","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[13-5]|2(?:[247-9]|5[0138])|6(?:[124-689]|7[0-2])|9(?:[125-8]|3[02-5]|4[0-3])","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1-$2 $3 $4","intlFormat": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[26]","format": "$1-$2 $3 $4 $5","intlFormat": "$1 $2 $3 $4 $5"}]},"generalDesc": {"nationalNumberPattern": "(?:[26]\\d\\d|9)\\d{9}|[1-9]\\d{8}|[1-689]\\d{7}|[1-4689]\\d{6}|2\\d{5}"},"fixedLine": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "8123456","nationalNumberPattern": "10[1-8]\\d{6}|90[1-9]\\d{4,6}|(?:[12][136]|3[356]|4[0246]|6[03]|8\\d)\\d{5,7}|(?:1(?:2[0-35]|4[0-4]|5[0-25-9]|7[13-6]|[89]\\d)|2(?:2[0-7]|4[0136-8]|5[0138]|7[018]|8[01]|9[0-57])|3(?:0[0-4]|1\\d|2[0-25]|4[056]|7[0-2]|8[0-3]|9[023])|4(?:1[013-8]|3[0135]|5[14-79]|7[0-246-9]|8[0156]|9[0-689])|5(?:0[0-6]|[15][0-5]|2[0-68]|3[0-4]|4\\d|6[03-5]|7[013]|8[0-79]|9[01])|6(?:1[1-3]|2[0-4]|4[02-57]|5[0-37]|6[0-3]|7[0-2]|8[0247]|9[0-356])|9(?:1[0-68]|2\\d|3[02-5]|4[0-3]|5[0-4]|[68][01]|7[0135-8]))\\d{5,6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "701234567","nationalNumberPattern": "7[02369]\\d{7}"},"pager": {"possibleLengths": {"national": "9"},"exampleNumber": "740123456","nationalNumberPattern": "74[02-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "[6-9]"},"exampleNumber": "20123456","nationalNumberPattern": "20\\d{4,7}"},"premiumRate": {"possibleLengths": {"national": "[7-10]"},"exampleNumber": "9001234567","nationalNumberPattern": "649\\d{6}|9(?:00|39|44)[1-8]\\d{3,6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "771234567","nationalNumberPattern": "77[0-7]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "751234567","nationalNumberPattern": "75[1-8]\\d{6}"},"voicemail": {"possibleLengths": {"national": "12"},"exampleNumber": "254123456789","nationalNumberPattern": "(?:25[245]|67[3-68])\\d{9}"}},{"id": "SG","countryCode": "65","internationalPrefix": "0[0-3]\\d","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{4,5})","leadingDigits": ["1[013-9]|77","1(?:[013-8]|9(?:0[1-9]|[1-9]))|77"],"format": "$1","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[369]|8[1-9]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","leadingDigits": "8","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{4})(\\d{3})","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:(?:1\\d|8)\\d\\d|7000)\\d{7}|[3689]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "61234567","nationalNumberPattern": "662[0-24-9]\\d{4}|6(?:[1-578]\\d|6[013-57-9]|9[0-35-9])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "81234567","nationalNumberPattern": "(?:8(?:[1-8]\\d\\d|9(?:[01]\\d|2[4-8]|3[0-4]))|9[0-8]\\d\\d)\\d{4}"},"tollFree": {"possibleLengths": {"national": "10,11"},"exampleNumber": "18001234567","nationalNumberPattern": "(?:18|8)00\\d{7}"},"premiumRate": {"possibleLengths": {"national": "11"},"exampleNumber": "19001234567","nationalNumberPattern": "1900\\d{7}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "31234567","nationalNumberPattern": "(?:3[12]\\d\\d|6666)\\d{4}"},"uan": {"possibleLengths": {"national": "11"},"exampleNumber": "70001234567","nationalNumberPattern": "7000\\d{7}"}},{"id": "SH","mainCountryForCode": "true","countryCode": "290","leadingDigits": "[256]","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "(?:[256]\\d|8)\\d{3}"},"fixedLine": {"possibleLengths": {"national": "4,5"},"exampleNumber": "22158","nationalNumberPattern": "2(?:[0-57-9]\\d|6[4-9])\\d\\d"},"mobile": {"possibleLengths": {"national": "5"},"exampleNumber": "51234","nationalNumberPattern": "[56]\\d{4}"},"voip": {"possibleLengths": {"national": "5"},"exampleNumber": "26212","nationalNumberPattern": "262\\d\\d"}},{"id": "SI","countryCode": "386","preferredInternationalPrefix": "00","internationalPrefix": "00|10(?:22|66|88|99)","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3,6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8[09]|9","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "59|8","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[37][01]|4[0139]|51|6","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[1-57]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[1-7]\\d{7}|8\\d{4,7}|90\\d{4,6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "12345678","nationalNumberPattern": "(?:[1-357][2-8]|4[24-8])\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "31234567","nationalNumberPattern": "65(?:1\\d|55|[67]0)\\d{4}|(?:[37][01]|4[0139]|51|6[489])\\d{6}"},"tollFree": {"possibleLengths": {"national": "[6-8]"},"exampleNumber": "80123456","nationalNumberPattern": "80\\d{4,6}"},"premiumRate": {"possibleLengths": {"national": "[5-8]"},"exampleNumber": "90123456","nationalNumberPattern": "89[1-3]\\d{2,5}|90\\d{4,6}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "59012345","nationalNumberPattern": "(?:59\\d\\d|8(?:1(?:[67]\\d|8[01389])|2(?:0\\d|2[0378]|8[0-2489])|3[389]\\d))\\d{4}"}},{"id": "SJ","countryCode": "47","leadingDigits": "79","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "0\\d{4}|(?:[4589]\\d|79)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "79123456","nationalNumberPattern": "79\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "41234567","nationalNumberPattern": "(?:4[015-8]|5[89]|9\\d)\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80012345","nationalNumberPattern": "80[01]\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "82012345","nationalNumberPattern": "82[09]\\d{5}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "81021234","nationalNumberPattern": "810(?:0[0-6]|[2-8]\\d)\\d{3}"},"personalNumber": {"possibleLengths": {"national": "8"},"exampleNumber": "88012345","nationalNumberPattern": "880\\d{5}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "85012345","nationalNumberPattern": "85[0-5]\\d{5}"},"uan": {"possibleLengths": {"national": "5,8"},"exampleNumber": "02000","nationalNumberPattern": "(?:0[2-9]|81(?:0(?:0[7-9]|1\\d)|5\\d\\d))\\d{3}"},"voicemail": {"possibleLengths": {"national": "8"},"exampleNumber": "81212345","nationalNumberPattern": "81[23]\\d{5}"}},{"id": "SK","countryCode": "421","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{2})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "21","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{2})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[3-5][1-8]1","[3-5][1-8]1[67]"],"format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["909","9090"],"format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d)(\\d{3})(\\d{3})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1/$2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[689]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-5]","format": "$1/$2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "[2-689]\\d{8}|[2-59]\\d{6}|[2-5]\\d{5}"},"noInternationalDialling": {"possibleLengths": {"national": "7,9"},"nationalNumberPattern": "9090\\d{3}|(?:602|8(?:00|[5-9]\\d)|9(?:00|[78]\\d))\\d{6}"},"fixedLine": {"possibleLengths": {"national": "6,7,9"},"exampleNumber": "221234567","nationalNumberPattern": "(?:2(?:16|[2-9]\\d{3})|[3-5][1-8]\\d{3})\\d{4}|(?:2|[3-5][1-8])1[67]\\d{3}|[3-5][1-8]16\\d\\d"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912123456","nationalNumberPattern": "909[1-9]\\d{5}|9(?:0[1-8]|1[0-24-9]|[45]\\d)\\d{6}"},"pager": {"possibleLengths": {"national": "7"},"exampleNumber": "9090123","nationalNumberPattern": "9090\\d{3}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "9(?:00|[78]\\d)\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "850123456","nationalNumberPattern": "8[5-9]\\d{7}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "690123456","nationalNumberPattern": "6(?:02|5[0-4]|9[0-6])\\d{6}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "961234567","nationalNumberPattern": "96\\d{7}"}},{"id": "SL","countryCode": "232","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": "[237-9]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[2378]\\d|99)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "6"},"exampleNumber": "22221234","nationalNumberPattern": "22\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "25123456","nationalNumberPattern": "(?:25|3[0134]|7[5-9]|8[08]|99)\\d{6}"}},{"id": "SM","countryCode": "378","internationalPrefix": "00","nationalPrefixForParsing": "([89]\\d{5})$","nationalPrefixTransformRule": "0549$1","availableFormats": {"numberFormat": [{"pattern": "(\\d{6})","leadingDigits": "[89]","format": "$1","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[5-7]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{4})(\\d{6})","leadingDigits": "0","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:0549|[5-7]\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "6"},"exampleNumber": "0549886377","nationalNumberPattern": "0549(?:8[0157-9]|9\\d)\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "66661212","nationalNumberPattern": "6[16]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "71123456","nationalNumberPattern": "7[178]\\d{6}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "58001110","nationalNumberPattern": "5[158]\\d{6}"}},{"id": "SN","countryCode": "221","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "8","format": "$1 $2 $3 $4"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","leadingDigits": "[379]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[378]\\d{4}|93330)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "301012345","nationalNumberPattern": "3(?:0(?:1[0-2]|80)|282|3(?:8[1-9]|9[3-9])|611)\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "701234567","nationalNumberPattern": "7(?:[06-8]\\d|21|90)\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "884123456","nationalNumberPattern": "88[4689]\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "810123456","nationalNumberPattern": "81[02468]\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "933301234","nationalNumberPattern": "93330\\d{4}|3(?:392|9[01]\\d)\\d{5}"}},{"id": "SO","countryCode": "252","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{4})","leadingDigits": "8[125]","format": "$1 $2"},{"pattern": "(\\d{6})","leadingDigits": "[134]","format": "$1"},{"pattern": "(\\d)(\\d{6})","leadingDigits": "1|2[0-79]|3[0-46-8]|4[0-7]|59","format": "$1 $2"},{"pattern": "(\\d)(\\d{7})","leadingDigits": "24|[67]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[348]|64|79[0-8]|90","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{5,7})","leadingDigits": "1|28|6[1-35-9]|799|9[2-9]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[346-9]\\d{8}|[12679]\\d{7}|(?:[1-4]\\d|59)\\d{5}|[1348]\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6,7"},"exampleNumber": "4012345","nationalNumberPattern": "(?:1\\d|2[0-79]|3[0-46-8]|4[0-7]|59)\\d{5}|(?:[134]\\d|8[125])\\d{4}"},"mobile": {"possibleLengths": {"national": "[7-9]"},"exampleNumber": "71123456","nationalNumberPattern": "28\\d{5}|(?:6[1-9]|79)\\d{6,7}|(?:15|24|(?:3[59]|4[89]|8[08])\\d|60|7[1-8]|9(?:0[67]|[2-9]))\\d{6}"}},{"id": "SR","countryCode": "597","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "56","format": "$1-$2-$3"},{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "[2-5]","format": "$1-$2"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[6-8]","format": "$1-$2"}]},"generalDesc": {"nationalNumberPattern": "(?:[2-5]|68|[78]\\d)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "6,7"},"exampleNumber": "211234","nationalNumberPattern": "(?:2[1-3]|3[0-7]|(?:4|68)\\d|5[2-58])\\d{4}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7412345","nationalNumberPattern": "(?:7[124-7]|8[125-9])\\d{5}"},"voip": {"possibleLengths": {"national": "6"},"exampleNumber": "561234","nationalNumberPattern": "56\\d{4}"}},{"id": "SS","countryCode": "211","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[19]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[19]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "181234567","nationalNumberPattern": "18\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "977123456","nationalNumberPattern": "(?:12|9[1257])\\d{7}"}},{"id": "ST","countryCode": "239","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[29]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:22|9\\d)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2221234","nationalNumberPattern": "22\\d{5}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "9812345","nationalNumberPattern": "900[5-9]\\d{3}|9(?:0[1-9]|[89]\\d)\\d{4}"}},{"id": "SV","countryCode": "503","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[89]","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[267]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[267]\\d{7}|[89]00\\d{4}(?:\\d{4})?"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "2[1-6]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "70123456","nationalNumberPattern": "[67]\\d{7}"},"tollFree": {"possibleLengths": {"national": "7,11"},"exampleNumber": "8001234","nationalNumberPattern": "800\\d{4}(?:\\d{4})?"},"premiumRate": {"possibleLengths": {"national": "7,11"},"exampleNumber": "9001234","nationalNumberPattern": "900\\d{4}(?:\\d{4})?"}},{"id": "SX","countryCode": "1","leadingDigits": "721","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|(5\\d{6})$","nationalPrefixTransformRule": "721$1","generalDesc": {"nationalNumberPattern": "7215\\d{6}|(?:[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7215425678","nationalNumberPattern": "7215(?:4[2-8]|8[239]|9[056])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7215205678","nationalNumberPattern": "7215(?:1[02]|2\\d|5[034679]|8[014-8])\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002123456","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002123456","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "SY","countryCode": "963","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[1-5]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "9","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[1-39]\\d{8}|[1-5]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8,9","localOnly": "6,7"},"exampleNumber": "112345678","nationalNumberPattern": "[12]1\\d{6,7}|(?:1(?:[2356]|4\\d)|2[235]|3(?:[13]\\d|4)|4[13]|5[1-3])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "944567890","nationalNumberPattern": "9(?:22|[3-589]\\d|6[024-9])\\d{6}"}},{"id": "SZ","countryCode": "268","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[0237]","format": "$1 $2"},{"pattern": "(\\d{5})(\\d{4})","leadingDigits": "9","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "0800\\d{4}|(?:[237]\\d|900)\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "8"},"nationalNumberPattern": "0800\\d{4}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22171234","nationalNumberPattern": "[23][2-5]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "76123456","nationalNumberPattern": "7[6-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "08001234","nationalNumberPattern": "0800\\d{4}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900012345","nationalNumberPattern": "900\\d{6}"},"voip": {"possibleLengths": {"national": "8"},"exampleNumber": "70012345","nationalNumberPattern": "70\\d{6}"}},{"id": "TA","countryCode": "290","leadingDigits": "8","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "8\\d{3}"},"fixedLine": {"possibleLengths": {"national": "4"},"exampleNumber": "8999","nationalNumberPattern": "8\\d{3}"}},{"id": "TC","countryCode": "1","leadingDigits": "649","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-479]\\d{6})$","nationalPrefixTransformRule": "649$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|649|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6497121234","nationalNumberPattern": "649(?:712|9(?:4\\d|50))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6492311234","nationalNumberPattern": "649(?:2(?:3[129]|4[1-7])|3(?:3[1-389]|4[1-8])|4[34][1-3])\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"voip": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "6497101234","nationalNumberPattern": "64971[01]\\d{4}"}},{"id": "TD","countryCode": "235","preferredInternationalPrefix": "00","internationalPrefix": "00|16","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[2679]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "(?:22|[69]\\d|77)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22501234","nationalNumberPattern": "22(?:[37-9]0|5[0-5]|6[89])\\d{4}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "63012345","nationalNumberPattern": "(?:6[023568]|77|9\\d)\\d{6}"}},{"id": "TG","countryCode": "228","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[279]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[279]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "22212345","nationalNumberPattern": "2(?:2[2-7]|3[23]|4[45]|55|6[67]|77)\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "90112345","nationalNumberPattern": "(?:7[09]|9[0-36-9])\\d{6}"}},{"id": "TH","countryCode": "66","internationalPrefix": "00[1-9]","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "14|[3-9]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3})(\\d{3})","leadingDigits": "1","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{8,9}|(?:[2-57]|[689]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "21234567","nationalNumberPattern": "(?:2\\d|3[2-9]|4[2-5]|5[2-6]|7[3-7])\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "812345678","nationalNumberPattern": "(?:14|6[1-6]|[89]\\d)\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "1800123456","nationalNumberPattern": "1800\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "1900123456","nationalNumberPattern": "1900\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "601234567","nationalNumberPattern": "6[08]\\d{7}"}},{"id": "TJ","countryCode": "992","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","availableFormats": {"numberFormat": [{"pattern": "(\\d{6})(\\d)(\\d{2})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["331","3317"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{2})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[34]7|91[78]","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d)(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "3","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[0457-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:00|[3-59]\\d|77|88)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "3,[5-7]"},"exampleNumber": "372123456","nationalNumberPattern": "(?:3(?:1[3-5]|2[245]|3[12]|4[24-7]|5[25]|72)|4(?:46|74|87))\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "917123456","nationalNumberPattern": "41[18]\\d{6}|(?:00|5[05]|77|88|9\\d)\\d{7}"}},{"id": "TK","countryCode": "690","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "[2-47]\\d{3,6}"},"fixedLine": {"possibleLengths": {"national": "[4-7]"},"exampleNumber": "3101","nationalNumberPattern": "(?:2[2-4]|[34]\\d)\\d{2,5}"},"mobile": {"possibleLengths": {"national": "[4-7]"},"exampleNumber": "7290","nationalNumberPattern": "7[2-4]\\d{2,5}"}},{"id": "TL","countryCode": "670","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-489]|70","format": "$1 $2"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "7","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "7\\d{7}|(?:[2-47]\\d|[89]0)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "7"},"exampleNumber": "2112345","nationalNumberPattern": "(?:2[1-5]|3[1-9]|4[1-4])\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "77212345","nationalNumberPattern": "7[3-8]\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8012345","nationalNumberPattern": "80\\d{5}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9012345","nationalNumberPattern": "90\\d{5}"},"personalNumber": {"possibleLengths": {"national": "7"},"exampleNumber": "7012345","nationalNumberPattern": "70\\d{5}"}},{"id": "TM","countryCode": "993","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{2})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "($NP $FG)","leadingDigits": "12","format": "$1 $2-$3-$4"},{"pattern": "(\\d{3})(\\d)(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "($NP $FG)","leadingDigits": "[1-5]","format": "$1 $2-$3-$4"},{"pattern": "(\\d{2})(\\d{6})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "6","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[1-6]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "12345678","nationalNumberPattern": "(?:1(?:2\\d|3[1-9])|2(?:22|4[0-35-8])|3(?:22|4[03-9])|4(?:22|3[128]|4\\d|6[15])|5(?:22|5[7-9]|6[014-689]))\\d{5}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "66123456","nationalNumberPattern": "6[1-9]\\d{6}"}},{"id": "TN","countryCode": "216","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{3})","leadingDigits": "[2-57-9]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[2-57-9]\\d{7}"},"fixedLine": {"possibleLengths": {"national": "8"},"exampleNumber": "30010123","nationalNumberPattern": "81200\\d{3}|(?:3[0-2]|7\\d)\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "20123456","nationalNumberPattern": "3(?:001|[12]40)\\d{4}|(?:(?:[259]\\d|4[0-6])\\d|3(?:1[1-35]|6[0-4]|91))\\d{5}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80101234","nationalNumberPattern": "8010\\d{4}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "88123456","nationalNumberPattern": "88\\d{6}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "81101234","nationalNumberPattern": "8[12]10\\d{4}"}},{"id": "TO","countryCode": "676","internationalPrefix": "00","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3})","leadingDigits": "[2-4]|50|6[09]|7[0-24-69]|8[05]","format": "$1-$2"},{"pattern": "(\\d{4})(\\d{3})","leadingDigits": "0","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[5-8]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:0800|[5-8]\\d{3})\\d{3}|[2-8]\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "20123","nationalNumberPattern": "(?:2\\d|3[0-8]|4[0-4]|50|6[09]|7[0-24-69]|8[05])\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "7715123","nationalNumberPattern": "(?:6(?:3[02]|85|90)|7(?:[2-46]0|[578]\\d)|8[46-9]\\d)\\d{4}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "0800222","nationalNumberPattern": "0800\\d{3}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "5501234","nationalNumberPattern": "55[04]\\d{4}"}},{"id": "TR","countryCode": "90","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d)(\\d{3})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "444","format": "$1 $2 $3","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "512|8[0589]|90","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": ["5(?:[0-59]|61)","5(?:[0-59]|616)","5(?:[0-59]|6161)"],"format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "($NP$FG)","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[24][1-8]|3[1-9]","format": "$1 $2 $3 $4"}]},"generalDesc": {"nationalNumberPattern": "(?:[2-58]\\d\\d|900)\\d{7}|4\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "7"},"nationalNumberPattern": "444\\d{4}"},"fixedLine": {"possibleLengths": {"national": "10"},"exampleNumber": "2123456789","nationalNumberPattern": "(?:2(?:[13][26]|[28][2468]|[45][268]|[67][246])|3(?:[13][28]|[24-6][2468]|[78][02468]|92)|4(?:[16][246]|[23578][2468]|4[26]))\\d{7}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "5012345678","nationalNumberPattern": "56161\\d{5}|5(?:0[15-7]|1[06]|24|[34]\\d|5[1-59]|9[46])\\d{7}"},"pager": {"possibleLengths": {"national": "10"},"exampleNumber": "5123456789","nationalNumberPattern": "512\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "(?:8[89]8|900)\\d{7}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5922121234","nationalNumberPattern": "592(?:21[12]|461)\\d{4}"},"uan": {"possibleLengths": {"national": "7,10"},"exampleNumber": "4441444","nationalNumberPattern": "(?:444|850\\d{3})\\d{4}"}},{"id": "TT","countryCode": "1","leadingDigits": "868","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-46-8]\\d{6})$","nationalPrefixTransformRule": "868$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8682211234","nationalNumberPattern": "868(?:2(?:01|1[89]|[23]\\d|4[0-2])|6(?:0[7-9]|1[02-8]|2[1-9]|[3-69]\\d|7[0-79])|82[124])\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8682911234","nationalNumberPattern": "868(?:2(?:6[6-9]|[7-9]\\d)|[37](?:0[1-9]|1[02-9]|[2-9]\\d)|4[6-9]\\d|6(?:20|78|8\\d))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"voicemail": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "8686191234","nationalNumberPattern": "868619\\d{4}"}},{"id": "TV","countryCode": "688","internationalPrefix": "00","generalDesc": {"nationalNumberPattern": "(?:2|7\\d\\d|90)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "20123","nationalNumberPattern": "2[02-9]\\d{3}"},"mobile": {"possibleLengths": {"national": "6,7"},"exampleNumber": "901234","nationalNumberPattern": "(?:7[01]\\d|90)\\d{4}"}},{"id": "TW","countryCode": "886","internationalPrefix": "0(?:0[25-79]|19)","nationalPrefix": "0","preferredExtnPrefix": "#","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d)(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "202","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[258]0","format": "$1 $2 $3"},{"pattern": "(\\d)(\\d{3,4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["[23568]|4(?:0[02-48]|[1-47-9])|7[1-9]","[23568]|4(?:0[2-48]|[1-47-9])|(?:400|7)[1-9]"],"format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[49]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[2-689]\\d{8}|7\\d{9,10}|[2-8]\\d{7}|2\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "221234567","nationalNumberPattern": "(?:2[2-8]\\d|370|55[01]|7[1-9])\\d{6}|4(?:(?:0(?:0[1-9]|[2-48]\\d)|1[023]\\d)\\d{4,5}|(?:[239]\\d\\d|4(?:0[56]|12|49))\\d{5})|6(?:[01]\\d{7}|4(?:0[56]|12|24|4[09])\\d{4,5})|8(?:(?:2(?:3\\d|4[0-269]|[578]0|66)|36[24-9]|90\\d\\d)\\d{4}|4(?:0[56]|12|24|4[09])\\d{4,5})|(?:2(?:2(?:0\\d\\d|4(?:0[68]|[249]0|3[0-467]|5[0-25-9]|6[0235689]))|(?:3(?:[09]\\d|1[0-4])|(?:4\\d|5[0-49]|6[0-29]|7[0-5])\\d)\\d)|(?:(?:3[2-9]|5[2-8]|6[0-35-79]|8[7-9])\\d\\d|4(?:2(?:[089]\\d|7[1-9])|(?:3[0-4]|[78]\\d|9[01])\\d))\\d)\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "(?:40001[0-2]|9[0-8]\\d{4})\\d{3}"},"tollFree": {"possibleLengths": {"national": "8,9"},"exampleNumber": "800123456","nationalNumberPattern": "80[0-79]\\d{6}|800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "7,9"},"exampleNumber": "203123456","nationalNumberPattern": "20(?:[013-9]\\d\\d|2)\\d{4}"},"personalNumber": {"possibleLengths": {"national": "9"},"exampleNumber": "990123456","nationalNumberPattern": "99\\d{7}"},"voip": {"possibleLengths": {"national": "10,11"},"exampleNumber": "7012345678","nationalNumberPattern": "7010(?:[0-2679]\\d|3[0-7]|8[0-5])\\d{5}|70\\d{8}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "500123456","nationalNumberPattern": "50[0-46-9]\\d{6}"}},{"id": "TZ","countryCode": "255","internationalPrefix": "00[056]","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{2})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[24]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[67]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:[26-8]\\d|41|90)\\d{7}"},"noInternationalDialling": {"possibleLengths": {"national": "9"},"nationalNumberPattern": "(?:8(?:[04]0|6[01])|90\\d)\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "222345678","nationalNumberPattern": "2[2-8]\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "621234567","nationalNumberPattern": "(?:6[2-9]|7[13-9])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "80[08]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "900123456","nationalNumberPattern": "90\\d{7}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "840123456","nationalNumberPattern": "8(?:40|6[01])\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "412345678","nationalNumberPattern": "41\\d{7}"}},{"id": "UA","countryCode": "380","preferredInternationalPrefix": "0~0","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["6[12][29]|(?:3[1-8]|4[136-8]|5[12457]|6[49])2|(?:56|65)[24]","6[12][29]|(?:35|4[1378]|5[12457]|6[49])2|(?:56|65)[24]|(?:3[1-46-8]|46)2[013-9]"],"format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["4[45][0-5]|5(?:0|6[37])|6(?:[12][018]|[36-8])|7|89|9[1-9]|(?:48|57)[0137-9]","4[45][0-5]|5(?:0|6(?:3[14-7]|7))|6(?:[12][018]|[36-8])|7|89|9[1-9]|(?:48|57)[0137-9]"],"format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[3-6]","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[89]\\d{9}|[3-9]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "[5-7]"},"exampleNumber": "311234567","nationalNumberPattern": "(?:3[1-8]|4[13-8]|5[1-7]|6[12459])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "501234567","nationalNumberPattern": "(?:50|6[36-8]|7[1-3]|9[1-9])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9,10"},"exampleNumber": "800123456","nationalNumberPattern": "800[1-8]\\d{5,6}"},"premiumRate": {"possibleLengths": {"national": "9,10"},"exampleNumber": "900212345","nationalNumberPattern": "900[239]\\d{5,6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "891234567","nationalNumberPattern": "89[1-579]\\d{6}"}},{"id": "UG","countryCode": "256","internationalPrefix": "00[057]","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{4})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["202","2024"],"format": "$1 $2"},{"pattern": "(\\d{3})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[27-9]|4(?:6[45]|[7-9])","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[34]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "800\\d{6}|(?:[29]0|[347]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "[5-7]"},"exampleNumber": "312345678","nationalNumberPattern": "(?:20(?:(?:(?:[0147]\\d|5[0-4])\\d|2(?:40|[5-9]\\d)|3(?:0[67]|2[0-4])|810)\\d|6(?:00[0-2]|[15-9]\\d\\d|30[0-4]))|[34]\\d{5})\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712345678","nationalNumberPattern": "7260\\d{5}|7(?:[0157-9]\\d|20|4[0-4])\\d{6}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800[1-3]\\d{5}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "901123456","nationalNumberPattern": "90[1-3]\\d{6}"}},{"id": "US","mainCountryForCode": "true","countryCode": "1","internationalPrefix": "011","nationalPrefix": "1","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[2-9]","format": "$1-$2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{4})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[2-9]","format": "($1) $2-$3","intlFormat": "$1-$2-$3"}]},"generalDesc": {"nationalNumberPattern": "[2-9]\\d{9}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2015550123","nationalNumberPattern": "(?:2(?:0[1-35-9]|1[02-9]|2[03-589]|3[149]|4[08]|5[1-46]|6[0279]|7[0269]|8[13])|3(?:0[1-57-9]|1[02-9]|2[0135]|3[0-24679]|4[167]|5[12]|6[014]|8[056])|4(?:0[124-9]|1[02-579]|2[3-5]|3[0245]|4[0235]|58|6[39]|7[0589]|8[04])|5(?:0[1-57-9]|1[0235-8]|20|3[0149]|4[01]|5[19]|6[1-47]|7[013-5]|8[056])|6(?:0[1-35-9]|1[024-9]|2[03689]|[34][016]|5[017]|6[0-279]|78|8[0-29])|7(?:0[1-46-8]|1[2-9]|2[04-7]|3[1247]|4[037]|5[47]|6[02359]|7[02-59]|8[156])|8(?:0[1-68]|1[02-8]|2[08]|3[0-28]|4[3578]|5[046-9]|6[02-5]|7[028])|9(?:0[1346-9]|1[02-9]|2[0589]|3[0146-8]|4[0179]|5[12469]|7[0-389]|8[04-69]))[2-9]\\d{6}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2015550123","nationalNumberPattern": "(?:2(?:0[1-35-9]|1[02-9]|2[03-589]|3[149]|4[08]|5[1-46]|6[0279]|7[0269]|8[13])|3(?:0[1-57-9]|1[02-9]|2[0135]|3[0-24679]|4[167]|5[12]|6[014]|8[056])|4(?:0[124-9]|1[02-579]|2[3-5]|3[0245]|4[0235]|58|6[39]|7[0589]|8[04])|5(?:0[1-57-9]|1[0235-8]|20|3[0149]|4[01]|5[19]|6[1-47]|7[013-5]|8[056])|6(?:0[1-35-9]|1[024-9]|2[03689]|[34][016]|5[017]|6[0-279]|78|8[0-29])|7(?:0[1-46-8]|1[2-9]|2[04-7]|3[1247]|4[037]|5[47]|6[02359]|7[02-59]|8[156])|8(?:0[1-68]|1[02-8]|2[08]|3[0-28]|4[3578]|5[046-9]|6[02-5]|7[028])|9(?:0[1346-9]|1[02-9]|2[0589]|3[0146-8]|4[0179]|5[12469]|7[0-389]|8[04-69]))[2-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"},"uan": {"possibleLengths": {"national": "10"},"exampleNumber": "7102123456","nationalNumberPattern": "710[2-9]\\d{6}"}},{"id": "UY","countryCode": "598","preferredInternationalPrefix": "00","internationalPrefix": "0(?:0|1[3-9]\\d)","nationalPrefix": "0","preferredExtnPrefix": " int. ","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8|90","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "9","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{4})","leadingDigits": "[24]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:[249]\\d\\d|80)\\d{5}|9\\d{6}"},"fixedLine": {"possibleLengths": {"national": "8","localOnly": "7"},"exampleNumber": "21231234","nationalNumberPattern": "(?:2\\d|4[2-7])\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "94231234","nationalNumberPattern": "9[1-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "80[05]\\d{4}"},"premiumRate": {"possibleLengths": {"national": "7"},"exampleNumber": "9001234","nationalNumberPattern": "90[0-8]\\d{4}"}},{"id": "UZ","countryCode": "998","preferredInternationalPrefix": "8~10","internationalPrefix": "810","nationalPrefix": "8","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP $FG","leadingDigits": "[679]","format": "$1 $2 $3 $4"}},"generalDesc": {"nationalNumberPattern": "[679]\\d{8}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "669050123","nationalNumberPattern": "78(?:1(?:13|2[02]|50)|2(?:10|2[139]|98)|77[01])\\d{4}|(?:6(?:1(?:22|3[124]|4[1-4]|5[1-3578]|64)|2(?:22|3[0-57-9]|41)|5(?:22|3[3-7]|5[024-8])|6\\d\\d|7(?:[23]\\d|7[69])|9(?:22|4[1-8]|6[135]))|7(?:0(?:5[4-9]|6[0146]|7[124-6]|9[135-8])|1[12]\\d|2(?:22|3[13-57-9]|4[1-3579]|5[14])|3(?:2\\d|3[1578]|4[1-35-7]|5[1-57]|61)|4(?:2\\d|3[1-579]|7[1-79])|5(?:22|5[1-9]|6[1457])|6(?:22|3[12457]|4[13-8])|9(?:22|5[1-9])))\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "(?:6(?:1(?:2(?:2[01]|98)|35[0-4]|50\\d|61[23]|7(?:[01][017]|4\\d|55|9[5-9]))|2(?:(?:11|7\\d)\\d|2(?:[12]1|9[01379])|5(?:[126]\\d|3[0-4]))|5(?:19[01]|2(?:27|9[26])|(?:30|59|7\\d)\\d)|6(?:2(?:1[5-9]|2[0367]|38|41|52|60)|(?:3[79]|9[0-3])\\d|4(?:56|83)|7(?:[07]\\d|1[017]|3[07]|4[047]|5[057]|67|8[0178]|9[79]))|7(?:2(?:24|3[237]|4[5-9]|7[15-8])|5(?:7[12]|8[0589])|7(?:0\\d|[39][07])|9(?:0\\d|7[079]))|9(?:2(?:1[1267]|3[01]|5\\d|7[0-4])|(?:5[67]|7\\d)\\d|6(?:2[0-26]|8\\d)))|7(?:0\\d{3}|1(?:13[01]|6(?:0[47]|1[67]|66)|71[3-69]|98\\d)|2(?:2(?:2[79]|95)|3(?:2[5-9]|6[0-6])|57\\d|7(?:0\\d|1[17]|2[27]|3[37]|44|5[057]|66|88))|3(?:2(?:1[0-6]|21|3[469]|7[159])|(?:33|9[4-6])\\d|5(?:0[0-4]|5[579]|9\\d)|7(?:[0-3579]\\d|4[0467]|6[67]|8[078]))|4(?:2(?:29|5[0257]|6[0-7]|7[1-57])|5(?:1[0-4]|8\\d|9[5-9])|7(?:0\\d|1[024589]|2[0-27]|3[0137]|[46][07]|5[01]|7[5-9]|9[079])|9(?:7[015-9]|[89]\\d))|5(?:112|2(?:0\\d|2[29]|[49]4)|3[1568]\\d|52[6-9]|7(?:0[01578]|1[017]|[23]7|4[047]|[5-7]\\d|8[78]|9[079]))|6(?:2(?:2[1245]|4[2-4])|39\\d|41[179]|5(?:[349]\\d|5[0-2])|7(?:0[017]|[13]\\d|22|44|55|67|88))|9(?:22[128]|3(?:2[0-4]|7\\d)|57[02569]|7(?:2[05-9]|3[37]|4\\d|60|7[2579]|87|9[07])))|9[0-57-9]\\d{3})\\d{4}"}},{"id": "VA","countryCode": "39","leadingDigits": "06698","internationalPrefix": "00","mobileNumberPortableRegion": "true","generalDesc": {"nationalNumberPattern": "0\\d{5,10}|3[0-8]\\d{7,10}|55\\d{8}|8\\d{5}(?:\\d{2,4})?|(?:1\\d|39)\\d{7,8}"},"fixedLine": {"possibleLengths": {"national": "[6-11]"},"exampleNumber": "0669812345","nationalNumberPattern": "06698\\d{1,6}"},"mobile": {"possibleLengths": {"national": "9,10"},"exampleNumber": "3123456789","nationalNumberPattern": "3[1-9]\\d{8}|3[2-9]\\d{7}"},"tollFree": {"possibleLengths": {"national": "6,9"},"exampleNumber": "800123456","nationalNumberPattern": "80(?:0\\d{3}|3)\\d{3}"},"premiumRate": {"possibleLengths": {"national": "6,[8-10]"},"exampleNumber": "899123456","nationalNumberPattern": "(?:0878\\d\\d|89(?:2|4[5-9]\\d))\\d{3}|89[45][0-4]\\d\\d|(?:1(?:44|6[346])|89(?:5[5-9]|9))\\d{6}"},"sharedCost": {"possibleLengths": {"national": "6,9"},"exampleNumber": "848123456","nationalNumberPattern": "84(?:[08]\\d{3}|[17])\\d{3}"},"personalNumber": {"possibleLengths": {"national": "9,10"},"exampleNumber": "1781234567","nationalNumberPattern": "1(?:78\\d|99)\\d{6}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "5512345678","nationalNumberPattern": "55\\d{8}"},"voicemail": {"possibleLengths": {"national": "11,12"},"exampleNumber": "33101234501","nationalNumberPattern": "3[2-8]\\d{9,10}"}},{"id": "VC","countryCode": "1","leadingDigits": "784","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-7]\\d{6})$","nationalPrefixTransformRule": "784$1","generalDesc": {"nationalNumberPattern": "(?:[58]\\d\\d|784|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7842661234","nationalNumberPattern": "784(?:266|3(?:6[6-9]|7\\d|8[0-24-6])|4(?:38|5[0-36-8]|8[0-8])|5(?:55|7[0-2]|93)|638|784)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "7844301234","nationalNumberPattern": "784(?:4(?:3[0-5]|5[45]|89|9[0-8])|5(?:2[6-9]|3[0-4]))\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "VE","countryCode": "58","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","carrierCodeFormattingRule": "$CC $FG","leadingDigits": "[24589]","format": "$1-$2"}},"generalDesc": {"nationalNumberPattern": "[89]00\\d{7}|(?:[24]\\d|50)\\d{8}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2121234567","nationalNumberPattern": "(?:2(?:12|3[457-9]|[467]\\d|[58][1-9]|9[1-6])|50[01])\\d{7}"},"mobile": {"possibleLengths": {"national": "10"},"exampleNumber": "4121234567","nationalNumberPattern": "4(?:1[24-8]|2[46])\\d{7}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8001234567","nationalNumberPattern": "800\\d{7}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9001234567","nationalNumberPattern": "900\\d{7}"}},{"id": "VG","countryCode": "1","leadingDigits": "284","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-578]\\d{6})$","nationalPrefixTransformRule": "284$1","generalDesc": {"nationalNumberPattern": "(?:284|[58]\\d\\d|900)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2842291234","nationalNumberPattern": "284496[0-5]\\d{3}|284(?:229|4(?:22|9[45])|774|8(?:52|6[459]))\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "2843001234","nationalNumberPattern": "284496[6-9]\\d{3}|284(?:3(?:0[0-3]|4[0-7]|68|9[34])|4(?:4[0-6]|68|99)|54[0-57])\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "VI","countryCode": "1","leadingDigits": "340","internationalPrefix": "011","nationalPrefix": "1","nationalPrefixForParsing": "1|([2-9]\\d{6})$","nationalPrefixTransformRule": "340$1","generalDesc": {"nationalNumberPattern": "[58]\\d{9}|(?:34|90)0\\d{7}"},"fixedLine": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "3406421234","nationalNumberPattern": "340(?:2(?:0[12]|2[06-8]|4[49]|77)|3(?:32|44)|4(?:22|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|27|7\\d)|884|998)\\d{4}"},"mobile": {"possibleLengths": {"national": "10","localOnly": "7"},"exampleNumber": "3406421234","nationalNumberPattern": "340(?:2(?:0[12]|2[06-8]|4[49]|77)|3(?:32|44)|4(?:22|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|27|7\\d)|884|998)\\d{4}"},"tollFree": {"possibleLengths": {"national": "10"},"exampleNumber": "8002345678","nationalNumberPattern": "8(?:00|33|44|55|66|77|88)[2-9]\\d{6}"},"premiumRate": {"possibleLengths": {"national": "10"},"exampleNumber": "9002345678","nationalNumberPattern": "900[2-9]\\d{6}"},"personalNumber": {"possibleLengths": {"national": "10"},"exampleNumber": "5002345678","nationalNumberPattern": "5(?:00|2[12]|33|44|66|77|88)[2-9]\\d{6}"}},{"id": "VN","countryCode": "84","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[17]99","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{2})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "80","format": "$1 $2"},{"pattern": "(\\d{3})(\\d{4,5})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "69","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{4})(\\d{4,6})","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "1","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{2})(\\d{2})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[69]","format": "$1 $2 $3 $4"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "[3578]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "2[48]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","nationalPrefixOptionalWhenFormatting": "true","leadingDigits": "2","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[12]\\d{9}|[135-9]\\d{8}|[16]\\d{7}|[16-8]\\d{6}"},"noInternationalDialling": {"possibleLengths": {"national": "7,8"},"nationalNumberPattern": "[17]99\\d{4}|69\\d{5,6}"},"fixedLine": {"possibleLengths": {"national": "10"},"exampleNumber": "2101234567","nationalNumberPattern": "2(?:0[3-9]|1[0-689]|2[0-25-9]|3[2-9]|4[2-8]|5[124-9]|6[0-39]|7[0-7]|8[2-79]|9[0-4679])\\d{7}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "912345678","nationalNumberPattern": "(?:52[238]|8(?:79|9[689])|99[013-9])\\d{6}|(?:3\\d|5[689]|7[06-9]|8[1-68]|9[0-8])\\d{7}"},"tollFree": {"possibleLengths": {"national": "[8-10]"},"exampleNumber": "1800123456","nationalNumberPattern": "1800\\d{4,6}|12(?:03|28)\\d{4}"},"premiumRate": {"possibleLengths": {"national": "[8-10]"},"exampleNumber": "1900123456","nationalNumberPattern": "1900\\d{4,6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "672012345","nationalNumberPattern": "672\\d{6}"},"uan": {"possibleLengths": {"national": "7,8"},"exampleNumber": "1992000","nationalNumberPattern": "(?:[17]99|80\\d)\\d{4}|69\\d{5,6}"}},{"id": "VU","countryCode": "678","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{4})","leadingDigits": "[579]","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "(?:[23]\\d|[48]8)\\d{3}|(?:[57]\\d|90)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "22123","nationalNumberPattern": "(?:38[0-8]|48[4-9])\\d\\d|(?:2[02-9]|3[4-7]|88)\\d{3}"},"mobile": {"possibleLengths": {"national": "7"},"exampleNumber": "5912345","nationalNumberPattern": "57[2-5]\\d{4}|(?:5[0-689]|7[013-7])\\d{5}"},"voip": {"possibleLengths": {"national": "7"},"exampleNumber": "9010123","nationalNumberPattern": "90[1-9]\\d{4}"},"uan": {"possibleLengths": {"national": "5,7"},"exampleNumber": "30123","nationalNumberPattern": "(?:3[03]|900\\d)\\d{3}"}},{"id": "WF","countryCode": "681","internationalPrefix": "00","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{2})(\\d{2})","leadingDigits": "[4-8]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "(?:[45]0|68|72|8\\d)\\d{4}"},"fixedLine": {"possibleLengths": {"national": "6"},"exampleNumber": "501234","nationalNumberPattern": "(?:50|68|72)\\d{4}"},"mobile": {"possibleLengths": {"national": "6"},"exampleNumber": "501234","nationalNumberPattern": "(?:50|68|72|8[23])\\d{4}"},"voicemail": {"possibleLengths": {"national": "6"},"exampleNumber": "401234","nationalNumberPattern": "[48]0\\d{4}"}},{"id": "WS","countryCode": "685","internationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{5})","leadingDigits": "[2-6]","format": "$1"},{"pattern": "(\\d{3})(\\d{3,7})","leadingDigits": "8","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{5})","leadingDigits": "7","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "[2-6]\\d{4}|8\\d{5}(?:\\d{4})?|[78]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "5"},"exampleNumber": "22123","nationalNumberPattern": "(?:[2-5]\\d|6[1-9])\\d{3}"},"mobile": {"possibleLengths": {"national": "7,10"},"exampleNumber": "7212345","nationalNumberPattern": "(?:7[25-7]|8(?:[3-7]|9\\d{3}))\\d{5}"},"tollFree": {"possibleLengths": {"national": "6"},"exampleNumber": "800123","nationalNumberPattern": "800\\d{3}"}},{"id": "XK","countryCode": "383","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[89]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[2-4]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[23]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[23]\\d{7,8}|(?:4\\d\\d|[89]00)\\d{5}"},"fixedLine": {"possibleLengths": {"national": "8,9"},"exampleNumber": "28012345","nationalNumberPattern": "(?:2[89]|39)0\\d{6}|[23][89]\\d{6}"},"mobile": {"possibleLengths": {"national": "8"},"exampleNumber": "43201234","nationalNumberPattern": "4[3-9]\\d{6}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "80001234","nationalNumberPattern": "800\\d{5}"},"premiumRate": {"possibleLengths": {"national": "8"},"exampleNumber": "90001234","nationalNumberPattern": "900\\d{5}"}},{"id": "YE","countryCode": "967","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d)(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-6]|7[24-68]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "(?:1|7\\d)\\d{7}|[1-7]\\d{6}"},"fixedLine": {"possibleLengths": {"national": "7,8","localOnly": "6"},"exampleNumber": "1234567","nationalNumberPattern": "17\\d{6}|(?:[12][2-68]|3[2358]|4[2-58]|5[2-6]|6[3-58]|7[24-68])\\d{5}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712345678","nationalNumberPattern": "7[0137]\\d{7}"}},{"id": "YT","countryCode": "262","leadingDigits": "269|63","internationalPrefix": "00","nationalPrefix": "0","generalDesc": {"nationalNumberPattern": "80\\d{7}|(?:26|63)9\\d{6}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "269601234","nationalNumberPattern": "269(?:0[67]|5[0-2]|6\\d|[78]0)\\d{4}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "639012345","nationalNumberPattern": "639(?:0[0-79]|1[019]|[267]\\d|3[09]|[45]0|9[04-79])\\d{4}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80\\d{7}"}},{"id": "ZA","countryCode": "27","internationalPrefix": "00","nationalPrefix": "0","mobileNumberPortableRegion": "true","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8[1-4]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{2,3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8[1-4]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "860","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[1-9]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "[1-9]\\d{8}|8\\d{4,7}"},"fixedLine": {"possibleLengths": {"national": "9"},"exampleNumber": "101234567","nationalNumberPattern": "(?:1[0-8]|2[1-378]|3[1-69]|4\\d|5[1346-8])\\d{7}"},"mobile": {"possibleLengths": {"national": "[5-9]"},"exampleNumber": "711234567","nationalNumberPattern": "(?:1(?:3492[0-25]|4495[0235]|549(?:20|5[01]))|4[34]492[01])\\d{3}|8[1-4]\\d{3,7}|(?:2[27]|47|54)4950\\d{3}|(?:1(?:049[2-4]|9[12]\\d\\d)|(?:6\\d|7[0-46-9])\\d{3}|8(?:5\\d{3}|7(?:08[67]|158|28[5-9]|310)))\\d{4}|(?:1[6-8]|28|3[2-69]|4[025689]|5[36-8])4920\\d{3}|(?:12|[2-5]1)492\\d{4}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "801234567","nationalNumberPattern": "80\\d{7}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "862345678","nationalNumberPattern": "(?:86[2-9]|9[0-2]\\d)\\d{6}"},"sharedCost": {"possibleLengths": {"national": "9"},"exampleNumber": "860123456","nationalNumberPattern": "860\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "871234567","nationalNumberPattern": "87(?:08[0-589]|15[0-79]|28[0-4]|31[1-9])\\d{4}|87(?:[02][0-79]|1[0-46-9]|3[02-9]|[4-9]\\d)\\d{5}"},"uan": {"possibleLengths": {"national": "9"},"exampleNumber": "861123456","nationalNumberPattern": "861\\d{6}"}},{"id": "ZM","countryCode": "260","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})","leadingDigits": "[1-9]","format": "$1 $2","intlFormat": "NA"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[28]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[79]","format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "(?:63|80)0\\d{6}|(?:21|[79]\\d)\\d{7}"},"fixedLine": {"possibleLengths": {"national": "9","localOnly": "6"},"exampleNumber": "211234567","nationalNumberPattern": "21[1-8]\\d{6}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "955123456","nationalNumberPattern": "(?:7[67]|9[5-8])\\d{7}"},"tollFree": {"possibleLengths": {"national": "9"},"exampleNumber": "800123456","nationalNumberPattern": "800\\d{6}"},"voip": {"possibleLengths": {"national": "9"},"exampleNumber": "630012345","nationalNumberPattern": "630\\d{6}"}},{"id": "ZW","countryCode": "263","internationalPrefix": "00","nationalPrefix": "0","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "2(?:0[45]|2[278]|[49]8)|3(?:[09]8|17)|6(?:[29]8|37|75)|[23][78]|(?:33|5[15]|6[68])[78]","format": "$1 $2"},{"pattern": "(\\d)(\\d{3})(\\d{2,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "[49]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "80","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{7})","nationalPrefixFormattingRule": "($NP$FG)","leadingDigits": ["24|8[13-59]|(?:2[05-79]|39|5[45]|6[15-8])2","2(?:02[014]|4|[56]20|[79]2)|392|5(?:42|525)|6(?:[16-8]21|52[013])|8[13-59]"],"format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "7","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["2(?:1[39]|2[0157]|[378]|[56][14])|3(?:12|29)","2(?:1[39]|2[0157]|[378]|[56][14])|3(?:123|29)"],"format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{6})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "8","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "1|2(?:0[0-36-9]|12|29|[56])|3(?:1[0-689]|[24-6])|5(?:[0236-9]|1[2-4])|6(?:[013-59]|7[0-46-9])|(?:33|55|6[68])[0-69]|(?:29|3[09]|62)[0-79]","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{3})(\\d{3,4})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": "29[013-9]|39|54","format": "$1 $2 $3"},{"pattern": "(\\d{4})(\\d{3,5})","nationalPrefixFormattingRule": "$NP$FG","leadingDigits": ["(?:25|54)8","258|5483"],"format": "$1 $2"}]},"generalDesc": {"nationalNumberPattern": "2(?:[0-57-9]\\d{6,8}|6[0-24-9]\\d{6,7})|[38]\\d{9}|[35-8]\\d{8}|[3-6]\\d{7}|[1-689]\\d{6}|[1-3569]\\d{5}|[1356]\\d{4}"},"fixedLine": {"possibleLengths": {"national": "[5-10]","localOnly": "3,4"},"exampleNumber": "1312345","nationalNumberPattern": "(?:1(?:(?:3\\d|9)\\d|[4-8])|2(?:(?:(?:0(?:2[014]|5)|(?:2[0157]|31|84|9)\\d\\d|[56](?:[14]\\d\\d|20)|7(?:[089]|2[03]|[35]\\d\\d))\\d|4(?:2\\d\\d|8))\\d|1(?:2|[39]\\d{4}))|3(?:(?:123|(?:29\\d|92)\\d)\\d\\d|7(?:[19]|[56]\\d))|5(?:0|1[2-478]|26|[37]2|4(?:2\\d{3}|83)|5(?:25\\d\\d|[78])|[689]\\d)|6(?:(?:[16-8]21|28|52[013])\\d\\d|[39])|8(?:[1349]28|523)\\d\\d)\\d{3}|(?:4\\d\\d|9[2-9])\\d{4,5}|(?:(?:2(?:(?:(?:0|8[146])\\d|7[1-7])\\d|2(?:[278]\\d|92)|58(?:2\\d|3))|3(?:[26]|9\\d{3})|5(?:4\\d|5)\\d\\d)\\d|6(?:(?:(?:[0-246]|[78]\\d)\\d|37)\\d|5[2-8]))\\d\\d|(?:2(?:[569]\\d|8[2-57-9])|3(?:[013-59]\\d|8[37])|6[89]8)\\d{3}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "712345678","nationalNumberPattern": "7(?:[17]\\d|[38][1-9])\\d{6}"},"tollFree": {"possibleLengths": {"national": "7"},"exampleNumber": "8001234","nationalNumberPattern": "80(?:[01]\\d|20|8[0-8])\\d{3}"},"voip": {"possibleLengths": {"national": "10"},"exampleNumber": "8686123456","nationalNumberPattern": "86(?:1[12]|22|30|44|55|77|8[368])\\d{6}"}},{"id": "001","countryCode": "800","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "\\d{8}"},"tollFree": {"possibleLengths": {"national": "8"},"exampleNumber": "12345678","nationalNumberPattern": "\\d{8}"}},{"id": "001","countryCode": "808","availableFormats": {"numberFormat": {"pattern": "(\\d{4})(\\d{4})","format": "$1 $2"}},"generalDesc": {"nationalNumberPattern": "\\d{8}"},"sharedCost": {"possibleLengths": {"national": "8"},"exampleNumber": "12345678","nationalNumberPattern": "\\d{8}"}},{"id": "001","countryCode": "870","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "[35-7]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[35-7]\\d{8}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "301234567","nationalNumberPattern": "(?:[356]\\d|7[6-8])\\d{7}"}},{"id": "001","countryCode": "878","availableFormats": {"numberFormat": {"pattern": "(\\d{2})(\\d{5})(\\d{5})","leadingDigits": "1","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "10\\d{10}"},"voip": {"possibleLengths": {"national": "12"},"exampleNumber": "101234567890","nationalNumberPattern": "10\\d{10}"}},{"id": "001","countryCode": "881","availableFormats": {"numberFormat": {"pattern": "(\\d)(\\d{3})(\\d{5})","leadingDigits": "[67]","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "[67]\\d{8}"},"mobile": {"possibleLengths": {"national": "9"},"exampleNumber": "612345678","nationalNumberPattern": "[67]\\d{8}"}},{"id": "001","countryCode": "882","availableFormats": {"numberFormat": [{"pattern": "(\\d{2})(\\d{5})","leadingDigits": "16|342","format": "$1 $2"},{"pattern": "(\\d{2})(\\d{2})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{3})","leadingDigits": "3[23]","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{3,4})(\\d{4})","leadingDigits": "1","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4})(\\d{4})","leadingDigits": "34[57]","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{4})(\\d{4})","leadingDigits": "34","format": "$1 $2 $3"},{"pattern": "(\\d{2})(\\d{4,5})(\\d{5})","leadingDigits": "[13]","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "1\\d{6,11}|3\\d{6}(?:\\d{2,5})?"},"mobile": {"possibleLengths": {"national": "7,9,10,12"},"exampleNumber": "3421234","nationalNumberPattern": "3(?:37\\d\\d|42)\\d{4}|3(?:2|47|7\\d{3})\\d{7}"},"voip": {"possibleLengths": {"national": "[7-12]"},"exampleNumber": "390123456789","nationalNumberPattern": "1(?:3(?:0[0347]|[13][0139]|2[035]|4[013568]|6[0459]|7[06]|8[15-8]|9[0689])\\d{4}|6\\d{5,10})|3(?:45|9\\d{3})\\d{7}"},"voicemail": {"possibleLengths": {"national": "11"},"exampleNumber": "34851234567","nationalNumberPattern": "348[57]\\d{7}"}},{"id": "001","countryCode": "883","availableFormats": {"numberFormat": [{"pattern": "(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "510","format": "$1 $2 $3"},{"pattern": "(\\d{3})(\\d{3})(\\d{3})(\\d{3})","leadingDigits": "510","format": "$1 $2 $3 $4"},{"pattern": "(\\d{4})(\\d{4})(\\d{4})","leadingDigits": "5","format": "$1 $2 $3"}]},"generalDesc": {"nationalNumberPattern": "51\\d{7}(?:\\d{3})?"},"voip": {"possibleLengths": {"national": "9,12"},"exampleNumber": "510012345","nationalNumberPattern": "51[013]0\\d{8}|5100\\d{5}"}},{"id": "001","countryCode": "888","availableFormats": {"numberFormat": {"pattern": "(\\d{3})(\\d{3})(\\d{5})","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "\\d{11}"},"uan": {"possibleLengths": {"national": "11"},"exampleNumber": "12345678901","nationalNumberPattern": "\\d{11}"}},{"id": "001","countryCode": "979","availableFormats": {"numberFormat": {"pattern": "(\\d)(\\d{4})(\\d{4})","format": "$1 $2 $3"}},"generalDesc": {"nationalNumberPattern": "\\d{9}"},"premiumRate": {"possibleLengths": {"national": "9"},"exampleNumber": "123456789","nationalNumberPattern": "\\d{9}"}}]} }} \ No newline at end of file diff --git a/CatchUp-SwiftUI/Supporting Views/ContactPicker.swift b/CatchUp-SwiftUI/Services/ContactPickerDelegate.swift similarity index 83% rename from CatchUp-SwiftUI/Supporting Views/ContactPicker.swift rename to CatchUp-SwiftUI/Services/ContactPickerDelegate.swift index fca5c5c..4c84832 100644 --- a/CatchUp-SwiftUI/Supporting Views/ContactPicker.swift +++ b/CatchUp-SwiftUI/Services/ContactPickerDelegate.swift @@ -1,5 +1,5 @@ // -// ContactPicker.swift +// ContactPickerDelegate.swift // CatchUp-SwiftUI // // Created by Ryan Token on 3/10/24. @@ -10,7 +10,7 @@ import ContactsUI import SwiftUI @Observable -class ContactPicker: NSObject, CNContactPickerDelegate { +class ContactPickerDelegate: NSObject, CNContactPickerDelegate { var chosenContacts = [CNContact]() func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) { diff --git a/CatchUp-SwiftUI/AboutScreen.swift b/CatchUp-SwiftUI/Views/AboutScreen.swift similarity index 100% rename from CatchUp-SwiftUI/AboutScreen.swift rename to CatchUp-SwiftUI/Views/AboutScreen.swift diff --git a/CatchUp-SwiftUI/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift similarity index 100% rename from CatchUp-SwiftUI/DetailScreen.swift rename to CatchUp-SwiftUI/Views/DetailScreen.swift diff --git a/CatchUp-SwiftUI/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift similarity index 98% rename from CatchUp-SwiftUI/HomeScreen.swift rename to CatchUp-SwiftUI/Views/HomeScreen.swift index ce783ac..3bd05d3 100644 --- a/CatchUp-SwiftUI/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -20,7 +20,7 @@ struct HomeScreen : View { @State private var isColdLaunch = true @State private var isShowingUpdatesSheet = false @State private var isShowingAboutSheet = false - @State private var contactPicker = ContactPicker() + @State private var contactPicker = ContactPickerDelegate() init() { //Use this if NavigationBarTitle is with Large Font diff --git a/CatchUp-SwiftUI/PreferenceScreen.swift b/CatchUp-SwiftUI/Views/PreferenceScreen.swift similarity index 100% rename from CatchUp-SwiftUI/PreferenceScreen.swift rename to CatchUp-SwiftUI/Views/PreferenceScreen.swift diff --git a/CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift b/CatchUp-SwiftUI/Views/Supporting Views/ContactPhoto.swift similarity index 100% rename from CatchUp-SwiftUI/Supporting Views/ContactPhoto.swift rename to CatchUp-SwiftUI/Views/Supporting Views/ContactPhoto.swift diff --git a/CatchUp-SwiftUI/Supporting Views/GradientView.swift b/CatchUp-SwiftUI/Views/Supporting Views/GradientView.swift similarity index 100% rename from CatchUp-SwiftUI/Supporting Views/GradientView.swift rename to CatchUp-SwiftUI/Views/Supporting Views/GradientView.swift diff --git a/CatchUp-SwiftUI/UpdatesScreen.swift b/CatchUp-SwiftUI/Views/UpdatesScreen.swift similarity index 100% rename from CatchUp-SwiftUI/UpdatesScreen.swift rename to CatchUp-SwiftUI/Views/UpdatesScreen.swift From dd0873203c754c33970c922f98e01cf0902c0fc4 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 10 Mar 2024 20:11:26 -0600 Subject: [PATCH 13/46] Pull-to-refresh contacts! And HomeScreen cleanup --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 8 ++ CatchUp-SwiftUI/Data/SelectedContact.swift | 23 ++++++ CatchUp-SwiftUI/Utilities/ContactHelper.swift | 75 +++++++++++++++++++ CatchUp-SwiftUI/Views/HomeScreen.swift | 61 ++++++--------- .../Supporting Views/ContactRowView.swift | 35 +++++++++ .../OpenContactPickerButtonView.swift | 27 +++++++ 6 files changed, 192 insertions(+), 37 deletions(-) create mode 100644 CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift create mode 100644 CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 6c071fa..8e1f704 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */; }; F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */; }; F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */; }; + F4118B492B9E8FC7001BC8C7 /* ContactRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */; }; + F4118B4B2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -34,6 +36,8 @@ 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesScreen.swift; sourceTree = ""; }; F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+localizedPrice.swift"; sourceTree = ""; }; F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerDelegate.swift; sourceTree = ""; }; + F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRowView.swift; sourceTree = ""; }; + F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenContactPickerButtonView.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -145,6 +149,8 @@ children = ( F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, F4A9B8562442A1F7001D8C55 /* GradientView.swift */, + F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */, + F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */, ); path = "Supporting Views"; sourceTree = ""; @@ -244,6 +250,7 @@ buildActionMask = 2147483647; files = ( F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */, + F4118B4B2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift in Sources */, F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */, F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, 14BE3AD324593556004F72DE /* Utils.swift in Sources */, @@ -259,6 +266,7 @@ F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, + F4118B492B9E8FC7001BC8C7 /* ContactRowView.swift in Sources */, F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift index 9cfb5d9..7630310 100644 --- a/CatchUp-SwiftUI/Data/SelectedContact.swift +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -80,4 +80,27 @@ class SelectedContact { self.secondary_phone = secondary_phone } + static let sampleData = SelectedContact( + address: "2190 E 11th Ave", + anniversary: "06/20/2020", + anniversary_notification_id: UUID(), + birthday: "05/16/1994", + birthday_notification_id: UUID(), + email: "ryantoken13@gmail.com", + id: UUID(), + name: "Ryan Token", + notification_identifier: UUID(), + notification_preference: 0, + notification_preference_custom_day: 3, + notification_preference_custom_month: 0, + notification_preference_custom_year: 0, + notification_preference_hour: 12, + notification_preference_minute: 0, + notification_preference_weekday: 3, + phone: "6363687771", + picture: "photo-as-data-string", + secondary_address: "", + secondary_email: "", + secondary_phone: "" + ) } diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 21d8b8e..442c65f 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -279,4 +279,79 @@ struct ContactHelper { return selectedContact } + + static func updateSelectedContacts(_ selectedContacts: [SelectedContact]) { + for contact in selectedContacts { + updateSelectedContact(contact) + } + } + + static func updateSelectedContact(_ selectedContact: SelectedContact?) { + guard let selectedContact else { return } + + getCNContactByName(selectedContact.name) { contact in + if let contact { + selectedContact.name = getContactName(for: contact) + selectedContact.phone = getContactPrimaryPhone(for: contact) + selectedContact.secondary_phone = getContactSecondaryPhone(for: contact) + selectedContact.email = getContactPrimaryEmail(for: contact) + selectedContact.secondary_email = getContactSecondaryEmail(for: contact) + selectedContact.address = getContactPrimaryAddress(for: contact) + selectedContact.secondary_address = getContactSecondaryAddress(for: contact) + selectedContact.picture = encodeContactPicture(for: contact) + selectedContact.birthday = getContactBirthday(for: contact) + selectedContact.anniversary = getContactAnniversary(for: contact) + } else { + print("No contact with name \(selectedContact.name) found") + } + } + } + + static func getCNContactByName(_ name: String, completion: @escaping (CNContact?) -> Void) { + print("searching contact book for \(name)") + + let contactStore = CNContactStore() + let keysToFetch: [CNKeyDescriptor] = [ + CNContactFormatter.descriptorForRequiredKeys(for: .fullName), + CNContactGivenNameKey as CNKeyDescriptor, + CNContactFamilyNameKey as CNKeyDescriptor, + CNContactPhoneNumbersKey as CNKeyDescriptor, + CNContactEmailAddressesKey as CNKeyDescriptor, + CNContactPostalAddressesKey as CNKeyDescriptor, + CNContactImageDataAvailableKey as CNKeyDescriptor, + CNContactImageDataKey as CNKeyDescriptor, + CNContactThumbnailImageDataKey as CNKeyDescriptor, + CNContactBirthdayKey as CNKeyDescriptor, + CNContactDatesKey as CNKeyDescriptor + ] + + DispatchQueue.global().async { + var allContacts: [CNContact] = [] + + do { + try contactStore.enumerateContacts(with: CNContactFetchRequest(keysToFetch: keysToFetch)) { (contact, stop) in + allContacts.append(contact) + } + } catch { + print("Unable to fetch contacts") + DispatchQueue.main.async { + completion(nil) + } + return + } + + let nameFormatter = CNContactFormatter() + nameFormatter.style = .fullName + + let filteredContacts = allContacts.filter { contact in + return nameFormatter.string(from: contact) == name + } + + print("Found matching contacts: \(filteredContacts)") + + DispatchQueue.main.async { + completion(filteredContacts.first) + } + } + } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 3bd05d3..c6d828c 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -33,25 +33,15 @@ struct HomeScreen : View { List { ForEach(selectedContacts) { contact in NavigationLink(destination: DetailScreen(contact: contact)) { - HStack { - Converter.getContactPicture(from: contact.picture) - .renderingMode(.original) - .resizable() - .frame(width: 45, height: 45, alignment: .leading) - .clipShape(Circle()) - - VStack(alignment: .leading, spacing: 2) { - Text(contact.name) - .font(.headline) - Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) - .font(.caption) - .foregroundColor(.gray) - } - } + ContactRowView(contact: contact) } } .onDelete(perform: removePendingNotificationsAndDeleteContact) } + .refreshable { + ContactHelper.updateSelectedContacts(selectedContacts) + } + .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in if !contacts.isEmpty { saveSelectedContact(for: contacts) @@ -62,31 +52,28 @@ struct HomeScreen : View { Button { openContactPicker() } label: { - HStack(alignment: .center, spacing: 6) { - Image(systemName: "person.crop.circle.fill.badge.plus") - - Text("Add Contacts") - } - .font(.headline) - .foregroundColor(.blue) - .padding(.top) - .padding(.bottom) + OpenContactPickerButtonView() } - .navigationBarTitle(Text("CatchUp")) - - .navigationBarItems(trailing: - Button { - isShowingAboutSheet = true - } label: { - Image(systemName: "ellipsis.circle") - .font(.title2) - .foregroundColor(.blue) - } - .sheet(isPresented: $isShowingAboutSheet) { - AboutScreen() + .navigationBarTitle("CatchUp") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + .foregroundStyle(.blue) } - ) + + ToolbarItem(placement: .topBarTrailing) { + Button { + isShowingAboutSheet = true + } label: { + Image(systemName: "person.crop.square") + .foregroundColor(.blue) + } + .sheet(isPresented: $isShowingAboutSheet) { + AboutScreen() + } + } + } } } .accentColor(.orange) diff --git a/CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift b/CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift new file mode 100644 index 0000000..cffd1a6 --- /dev/null +++ b/CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift @@ -0,0 +1,35 @@ +// +// ContactRowView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct ContactRowView: View { + let contact: SelectedContact + + var body: some View { + HStack { + Converter.getContactPicture(from: contact.picture) + .renderingMode(.original) + .resizable() + .frame(width: 45, height: 45, alignment: .leading) + .clipShape(Circle()) + + VStack(alignment: .leading, spacing: 2) { + Text(contact.name) + .font(.headline) + Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) + .font(.caption) + .foregroundColor(.gray) + } + } + } +} + +#Preview { + ContactRowView(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift b/CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift new file mode 100644 index 0000000..2c73a60 --- /dev/null +++ b/CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift @@ -0,0 +1,27 @@ +// +// OpenContactPickerButtonView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct OpenContactPickerButtonView: View { + var body: some View { + HStack(alignment: .center, spacing: 6) { + Image(systemName: "person.crop.circle.fill.badge.plus") + + Text("Add Contacts") + } + .font(.headline) + .foregroundColor(.blue) + .padding(.top) + .padding(.bottom) + } +} + +#Preview { + OpenContactPickerButtonView() +} From 87233afc93d4d3453946a092a4a66750a84f330e Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 10 Mar 2024 20:27:45 -0600 Subject: [PATCH 14/46] DetailScreen cleanup and more file reorganization --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 40 ++++-- .../Utilities/NotificationHelper.swift | 4 +- .../ContactInfoListView.swift | 120 ++++++++++++++++ .../ContactPhoto.swift | 0 .../GradientView.swift | 0 .../NamePreferenceChangeStack.swift | 52 +++++++ CatchUp-SwiftUI/Views/DetailScreen.swift | 131 +----------------- .../ContactRowView.swift | 0 .../OpenContactPickerButtonView.swift | 0 9 files changed, 206 insertions(+), 141 deletions(-) create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift rename CatchUp-SwiftUI/Views/{Supporting Views => DetailScreen Subviews}/ContactPhoto.swift (100%) rename CatchUp-SwiftUI/Views/{Supporting Views => DetailScreen Subviews}/GradientView.swift (100%) create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift rename CatchUp-SwiftUI/Views/{Supporting Views => HomeScreen Subviews}/ContactRowView.swift (100%) rename CatchUp-SwiftUI/Views/{Supporting Views => HomeScreen Subviews}/OpenContactPickerButtonView.swift (100%) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 8e1f704..8c8888a 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */; }; F4118B492B9E8FC7001BC8C7 /* ContactRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */; }; F4118B4B2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */; }; + F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */; }; + F4118B4F2B9EA168001BC8C7 /* ContactInfoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -38,6 +40,8 @@ F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerDelegate.swift; sourceTree = ""; }; F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRowView.swift; sourceTree = ""; }; F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenContactPickerButtonView.swift; sourceTree = ""; }; + F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamePreferenceChangeStack.swift; sourceTree = ""; }; + F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInfoListView.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -74,15 +78,36 @@ isa = PBXGroup; children = ( F48E37F822C455C3008B0B8B /* HomeScreen.swift */, + F4118B502B9EA2E3001BC8C7 /* HomeScreen Subviews */, F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */, + F4118B512B9EA2F7001BC8C7 /* DetailScreen Subviews */, F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */, F4AD59AF244CA12C00296568 /* AboutScreen.swift */, 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */, - F4A9B8532442A162001D8C55 /* Supporting Views */, ); path = Views; sourceTree = ""; }; + F4118B502B9EA2E3001BC8C7 /* HomeScreen Subviews */ = { + isa = PBXGroup; + children = ( + F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */, + F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */, + ); + path = "HomeScreen Subviews"; + sourceTree = ""; + }; + F4118B512B9EA2F7001BC8C7 /* DetailScreen Subviews */ = { + isa = PBXGroup; + children = ( + F4A9B8562442A1F7001D8C55 /* GradientView.swift */, + F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, + F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */, + F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */, + ); + path = "DetailScreen Subviews"; + sourceTree = ""; + }; F4871DA2244FC44800925392 /* Utilities */ = { isa = PBXGroup; children = ( @@ -144,17 +169,6 @@ path = Services; sourceTree = ""; }; - F4A9B8532442A162001D8C55 /* Supporting Views */ = { - isa = PBXGroup; - children = ( - F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, - F4A9B8562442A1F7001D8C55 /* GradientView.swift */, - F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */, - F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */, - ); - path = "Supporting Views"; - sourceTree = ""; - }; F4AD59B1244CC9C100296568 /* Extensions */ = { isa = PBXGroup; children = ( @@ -255,7 +269,9 @@ F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, 14BE3AD324593556004F72DE /* Utils.swift in Sources */, F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, + F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */, F494F95824424A03003CE7B5 /* ContactHelper.swift in Sources */, + F4118B4F2B9EA168001BC8C7 /* ContactInfoListView.swift in Sources */, F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */, F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */, 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */, diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 8155b83..dbdf5c1 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -313,7 +313,9 @@ class NotificationHelper { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() for contact in selectedContacts { - NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + if contact.notification_preference != 0 { + NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + } } } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift new file mode 100644 index 0000000..e1a09de --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift @@ -0,0 +1,120 @@ +// +// ContactInfoListView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct ContactInfoListView: View { + let contact: SelectedContact + + var formattedPrimaryPhoneNumber: String { + Converter.getFormattedPhoneNumber(from: contact.phone) + } + + var formattedSecondaryPhoneNumber: String { + Converter.getFormattedPhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryPhoneNumber: URL { + Converter.getTappablePhoneNumber(from: contact.phone) + } + + var tappableSecondaryPhoneNumber: URL { + Converter.getTappablePhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryEmail: URL { + Converter.getTappablePhoneNumber(from: contact.email) + } + + var tappableSecondaryEmail: URL { + Converter.getTappablePhoneNumber(from: contact.secondary_email) + } + + var body: some View { + List { + Section(header: Text("Contact Information")) { + if ContactHelper.contactHasPhone(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Phone") + .font(.caption) + + Button(formattedPrimaryPhoneNumber) { + UIApplication.shared.open(tappablePrimaryPhoneNumber) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasSecondaryPhone(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Phone") + .font(.caption) + + Button(formattedSecondaryPhoneNumber) { + UIApplication.shared.open(tappableSecondaryPhoneNumber) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasEmail(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Email") + .font(.caption) + + Button(contact.email) { + UIApplication.shared.open(tappablePrimaryEmail) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasSecondaryEmail(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Email") + .font(.caption) + + Button(contact.secondary_email) { + UIApplication.shared.open(tappableSecondaryEmail) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasAddress(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Address") + .font(.caption) + Text(contact.address) + } + } + if ContactHelper.contactHasSecondaryAddress(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Address") + .font(.caption) + Text(contact.secondary_address) + } + } + if NotificationHelper.contactHasBirthday(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Birthday") + .font(.caption) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) + } + } + if NotificationHelper.contactHasAnniversary(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Anniversary") + .font(.caption) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) + } + } + } + } + } +} + +#Preview { + ContactInfoListView(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/Supporting Views/ContactPhoto.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactPhoto.swift similarity index 100% rename from CatchUp-SwiftUI/Views/Supporting Views/ContactPhoto.swift rename to CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactPhoto.swift diff --git a/CatchUp-SwiftUI/Views/Supporting Views/GradientView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/GradientView.swift similarity index 100% rename from CatchUp-SwiftUI/Views/Supporting Views/GradientView.swift rename to CatchUp-SwiftUI/Views/DetailScreen Subviews/GradientView.swift diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift new file mode 100644 index 0000000..2aea904 --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift @@ -0,0 +1,52 @@ +// +// NamePreferenceChangeStack.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NamePreferenceChangeStack: View { + @Environment(\.modelContext) var modelContext + + let contact: SelectedContact + @Binding var isShowingPreferenceScreen: Bool + + var body: some View { + VStack(alignment: .center, spacing: 10) { + Text(contact.name) + .font(.largeTitle) + .bold() + + HStack(spacing: 0) { + Text("Preference: ") + .foregroundColor(.gray) + Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) + .foregroundColor(.gray) + } + + Button { + isShowingPreferenceScreen = true + } label: { + Text("Change Notification Preference") + .font(.headline) + .foregroundColor(.orange) + } + .sheet( + isPresented: $isShowingPreferenceScreen, + onDismiss: { + NotificationHelper.removeExistingNotifications(for: contact) + NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + } + ) { + PreferenceScreen(contact: contact) + } + } + } +} + +#Preview { + NamePreferenceChangeStack(contact: SelectedContact.sampleData, isShowingPreferenceScreen: .constant(false)) +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index ccd77c0..975cb8f 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -10,34 +10,9 @@ import SwiftUI struct DetailScreen: View { @State private var isShowingPreferenceScreen = false - @Environment(\.modelContext) var modelContext @Bindable var contact: SelectedContact - var formattedPrimaryPhoneNumber: String { - Converter.getFormattedPhoneNumber(from: contact.phone) - } - - var formattedSecondaryPhoneNumber: String { - Converter.getFormattedPhoneNumber(from: contact.secondary_phone) - } - - var tappablePrimaryPhoneNumber: URL { - Converter.getTappablePhoneNumber(from: contact.phone) - } - - var tappableSecondaryPhoneNumber: URL { - Converter.getTappablePhoneNumber(from: contact.secondary_phone) - } - - var tappablePrimaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.email) - } - - var tappableSecondaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.secondary_email) - } - var body: some View { VStack { GradientView() @@ -48,109 +23,9 @@ struct DetailScreen: View { .offset(x: 0, y: -130) .padding(.bottom, -130) - VStack(alignment: .center, spacing: 10) { - Text(contact.name) - .font(.largeTitle) - .bold() - HStack(spacing: 0) { - Text("Preference: ") - .foregroundColor(.gray) - Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) - .foregroundColor(.gray) - } - Button { - isShowingPreferenceScreen = true - } label: { - Text("Change Notification Preference") - .font(.headline) - .foregroundColor(.orange) - } - } - - List { - Section(header: Text("Contact Information")) { - if ContactHelper.contactHasPhone(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Phone") - .font(.caption) - - Button(formattedPrimaryPhoneNumber) { - UIApplication.shared.open(tappablePrimaryPhoneNumber) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasSecondaryPhone(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Phone") - .font(.caption) - - Button(formattedSecondaryPhoneNumber) { - UIApplication.shared.open(tappableSecondaryPhoneNumber) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasEmail(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Email") - .font(.caption) - - Button(contact.email) { - UIApplication.shared.open(tappablePrimaryEmail) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasSecondaryEmail(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Email") - .font(.caption) - - Button(contact.secondary_email) { - UIApplication.shared.open(tappableSecondaryEmail) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasAddress(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Address") - .font(.caption) - Text(contact.address) - } - } - if ContactHelper.contactHasSecondaryAddress(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Address") - .font(.caption) - Text(contact.secondary_address) - } - } - if NotificationHelper.contactHasBirthday(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Birthday") - .font(.caption) - Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) - } - } - if NotificationHelper.contactHasAnniversary(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Anniversary") - .font(.caption) - Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) - } - } - } - } - } - .sheet( - isPresented: $isShowingPreferenceScreen, - onDismiss: { - NotificationHelper.removeExistingNotifications(for: contact) - NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) - }) { - PreferenceScreen(contact: contact) + NamePreferenceChangeStack(contact: contact, isShowingPreferenceScreen: $isShowingPreferenceScreen) + + ContactInfoListView(contact: contact) } .onAppear(perform: Utils.clearNotificationBadge) } diff --git a/CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift similarity index 100% rename from CatchUp-SwiftUI/Views/Supporting Views/ContactRowView.swift rename to CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift diff --git a/CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift similarity index 100% rename from CatchUp-SwiftUI/Views/Supporting Views/OpenContactPickerButtonView.swift rename to CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift From cd806c96e3aac209a1cec25031d221632e6e2e14 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 00:59:03 -0600 Subject: [PATCH 15/46] New 'next_notification_date_time' property so we can show the upcoming CatchUps --- CatchUp-SwiftUI/Data/SelectedContact.swift | 8 ++ CatchUp-SwiftUI/Utilities/ContactHelper.swift | 6 + .../Utilities/NotificationHelper.swift | 103 ++++++++++++++++-- CatchUp-SwiftUI/Views/HomeScreen.swift | 22 ++-- 4 files changed, 121 insertions(+), 18 deletions(-) diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift index 7630310..7b9a37c 100644 --- a/CatchUp-SwiftUI/Data/SelectedContact.swift +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -28,11 +28,13 @@ class SelectedContact { var notification_preference_hour: Int = 0 var notification_preference_minute: Int = 0 var notification_preference_weekday: Int = 0 + var notification_preference_week_of_month: Int = 0 var phone: String = "" var picture: String = "" var secondary_address: String = "" var secondary_email: String = "" var secondary_phone: String = "" + var next_notification_date_time: String = "" init( address: String, @@ -43,6 +45,7 @@ class SelectedContact { email: String, id: UUID, name: String, + next_notification_date_time: String, notification_identifier: UUID, notification_preference: Int, notification_preference_custom_day: Int, @@ -51,6 +54,7 @@ class SelectedContact { notification_preference_hour: Int, notification_preference_minute: Int, notification_preference_weekday: Int, + notification_preference_week_of_month: Int, phone: String, picture: String, secondary_address: String, @@ -65,6 +69,7 @@ class SelectedContact { self.email = email self.id = id self.name = name + self.next_notification_date_time = next_notification_date_time self.notification_identifier = notification_identifier self.notification_preference = notification_preference self.notification_preference_custom_day = notification_preference_custom_day @@ -73,6 +78,7 @@ class SelectedContact { self.notification_preference_hour = notification_preference_hour self.notification_preference_minute = notification_preference_minute self.notification_preference_weekday = notification_preference_weekday + self.notification_preference_week_of_month = notification_preference_week_of_month self.phone = phone self.picture = picture self.secondary_address = secondary_address @@ -89,6 +95,7 @@ class SelectedContact { email: "ryantoken13@gmail.com", id: UUID(), name: "Ryan Token", + next_notification_date_time: "", notification_identifier: UUID(), notification_preference: 0, notification_preference_custom_day: 3, @@ -97,6 +104,7 @@ class SelectedContact { notification_preference_hour: 12, notification_preference_minute: 0, notification_preference_weekday: 3, + notification_preference_week_of_month: 2, phone: "6363687771", picture: "photo-as-data-string", secondary_address: "", diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 442c65f..4c91f00 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -230,6 +230,7 @@ struct ContactHelper { let currentDay = Calendar.current.component(.day, from: Date()) let currentMonth = Calendar.current.component(.month, from: Date()) let currentYear = Calendar.current.component(.year, from: Date()) + let currentWeekOfMonth = Calendar.current.component(.weekOfYear, from: Date()) let id = UUID() let address = ContactHelper.getContactPrimaryAddress(for: contact) @@ -262,6 +263,7 @@ struct ContactHelper { email: email, id: id, name: name, + next_notification_date_time: "", notification_identifier: notification_identifier, notification_preference: notification_preference, notification_preference_custom_day: notification_preference_custom_day, @@ -270,6 +272,7 @@ struct ContactHelper { notification_preference_hour: notification_preference_hour, notification_preference_minute: notification_preference_minute, notification_preference_weekday: notification_preference_weekday, + notification_preference_week_of_month: currentWeekOfMonth, phone: phone, picture: picture, secondary_address: secondary_address, @@ -291,6 +294,8 @@ struct ContactHelper { getCNContactByName(selectedContact.name) { contact in if let contact { + let nextNotificationDateTime = NotificationHelper.getNextNotificationDateFor(contact: selectedContact) + selectedContact.name = getContactName(for: contact) selectedContact.phone = getContactPrimaryPhone(for: contact) selectedContact.secondary_phone = getContactSecondaryPhone(for: contact) @@ -301,6 +306,7 @@ struct ContactHelper { selectedContact.picture = encodeContactPicture(for: contact) selectedContact.birthday = getContactBirthday(for: contact) selectedContact.anniversary = getContactAnniversary(for: contact) + selectedContact.next_notification_date_time = nextNotificationDateTime } else { print("No contact with name \(selectedContact.name) found") } diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index dbdf5c1..16bedaf 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -25,6 +25,8 @@ class NotificationHelper { if contactHasAnniversary(contact) { addAnniversaryNotification(for: contact, modelContext: modelContext) } + + updateNextNotificationDateTimeFor(contact: contact) } checkNotificationAuthorizationStatusAndAddRequest(action: addRequest) @@ -50,8 +52,12 @@ class NotificationHelper { notificationContent.badge = 1 let identifier = UUID() - let dateComponents = setNotificationDateComponents(for: contact) - + let dateComponents = getNotificationDateComponents(for: contact) + if let weekOfMonth = dateComponents.weekOfMonth { + contact.notification_preference_week_of_month = weekOfMonth + try? modelContext.save() + } + scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent, modelContext: modelContext) } @@ -63,8 +69,8 @@ class NotificationHelper { birthdayNotificationContent.badge = 1 let birthdayIdentifier = UUID() - let birthdayDateComponents = setBirthdayDateComponents(for: contact) - + let birthdayDateComponents = getBirthdayDateComponents(for: contact) + scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent, modelContext: modelContext) } @@ -76,8 +82,8 @@ class NotificationHelper { anniversaryNotificationContent.badge = 1 let anniversaryIdentifier = UUID() - let anniversaryDateComponents = setAnniversaryDateComponents(for: contact) - + let anniversaryDateComponents = getAnniversaryDateComponents(for: contact) + scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent, modelContext: modelContext) } @@ -98,9 +104,9 @@ class NotificationHelper { } } - static func setNotificationDateComponents(for contact: SelectedContact) -> DateComponents { + static func getNotificationDateComponents(for contact: SelectedContact) -> DateComponents { var dateComponents = DateComponents() - + switch contact.notification_preference { case 0: // Never break @@ -130,11 +136,11 @@ class NotificationHelper { default: print("It's impossible to get here") } - + return dateComponents } - static func setBirthdayDateComponents(for contact: SelectedContact) -> DateComponents { + static func getBirthdayDateComponents(for contact: SelectedContact) -> DateComponents { var birthdayDateComponents = DateComponents() let month = (contact.birthday).prefix(2) @@ -148,7 +154,7 @@ class NotificationHelper { return birthdayDateComponents } - static func setAnniversaryDateComponents(for contact: SelectedContact) -> DateComponents { + static func getAnniversaryDateComponents(for contact: SelectedContact) -> DateComponents { var anniversaryDateComponents = DateComponents() let formatter = DateFormatter() @@ -308,6 +314,81 @@ class NotificationHelper { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [contact.anniversary_notification_id.uuidString]) } + static func updateNextNotificationDateTimeFor(contact: SelectedContact) { + let nextNotificationDateTime = getNextNotificationDateFor(contact: contact) + contact.next_notification_date_time = nextNotificationDateTime + } + + static func getNextNotificationDateFor(contact: SelectedContact) -> String { + var soonestUpcomingNotificationDateString = "Unknown" + + // Get next notification date for the general notification + var components = DateComponents() + switch contact.notification_preference { + case 1: // daily + components.hour = contact.notification_preference_hour + components.minute = contact.notification_preference_minute + case 2, 3: // weekly, monthly + components.hour = contact.notification_preference_hour + components.minute = contact.notification_preference_minute + components.weekday = contact.notification_preference_weekday + 1 + if contact.notification_preference_week_of_month != 0 { + components.weekOfMonth = contact.notification_preference_week_of_month + } + case 4: // custom date + components.hour = contact.notification_preference_hour + components.minute = contact.notification_preference_minute + components.month = contact.notification_preference_custom_month + components.day = contact.notification_preference_custom_day + components.year = contact.notification_preference_custom_year + default: + print("do nothing") + } + + print("dateComponents for \(contact.name): \(components)") + + soonestUpcomingNotificationDateString = calculateDateFromComponents(components) + print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString)") + + if contactHasBirthday(contact) { + let birthdayDateString = calculateDateFromComponents(getBirthdayDateComponents(for: contact)) + if birthdayDateString < soonestUpcomingNotificationDateString { + soonestUpcomingNotificationDateString = birthdayDateString + print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString) because their birthday is \(birthdayDateString)") + } + } + + if contactHasAnniversary(contact) { + let anniversaryDateString = calculateDateFromComponents(getAnniversaryDateComponents(for: contact)) + if anniversaryDateString < soonestUpcomingNotificationDateString { + soonestUpcomingNotificationDateString = anniversaryDateString + print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString) because their anniversary is \(anniversaryDateString)") + } + } + + print("soonestUpcomingNotification for \(contact.name): \(soonestUpcomingNotificationDateString)") + return soonestUpcomingNotificationDateString + } + + static func calculateDateFromComponents(_ dateComponents: DateComponents) -> String { + let calendar = Calendar.current + let currentDate = Date() + + // Calculate the date based on the provided components and current date + if let calculatedDate = calendar.nextDate(after: currentDate, matching: dateComponents, matchingPolicy: .nextTime) { + // Create a DateFormatter instance + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // Define the desired date format + + // Convert the calculated date to a human-readable date string + let formattedDate = dateFormatter.string(from: calculatedDate) + return formattedDate + } + + print("returning Unknown for soonestUpcomingNotification") + return "Unknown" + } + static func resetNotifications(for selectedContacts: [SelectedContact], modelContext: ModelContext) { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index c6d828c..346ce7a 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -8,12 +8,13 @@ import SwiftData import SwiftUI -import SwiftUIKit import ContactsUI struct HomeScreen : View { @Environment(\.modelContext) var modelContext + @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] + @Query(sort: \SelectedContact.next_notification_date_time) var nextCatchups: [SelectedContact] @AppStorage("savedVersion") var savedVersion = "2.0.0" @@ -30,13 +31,20 @@ struct HomeScreen : View { var body: some View { NavigationView { VStack { + // Grid of upcoming CatchUps + ForEach(nextCatchups.prefix(4)) { contact in + Text(contact.name) + } + List { - ForEach(selectedContacts) { contact in - NavigationLink(destination: DetailScreen(contact: contact)) { - ContactRowView(contact: contact) - } - } - .onDelete(perform: removePendingNotificationsAndDeleteContact) + Section("All CatchUps") { + ForEach(selectedContacts) { contact in + NavigationLink(destination: DetailScreen(contact: contact)) { + ContactRowView(contact: contact) + } + } + .onDelete(perform: removePendingNotificationsAndDeleteContact) + } } .refreshable { ContactHelper.updateSelectedContacts(selectedContacts) From 15fd75437a8479255ac49461332887dc5a18f822 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 01:00:06 -0600 Subject: [PATCH 16/46] Remove ScenePhase from the main app initializer --- CatchUp-SwiftUI/CatchUpApp.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index 67fd740..e6451ad 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -11,8 +11,6 @@ import SwiftUI @main struct CatchUpApp: App { - @Environment(\.scenePhase) var scenePhase - // use the SQLite file created by Core Data originally, instead of SwiftData's default.store file let url = URL.applicationSupportDirectory.appending(path: "CatchUp-SwiftUI.sqlite") let modelContainer: ModelContainer From 05f662e6510c5a700eb180cdb67f0eaba7097d8d Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 01:04:36 -0600 Subject: [PATCH 17/46] Filter out empty next catchup strings --- CatchUp-SwiftUI/Views/HomeScreen.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 346ce7a..fb2e543 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -23,6 +23,10 @@ struct HomeScreen : View { @State private var isShowingAboutSheet = false @State private var contactPicker = ContactPickerDelegate() + var filteredNextCatchups: [SelectedContact] { + return Array(nextCatchups.filter({ $0.next_notification_date_time != "" }).prefix(4)) + } + init() { //Use this if NavigationBarTitle is with Large Font UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange] @@ -32,7 +36,7 @@ struct HomeScreen : View { NavigationView { VStack { // Grid of upcoming CatchUps - ForEach(nextCatchups.prefix(4)) { contact in + ForEach(filteredNextCatchups) { contact in Text(contact.name) } From 99b895f465299ae0185ba2664b6818b3f3f3ae31 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 13:03:33 -0600 Subject: [PATCH 18/46] New grid to show your next CatchUps --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 8 ++ CatchUp-SwiftUI/Views/DetailScreen.swift | 1 + .../ContactPictureView.swift | 25 ++++++ .../HomeScreen Subviews/ContactRowView.swift | 6 +- .../NextCatchUpsGridView.swift | 85 +++++++++++++++++++ CatchUp-SwiftUI/Views/HomeScreen.swift | 17 ++-- 6 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactPictureView.swift create mode 100644 CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 8c8888a..6737bb3 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */; }; F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */; }; F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */; }; + F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F7535C2BB0956800B20090 /* NextCatchUpsGridView.swift */; }; + F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F7535E2BB0969A00B20090 /* ContactPictureView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -60,6 +62,8 @@ F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedContact.swift; sourceTree = ""; }; F4BAD42E2B94E8740009CD50 /* CatchUpApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatchUpApp.swift; sourceTree = ""; }; F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelContext+sqliteCommand.swift"; sourceTree = ""; }; + F4F7535C2BB0956800B20090 /* NextCatchUpsGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpsGridView.swift; sourceTree = ""; }; + F4F7535E2BB0969A00B20090 /* ContactPictureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPictureView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -92,7 +96,9 @@ isa = PBXGroup; children = ( F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */, + F4F7535E2BB0969A00B20090 /* ContactPictureView.swift */, F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */, + F4F7535C2BB0956800B20090 /* NextCatchUpsGridView.swift */, ); path = "HomeScreen Subviews"; sourceTree = ""; @@ -269,6 +275,7 @@ F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, 14BE3AD324593556004F72DE /* Utils.swift in Sources */, F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, + F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */, F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */, F494F95824424A03003CE7B5 /* ContactHelper.swift in Sources */, F4118B4F2B9EA168001BC8C7 /* ContactInfoListView.swift in Sources */, @@ -280,6 +287,7 @@ F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, + F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */, F4118B492B9E8FC7001BC8C7 /* ContactRowView.swift in Sources */, diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 975cb8f..f25aa58 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -28,5 +28,6 @@ struct DetailScreen: View { ContactInfoListView(contact: contact) } .onAppear(perform: Utils.clearNotificationBadge) + .navigationBarTitleDisplayMode(.inline) } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactPictureView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactPictureView.swift new file mode 100644 index 0000000..bf92ace --- /dev/null +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactPictureView.swift @@ -0,0 +1,25 @@ +// +// ContactPictureView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/24/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct ContactPictureView: View { + let contact: SelectedContact + + var body: some View { + Converter.getContactPicture(from: contact.picture) + .renderingMode(.original) + .resizable() + .frame(width: 45, height: 45, alignment: .leading) + .clipShape(Circle()) + } +} + +#Preview { + ContactPictureView(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index cffd1a6..6c2a885 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -13,11 +13,7 @@ struct ContactRowView: View { var body: some View { HStack { - Converter.getContactPicture(from: contact.picture) - .renderingMode(.original) - .resizable() - .frame(width: 45, height: 45, alignment: .leading) - .clipShape(Circle()) + ContactPictureView(contact: contact) VStack(alignment: .leading, spacing: 2) { Text(contact.name) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift new file mode 100644 index 0000000..6f8bc67 --- /dev/null +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -0,0 +1,85 @@ +// +// NextCatchUpsGridView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/24/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NextCatchUpsGridView: View { + var nextCatchUps: [SelectedContact] + + @Binding var shouldNavigateViaGrid: Bool + @Binding var tappedGridContact: SelectedContact + + // 2 column grid + let columns = [ + GridItem(.flexible(minimum: 0, maximum: .infinity)), + GridItem(.flexible(minimum: 0, maximum: .infinity)) + ] + + var body: some View { + LazyVGrid(columns: columns, spacing: 10) { + ForEach(nextCatchUps) { contact in + HStack { + ContactPictureView(contact: contact) + .padding(.trailing, 5) + + VStack(alignment: .leading, spacing: 2) { + Text(contact.name.components(separatedBy: " ").first ?? contact.name) + .font(.headline) + + Text(friendlyNextCatchUpTime(for: contact)) + .foregroundStyle(.gray) + .font(.caption) + } + } + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(10) + .background(Color.white) + .cornerRadius(10) + .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 0, y: 2) + + .onTapGesture { + tappedGridContact = contact + shouldNavigateViaGrid = true + } + } + } + .padding(.bottom, 5) + .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0)) + .listRowBackground(Color.clear) + } + + func friendlyNextCatchUpTime(for contact: SelectedContact) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + if let date = dateFormatter.date(from: contact.next_notification_date_time) { + let friendlyFormatter = DateFormatter() + + if Calendar.current.isDateInToday(date) { + friendlyFormatter.dateFormat = "h:mm a" + return "Today at \(friendlyFormatter.string(from: date))" + } else if Calendar.current.isDateInTomorrow(date) { + friendlyFormatter.dateFormat = "h:mm a" + return "Tomorrow at \(friendlyFormatter.string(from: date))" + } else { + friendlyFormatter.dateFormat = "MMMM d 'at' h:mm a" + return friendlyFormatter.string(from: date) + } + } else { + return "Unknown" + } + } +} + +#Preview { + NextCatchUpsGridView( + nextCatchUps: [SelectedContact.sampleData, SelectedContact.sampleData, SelectedContact.sampleData], + shouldNavigateViaGrid: .constant(false), + tappedGridContact: .constant(SelectedContact.sampleData) + ) +} diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index fb2e543..1e53d9f 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -21,6 +21,8 @@ struct HomeScreen : View { @State private var isColdLaunch = true @State private var isShowingUpdatesSheet = false @State private var isShowingAboutSheet = false + @State private var shouldNavigateViaGrid = false + @State private var tappedGridContact: SelectedContact = SelectedContact.sampleData @State private var contactPicker = ContactPickerDelegate() var filteredNextCatchups: [SelectedContact] { @@ -33,14 +35,13 @@ struct HomeScreen : View { } var body: some View { - NavigationView { + NavigationStack { VStack { - // Grid of upcoming CatchUps - ForEach(filteredNextCatchups) { contact in - Text(contact.name) - } - List { + Section("Next CatchUps") { + NextCatchUpsGridView(nextCatchUps: filteredNextCatchups, shouldNavigateViaGrid: $shouldNavigateViaGrid, tappedGridContact: $tappedGridContact) + } + Section("All CatchUps") { ForEach(selectedContacts) { contact in NavigationLink(destination: DetailScreen(contact: contact)) { @@ -87,6 +88,10 @@ struct HomeScreen : View { } } } + + .navigationDestination(isPresented: $shouldNavigateViaGrid) { + DetailScreen(contact: tappedGridContact) + } } .accentColor(.orange) From f35bfb64d96ba7ac407656b39519fac972f0397b Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 21:04:22 -0600 Subject: [PATCH 19/46] Minor design tweaks to the grid; run the getCNContactByName on a background thread --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 ++++ CatchUp-SwiftUI/Extensions/View+if.swift | 24 +++++++++++++++++++ CatchUp-SwiftUI/Utilities/ContactHelper.swift | 2 +- .../NextCatchUpsGridView.swift | 9 ++++--- .../OpenContactPickerButtonView.swift | 3 +-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 CatchUp-SwiftUI/Extensions/View+if.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 6737bb3..589874d 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */; }; F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F7535C2BB0956800B20090 /* NextCatchUpsGridView.swift */; }; F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F7535E2BB0969A00B20090 /* ContactPictureView.swift */; }; + F4F753652BB11FA300B20090 /* View+if.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F753642BB11FA300B20090 /* View+if.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -64,6 +65,7 @@ F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelContext+sqliteCommand.swift"; sourceTree = ""; }; F4F7535C2BB0956800B20090 /* NextCatchUpsGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpsGridView.swift; sourceTree = ""; }; F4F7535E2BB0969A00B20090 /* ContactPictureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPictureView.swift; sourceTree = ""; }; + F4F753642BB11FA300B20090 /* View+if.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+if.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -180,6 +182,7 @@ children = ( F4095B6324C66F87007163E3 /* SKProduct+localizedPrice.swift */, F4BAD4302B94F5680009CD50 /* ModelContext+sqliteCommand.swift */, + F4F753642BB11FA300B20090 /* View+if.swift */, ); path = Extensions; sourceTree = ""; @@ -274,6 +277,7 @@ F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */, F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, 14BE3AD324593556004F72DE /* Utils.swift in Sources */, + F4F753652BB11FA300B20090 /* View+if.swift in Sources */, F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */, F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */, diff --git a/CatchUp-SwiftUI/Extensions/View+if.swift b/CatchUp-SwiftUI/Extensions/View+if.swift new file mode 100644 index 0000000..550e77e --- /dev/null +++ b/CatchUp-SwiftUI/Extensions/View+if.swift @@ -0,0 +1,24 @@ +// +// View+if.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/24/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +extension View { + /// Applies the given transform if the given condition evaluates to `true`. + /// - Parameters: + /// - condition: The condition to evaluate. + /// - transform: The transform to apply to the source `View`. + /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. + @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { + if condition() { + transform(self) + } else { + self + } + } +} diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 4c91f00..ee90172 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -331,7 +331,7 @@ struct ContactHelper { CNContactDatesKey as CNKeyDescriptor ] - DispatchQueue.global().async { + DispatchQueue.global(qos: .background).async { var allContacts: [CNContact] = [] do { diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index 6f8bc67..21147a2 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -9,8 +9,9 @@ import SwiftUI struct NextCatchUpsGridView: View { - var nextCatchUps: [SelectedContact] + @Environment(\.colorScheme) var colorScheme + var nextCatchUps: [SelectedContact] @Binding var shouldNavigateViaGrid: Bool @Binding var tappedGridContact: SelectedContact @@ -38,9 +39,11 @@ struct NextCatchUpsGridView: View { } .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .padding(10) - .background(Color.white) + .background(colorScheme == .light ? Color.white : Color(UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 1))) .cornerRadius(10) - .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 0, y: 2) + .if(colorScheme == .light) { view in + view.shadow(color: Color.gray.opacity(0.4), radius: 3, x: 0, y: 2) + } .onTapGesture { tappedGridContact = contact diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift index 2c73a60..7e13a59 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift @@ -17,8 +17,7 @@ struct OpenContactPickerButtonView: View { } .font(.headline) .foregroundColor(.blue) - .padding(.top) - .padding(.bottom) + .padding(.top, 10) } } From c26eb38c1cd115caa78cf303883ad39783d843e1 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 24 Mar 2024 21:08:58 -0600 Subject: [PATCH 20/46] Use structs instead of classes for static utils; moved some functions --- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 11 +++++-- CatchUp-SwiftUI/Utilities/Conversions.swift | 2 +- .../Utilities/NotificationHelper.swift | 30 +++++-------------- CatchUp-SwiftUI/Utilities/Utils.swift | 2 +- .../ContactInfoListView.swift | 4 +-- 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index ee90172..b8e7884 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -12,7 +12,6 @@ import Contacts import CoreData struct ContactHelper { - // MARK: Functions for DetailScreen static func contactHasPhone(_ contact: SelectedContact) -> Bool { @@ -38,7 +37,15 @@ struct ContactHelper { static func contactHasSecondaryAddress(_ contact: SelectedContact) -> Bool { return contact.secondary_address != "" ? true : false } - + + static func contactHasBirthday(_ contact: SelectedContact) -> Bool { + return contact.birthday != "" ? true : false + } + + static func contactHasAnniversary(_ contact: SelectedContact) -> Bool { + return contact.anniversary != "" ? true : false + } + // MARK: Functions for ContactPickerViewController static func encodeContactPicture(for contact: CNContact) -> String { diff --git a/CatchUp-SwiftUI/Utilities/Conversions.swift b/CatchUp-SwiftUI/Utilities/Conversions.swift index 3f8242e..d0b56f3 100644 --- a/CatchUp-SwiftUI/Utilities/Conversions.swift +++ b/CatchUp-SwiftUI/Utilities/Conversions.swift @@ -10,7 +10,7 @@ import Foundation import SwiftUI import PhoneNumberKit -class Converter { +struct Converter { // MARK: Only used in DetailScreen static func getFormattedPhoneNumber(from phoneNumber: String) -> String { let phoneNumberKit = PhoneNumberKit() diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 16bedaf..197401d 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -11,18 +11,18 @@ import SwiftUI import UserNotifications import CoreData -class NotificationHelper { +struct NotificationHelper { static func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { let addRequest = { if preferenceIsNotSetToNever(for: contact) { addGeneralNotification(for: contact, modelContext: modelContext) } - if contactHasBirthday(contact) { + if ContactHelper.contactHasBirthday(contact) { addBirthdayNotification(for: contact, modelContext: modelContext) } - if contactHasAnniversary(contact) { + if ContactHelper.contactHasAnniversary(contact) { addAnniversaryNotification(for: contact, modelContext: modelContext) } @@ -36,14 +36,6 @@ class NotificationHelper { return contact.notification_preference != 0 ? true : false } - static func contactHasBirthday(_ contact: SelectedContact) -> Bool { - return contact.birthday != "" ? true : false - } - - static func contactHasAnniversary(_ contact: SelectedContact) -> Bool { - return contact.anniversary != "" ? true : false - } - static func addGeneralNotification(for contact: SelectedContact, modelContext: ModelContext) { let notificationContent = UNMutableNotificationContent() notificationContent.title = "👋 CatchUp with \(contact.name)" @@ -289,11 +281,11 @@ class NotificationHelper { static func removeExistingNotifications(for contact: SelectedContact) { removeGeneralNotification(for: contact) - if contactHasBirthday(contact) { + if ContactHelper.contactHasBirthday(contact) { removeBirthdayNotification(for: contact) } - if contactHasAnniversary(contact) { + if ContactHelper.contactHasAnniversary(contact) { removeAnniversaryNotification(for: contact) } } @@ -320,8 +312,6 @@ class NotificationHelper { } static func getNextNotificationDateFor(contact: SelectedContact) -> String { - var soonestUpcomingNotificationDateString = "Unknown" - // Get next notification date for the general notification var components = DateComponents() switch contact.notification_preference { @@ -345,24 +335,20 @@ class NotificationHelper { print("do nothing") } - print("dateComponents for \(contact.name): \(components)") - + var soonestUpcomingNotificationDateString = "Unknown" soonestUpcomingNotificationDateString = calculateDateFromComponents(components) - print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString)") - if contactHasBirthday(contact) { + if ContactHelper.contactHasBirthday(contact) { let birthdayDateString = calculateDateFromComponents(getBirthdayDateComponents(for: contact)) if birthdayDateString < soonestUpcomingNotificationDateString { soonestUpcomingNotificationDateString = birthdayDateString - print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString) because their birthday is \(birthdayDateString)") } } - if contactHasAnniversary(contact) { + if ContactHelper.contactHasAnniversary(contact) { let anniversaryDateString = calculateDateFromComponents(getAnniversaryDateComponents(for: contact)) if anniversaryDateString < soonestUpcomingNotificationDateString { soonestUpcomingNotificationDateString = anniversaryDateString - print("soonestUpcomingNotification for \(contact.name) is now \(soonestUpcomingNotificationDateString) because their anniversary is \(anniversaryDateString)") } } diff --git a/CatchUp-SwiftUI/Utilities/Utils.swift b/CatchUp-SwiftUI/Utilities/Utils.swift index 68bbddf..11be633 100644 --- a/CatchUp-SwiftUI/Utilities/Utils.swift +++ b/CatchUp-SwiftUI/Utilities/Utils.swift @@ -11,7 +11,7 @@ import Foundation import CoreData import UserNotifications -class Utils { +struct Utils { static func clearNotificationBadge() { UNUserNotificationCenter.current().setBadgeCount(0) } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift index e1a09de..e923b4d 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift @@ -96,14 +96,14 @@ struct ContactInfoListView: View { Text(contact.secondary_address) } } - if NotificationHelper.contactHasBirthday(contact) { + if ContactHelper.contactHasBirthday(contact) { VStack(alignment: .leading, spacing: 3) { Text("Birthday") .font(.caption) Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) } } - if NotificationHelper.contactHasAnniversary(contact) { + if ContactHelper.contactHasAnniversary(contact) { VStack(alignment: .leading, spacing: 3) { Text("Anniversary") .font(.caption) From 0c72b80f936b772f5c5ae2ef9afaa1b9e19426a0 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Wed, 27 Mar 2024 20:44:39 -0600 Subject: [PATCH 21/46] Enabled Strict Concurrency again and fixed most issues; fixed an IAP bug; and we now update the next notification time every time the user opens the app --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 2 + CatchUp-SwiftUI/Services/IAPService.swift | 8 ++- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 2 +- CatchUp-SwiftUI/Views/AboutScreen.swift | 69 ++++++++++--------- .../NextCatchUpsGridView.swift | 2 +- CatchUp-SwiftUI/Views/HomeScreen.swift | 30 ++++++-- 6 files changed, 70 insertions(+), 43 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 589874d..6e4e4e3 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -448,6 +448,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.tokensolutions.CatchUp; PRODUCT_NAME = CatchUp; SUPPORTS_MACCATALYST = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -475,6 +476,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.tokensolutions.CatchUp; PRODUCT_NAME = CatchUp; SUPPORTS_MACCATALYST = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/CatchUp-SwiftUI/Services/IAPService.swift b/CatchUp-SwiftUI/Services/IAPService.swift index f90e24a..7e64982 100644 --- a/CatchUp-SwiftUI/Services/IAPService.swift +++ b/CatchUp-SwiftUI/Services/IAPService.swift @@ -23,7 +23,7 @@ enum IAPServiceAlertType{ } } -class IAPService: NSObject { +final class IAPService: NSObject { static let shared = IAPService() let graciousTipProductID = "gracious_tip_0.99" @@ -39,7 +39,7 @@ class IAPService: NSObject { // MARK: - MAKE PURCHASE OF A PRODUCT func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() } - func leaveATip(index: Int){ + func leaveATip(index: Int) { if iapProducts.count == 0 { print("No IAPs to purchase") return @@ -92,7 +92,9 @@ extension IAPService: SKProductsRequestDelegate, SKPaymentTransactionObserver{ // MARK: - REQUEST IAP PRODUCTS func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) { if (response.products.count > 0) { - iapProducts = response.products + let sortedProducts = response.products.sorted(by: { $0.price.decimalValue < $1.price.decimalValue }) + + iapProducts = sortedProducts for product in iapProducts{ let numberFormatter = NumberFormatter() numberFormatter.formatterBehavior = .behavior10_4 diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index b8e7884..8034fb0 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -360,7 +360,7 @@ struct ContactHelper { return nameFormatter.string(from: contact) == name } - print("Found matching contacts: \(filteredContacts)") + print("Found matching contact: \(filteredContacts.first?.givenName ?? "Unknown")") DispatchQueue.main.async { completion(filteredContacts.first) diff --git a/CatchUp-SwiftUI/Views/AboutScreen.swift b/CatchUp-SwiftUI/Views/AboutScreen.swift index e6e1243..c2924a0 100644 --- a/CatchUp-SwiftUI/Views/AboutScreen.swift +++ b/CatchUp-SwiftUI/Views/AboutScreen.swift @@ -10,9 +10,7 @@ import SwiftUI struct AboutScreen: View { @State private var showingUpdateScreen = false - - let generator = UINotificationFeedbackGenerator() - + let smallTip = IAPService.shared.getSmallTipAmount() let mediumTip = IAPService.shared.getMediumTipAmount() let largeTip = IAPService.shared.getLargeTipAmount() @@ -58,38 +56,38 @@ struct AboutScreen: View { Spacer() Button(smallTip) { - graciousTipPressed() + tappedSmallTip() } - .font(.headline) - .foregroundColor(.white) - .padding() - .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) - ) - .shadow(radius: 15) + .font(.headline) + .foregroundColor(.white) + .padding() + .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) + ) + .shadow(radius: 15) Spacer() Button(mediumTip) { - generousTipPressed() + tappedMediumTip() } - .font(.headline) - .foregroundColor(.white) - .padding() - .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) - ) - .shadow(radius: 15) + .font(.headline) + .foregroundColor(.white) + .padding() + .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) + ) + .shadow(radius: 15) Spacer() Button(largeTip) { - gratuitousTipPressed() + tappedLargeTip() } - .font(.headline) - .foregroundColor(.white) - .padding() - .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) - ) - .shadow(radius: 15) + .font(.headline) + .foregroundColor(.white) + .padding() + .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) + ) + .shadow(radius: 15) Spacer() } @@ -120,19 +118,22 @@ struct AboutScreen: View { UpdatesScreen() } } - - func graciousTipPressed() { - generator.notificationOccurred(.success) - IAPService.shared.leaveATip(index: 1) + + @MainActor + func tappedSmallTip() { + UINotificationFeedbackGenerator().notificationOccurred(.success) + IAPService.shared.leaveATip(index: 0) } - - func generousTipPressed() { - generator.notificationOccurred(.success) - IAPService.shared.leaveATip(index: 0) + + @MainActor + func tappedMediumTip() { + UINotificationFeedbackGenerator().notificationOccurred(.success) + IAPService.shared.leaveATip(index: 1) } - func gratuitousTipPressed() { - generator.notificationOccurred(.success) + @MainActor + func tappedLargeTip() { + UINotificationFeedbackGenerator().notificationOccurred(.success) IAPService.shared.leaveATip(index: 2) } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index 21147a2..7f5178b 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -13,7 +13,7 @@ struct NextCatchUpsGridView: View { var nextCatchUps: [SelectedContact] @Binding var shouldNavigateViaGrid: Bool - @Binding var tappedGridContact: SelectedContact + @Binding var tappedGridContact: SelectedContact? // 2 column grid let columns = [ diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 1e53d9f..1d8992d 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -12,6 +12,7 @@ import ContactsUI struct HomeScreen : View { @Environment(\.modelContext) var modelContext + @Environment(\.scenePhase) var scenePhase @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] @Query(sort: \SelectedContact.next_notification_date_time) var nextCatchups: [SelectedContact] @@ -22,13 +23,14 @@ struct HomeScreen : View { @State private var isShowingUpdatesSheet = false @State private var isShowingAboutSheet = false @State private var shouldNavigateViaGrid = false - @State private var tappedGridContact: SelectedContact = SelectedContact.sampleData + @State private var tappedGridContact: SelectedContact? = nil @State private var contactPicker = ContactPickerDelegate() var filteredNextCatchups: [SelectedContact] { return Array(nextCatchups.filter({ $0.next_notification_date_time != "" }).prefix(4)) } + @MainActor init() { //Use this if NavigationBarTitle is with Large Font UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemOrange] @@ -52,7 +54,7 @@ struct HomeScreen : View { } } .refreshable { - ContactHelper.updateSelectedContacts(selectedContacts) + ContactHelper.updateSelectedContacts(selectedContacts) } .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in @@ -90,7 +92,9 @@ struct HomeScreen : View { } .navigationDestination(isPresented: $shouldNavigateViaGrid) { - DetailScreen(contact: tappedGridContact) + if let tappedGridContact { + DetailScreen(contact: tappedGridContact) + } } } .accentColor(.orange) @@ -101,12 +105,22 @@ struct HomeScreen : View { NotificationHelper.requestAuthorizationForNotifications() if isColdLaunch { + print("resetting notifications") NotificationHelper.resetNotifications(for: selectedContacts, modelContext: modelContext) - isColdLaunch = false } } + + .onChange(of: scenePhase) { initialPhase, newPhase in + if !isColdLaunch { + if newPhase == .active { + updateNextNotificationTime(for: selectedContacts) + } + } + isColdLaunch = false + } } + @MainActor func openContactPicker() { let contactPicker = CNContactPickerViewController() contactPicker.delegate = self.contactPicker @@ -116,6 +130,14 @@ struct HomeScreen : View { window?.rootViewController?.present(contactPicker, animated: true, completion: nil) } + func updateNextNotificationTime(for contacts: [SelectedContact]) { + print("updating next notification time for all contacts") + for contact in contacts { + let nextNotificationDateTime = NotificationHelper.getNextNotificationDateFor(contact: contact) + contact.next_notification_date_time = nextNotificationDateTime + } + } + // save selected contacts and their properties to SwiftData func saveSelectedContact(for contacts: [CNContact]) { for contact in contacts { From 5707eed20e2981a302456153b72ce25a13fa2dfe Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Wed, 27 Mar 2024 20:44:53 -0600 Subject: [PATCH 22/46] formatting fix --- CatchUp-SwiftUI/Views/HomeScreen.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 1d8992d..e216af6 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -54,7 +54,7 @@ struct HomeScreen : View { } } .refreshable { - ContactHelper.updateSelectedContacts(selectedContacts) + ContactHelper.updateSelectedContacts(selectedContacts) } .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in From ca2f6cb00e8edd75a02701ea0bdc2ae69a918230 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Wed, 27 Mar 2024 21:00:38 -0600 Subject: [PATCH 23/46] Fix bug where new notification preference wouldn't update the next catchups grid right away in some cases --- CatchUp-SwiftUI/Utilities/NotificationHelper.swift | 4 ++-- .../Views/HomeScreen Subviews/NextCatchUpsGridView.swift | 2 +- CatchUp-SwiftUI/Views/HomeScreen.swift | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 197401d..9394ff7 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -14,6 +14,8 @@ import CoreData struct NotificationHelper { static func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { let addRequest = { + updateNextNotificationDateTimeFor(contact: contact) + if preferenceIsNotSetToNever(for: contact) { addGeneralNotification(for: contact, modelContext: modelContext) } @@ -25,8 +27,6 @@ struct NotificationHelper { if ContactHelper.contactHasAnniversary(contact) { addAnniversaryNotification(for: contact, modelContext: modelContext) } - - updateNextNotificationDateTimeFor(contact: contact) } checkNotificationAuthorizationStatusAndAddRequest(action: addRequest) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index 7f5178b..56d540f 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -11,7 +11,7 @@ import SwiftUI struct NextCatchUpsGridView: View { @Environment(\.colorScheme) var colorScheme - var nextCatchUps: [SelectedContact] + let nextCatchUps: [SelectedContact] @Binding var shouldNavigateViaGrid: Bool @Binding var tappedGridContact: SelectedContact? diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index e216af6..c49ffe2 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -27,7 +27,9 @@ struct HomeScreen : View { @State private var contactPicker = ContactPickerDelegate() var filteredNextCatchups: [SelectedContact] { - return Array(nextCatchups.filter({ $0.next_notification_date_time != "" }).prefix(4)) + withAnimation { + return Array(nextCatchups.filter({ $0.next_notification_date_time != "" }).prefix(4)) + } } @MainActor From 6e3389c9f0de2d8b53d55e8490ea9c511f9cf3a3 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Wed, 27 Mar 2024 21:23:37 -0600 Subject: [PATCH 24/46] Found and fixed the cause of the frequent crashes when creating new notifications --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- CatchUp-SwiftUI/Utilities/NotificationHelper.swift | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4e5c8e0..bebd010 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CatchUp-SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/marmelroy/PhoneNumberKit.git", "state" : { - "revision" : "a8d72d9c90f8336aff6fd6002976d7e36f4fbe8c", - "version" : "3.7.9" + "revision" : "ee5d7114934e60812c9b47c333f01b67d002be2d", + "version" : "3.7.10" } } ], diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 9394ff7..c8b02b9 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -12,10 +12,11 @@ import UserNotifications import CoreData struct NotificationHelper { + @MainActor static func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { + updateNextNotificationDateTimeFor(contact: contact) + let addRequest = { - updateNextNotificationDateTimeFor(contact: contact) - if preferenceIsNotSetToNever(for: contact) { addGeneralNotification(for: contact, modelContext: modelContext) } @@ -306,6 +307,7 @@ struct NotificationHelper { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [contact.anniversary_notification_id.uuidString]) } + @MainActor static func updateNextNotificationDateTimeFor(contact: SelectedContact) { let nextNotificationDateTime = getNextNotificationDateFor(contact: contact) contact.next_notification_date_time = nextNotificationDateTime @@ -375,6 +377,7 @@ struct NotificationHelper { return "Unknown" } + @MainActor static func resetNotifications(for selectedContacts: [SelectedContact], modelContext: ModelContext) { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() From 2bd42df7d6bd825402154b29733621fb1686d1e0 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Fri, 29 Mar 2024 22:48:33 -0600 Subject: [PATCH 25/46] Remove PreferenceScreen and redesign DetailScreen to include notification preference --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 24 +-- .../ContactInfoListView.swift | 120 --------------- .../ContactInfoView.swift | 116 +++++++++++++++ .../NameAndPreferenceStack.swift | 35 +++++ .../NamePreferenceChangeStack.swift | 52 ------- .../NotificationPreferenceView.swift | 123 +++++++++++++++ CatchUp-SwiftUI/Views/DetailScreen.swift | 33 ++++- CatchUp-SwiftUI/Views/PreferenceScreen.swift | 140 ------------------ 8 files changed, 312 insertions(+), 331 deletions(-) delete mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift delete mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift delete mode 100644 CatchUp-SwiftUI/Views/PreferenceScreen.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 6e4e4e3..9bdedf9 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -14,8 +14,9 @@ F4118B452B9E68AE001BC8C7 /* ContactPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */; }; F4118B492B9E8FC7001BC8C7 /* ContactRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */; }; F4118B4B2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */; }; - F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */; }; - F4118B4F2B9EA168001BC8C7 /* ContactInfoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */; }; + F4118B4D2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */; }; + F4118B4F2B9EA168001BC8C7 /* ContactInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */; }; + F435D42F2BB78A6F00C43586 /* NotificationPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -24,7 +25,6 @@ F4A9B8522442A0F5001D8C55 /* DetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */; }; F4A9B8552442A179001D8C55 /* ContactPhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8542442A179001D8C55 /* ContactPhoto.swift */; }; F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8562442A1F7001D8C55 /* GradientView.swift */; }; - F4A9B8592442AB81001D8C55 /* PreferenceScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */; }; F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A9B85A2443FFF3001D8C55 /* Conversions.swift */; }; F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AD244C9FF600296568 /* IAPService.swift */; }; F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AD59AF244CA12C00296568 /* AboutScreen.swift */; }; @@ -43,8 +43,9 @@ F4118B442B9E68AE001BC8C7 /* ContactPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerDelegate.swift; sourceTree = ""; }; F4118B482B9E8FC7001BC8C7 /* ContactRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRowView.swift; sourceTree = ""; }; F4118B4A2B9E912E001BC8C7 /* OpenContactPickerButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenContactPickerButtonView.swift; sourceTree = ""; }; - F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamePreferenceChangeStack.swift; sourceTree = ""; }; - F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInfoListView.swift; sourceTree = ""; }; + F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameAndPreferenceStack.swift; sourceTree = ""; }; + F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInfoView.swift; sourceTree = ""; }; + F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferenceView.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -55,7 +56,6 @@ F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreen.swift; sourceTree = ""; }; F4A9B8542442A179001D8C55 /* ContactPhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPhoto.swift; sourceTree = ""; }; F4A9B8562442A1F7001D8C55 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; - F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceScreen.swift; sourceTree = ""; }; F4A9B85A2443FFF3001D8C55 /* Conversions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conversions.swift; sourceTree = ""; }; F4AD59AC244B7B6000296568 /* CatchUp-SwiftUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "CatchUp-SwiftUI.entitlements"; sourceTree = ""; }; F4AD59AD244C9FF600296568 /* IAPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPService.swift; sourceTree = ""; }; @@ -87,7 +87,6 @@ F4118B502B9EA2E3001BC8C7 /* HomeScreen Subviews */, F4A9B8512442A0F5001D8C55 /* DetailScreen.swift */, F4118B512B9EA2F7001BC8C7 /* DetailScreen Subviews */, - F4A9B8582442AB81001D8C55 /* PreferenceScreen.swift */, F4AD59AF244CA12C00296568 /* AboutScreen.swift */, 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */, ); @@ -110,8 +109,9 @@ children = ( F4A9B8562442A1F7001D8C55 /* GradientView.swift */, F4A9B8542442A179001D8C55 /* ContactPhoto.swift */, - F4118B4C2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift */, - F4118B4E2B9EA168001BC8C7 /* ContactInfoListView.swift */, + F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */, + F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */, + F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */, ); path = "DetailScreen Subviews"; sourceTree = ""; @@ -280,14 +280,14 @@ F4F753652BB11FA300B20090 /* View+if.swift in Sources */, F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */, - F4118B4D2B9EA11A001BC8C7 /* NamePreferenceChangeStack.swift in Sources */, + F4118B4D2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift in Sources */, F494F95824424A03003CE7B5 /* ContactHelper.swift in Sources */, - F4118B4F2B9EA168001BC8C7 /* ContactInfoListView.swift in Sources */, + F4118B4F2B9EA168001BC8C7 /* ContactInfoView.swift in Sources */, + F435D42F2BB78A6F00C43586 /* NotificationPreferenceView.swift in Sources */, F4095B6424C66F87007163E3 /* SKProduct+localizedPrice.swift in Sources */, F4BAD42F2B94E8740009CD50 /* CatchUpApp.swift in Sources */, 14BE3AD72459F610004F72DE /* UpdatesScreen.swift in Sources */, F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */, - F4A9B8592442AB81001D8C55 /* PreferenceScreen.swift in Sources */, F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift deleted file mode 100644 index e923b4d..0000000 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoListView.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// ContactInfoListView.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 3/10/24. -// Copyright © 2024 Token Solutions. All rights reserved. -// - -import SwiftUI - -struct ContactInfoListView: View { - let contact: SelectedContact - - var formattedPrimaryPhoneNumber: String { - Converter.getFormattedPhoneNumber(from: contact.phone) - } - - var formattedSecondaryPhoneNumber: String { - Converter.getFormattedPhoneNumber(from: contact.secondary_phone) - } - - var tappablePrimaryPhoneNumber: URL { - Converter.getTappablePhoneNumber(from: contact.phone) - } - - var tappableSecondaryPhoneNumber: URL { - Converter.getTappablePhoneNumber(from: contact.secondary_phone) - } - - var tappablePrimaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.email) - } - - var tappableSecondaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.secondary_email) - } - - var body: some View { - List { - Section(header: Text("Contact Information")) { - if ContactHelper.contactHasPhone(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Phone") - .font(.caption) - - Button(formattedPrimaryPhoneNumber) { - UIApplication.shared.open(tappablePrimaryPhoneNumber) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasSecondaryPhone(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Phone") - .font(.caption) - - Button(formattedSecondaryPhoneNumber) { - UIApplication.shared.open(tappableSecondaryPhoneNumber) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasEmail(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Email") - .font(.caption) - - Button(contact.email) { - UIApplication.shared.open(tappablePrimaryEmail) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasSecondaryEmail(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Email") - .font(.caption) - - Button(contact.secondary_email) { - UIApplication.shared.open(tappableSecondaryEmail) - } - .foregroundColor(.blue) - } - } - if ContactHelper.contactHasAddress(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Address") - .font(.caption) - Text(contact.address) - } - } - if ContactHelper.contactHasSecondaryAddress(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Secondary Address") - .font(.caption) - Text(contact.secondary_address) - } - } - if ContactHelper.contactHasBirthday(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Birthday") - .font(.caption) - Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) - } - } - if ContactHelper.contactHasAnniversary(contact) { - VStack(alignment: .leading, spacing: 3) { - Text("Anniversary") - .font(.caption) - Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) - } - } - } - } - } -} - -#Preview { - ContactInfoListView(contact: SelectedContact.sampleData) -} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift new file mode 100644 index 0000000..98ae23f --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift @@ -0,0 +1,116 @@ +// +// ContactInfoView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct ContactInfoView: View { + let contact: SelectedContact + + var formattedPrimaryPhoneNumber: String { + Converter.getFormattedPhoneNumber(from: contact.phone) + } + + var formattedSecondaryPhoneNumber: String { + Converter.getFormattedPhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryPhoneNumber: URL { + Converter.getTappablePhoneNumber(from: contact.phone) + } + + var tappableSecondaryPhoneNumber: URL { + Converter.getTappablePhoneNumber(from: contact.secondary_phone) + } + + var tappablePrimaryEmail: URL { + Converter.getTappablePhoneNumber(from: contact.email) + } + + var tappableSecondaryEmail: URL { + Converter.getTappablePhoneNumber(from: contact.secondary_email) + } + + var body: some View { + if ContactHelper.contactHasPhone(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Phone") + .font(.caption) + + Button(formattedPrimaryPhoneNumber) { + UIApplication.shared.open(tappablePrimaryPhoneNumber) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasSecondaryPhone(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Phone") + .font(.caption) + + Button(formattedSecondaryPhoneNumber) { + UIApplication.shared.open(tappableSecondaryPhoneNumber) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasEmail(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Email") + .font(.caption) + + Button(contact.email) { + UIApplication.shared.open(tappablePrimaryEmail) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasSecondaryEmail(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Email") + .font(.caption) + + Button(contact.secondary_email) { + UIApplication.shared.open(tappableSecondaryEmail) + } + .foregroundColor(.blue) + } + } + if ContactHelper.contactHasAddress(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Address") + .font(.caption) + Text(contact.address) + } + } + if ContactHelper.contactHasSecondaryAddress(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Secondary Address") + .font(.caption) + Text(contact.secondary_address) + } + } + if ContactHelper.contactHasBirthday(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Birthday") + .font(.caption) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.birthday)) + } + } + if ContactHelper.contactHasAnniversary(contact) { + VStack(alignment: .leading, spacing: 3) { + Text("Anniversary") + .font(.caption) + Text(Converter.getFormattedBirthdayOrAnniversary(from: contact.anniversary)) + } + } + } +} + +#Preview { + ContactInfoView(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift new file mode 100644 index 0000000..4522d53 --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift @@ -0,0 +1,35 @@ +// +// NameAndPreferenceStack.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/10/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NameAndPreferenceStack: View { + @Environment(\.modelContext) var modelContext + + let contact: SelectedContact + + var body: some View { + VStack(alignment: .center, spacing: 10) { + Text(contact.name) + .font(.largeTitle) + .bold() + + HStack(spacing: 0) { + Text("Preference: ") + .foregroundColor(.gray) + Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) + .foregroundColor(.gray) + } + } + .padding(.bottom, 5) + } +} + +#Preview { + NameAndPreferenceStack(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift deleted file mode 100644 index 2aea904..0000000 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NamePreferenceChangeStack.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// NamePreferenceChangeStack.swift -// CatchUp-SwiftUI -// -// Created by Ryan Token on 3/10/24. -// Copyright © 2024 Token Solutions. All rights reserved. -// - -import SwiftUI - -struct NamePreferenceChangeStack: View { - @Environment(\.modelContext) var modelContext - - let contact: SelectedContact - @Binding var isShowingPreferenceScreen: Bool - - var body: some View { - VStack(alignment: .center, spacing: 10) { - Text(contact.name) - .font(.largeTitle) - .bold() - - HStack(spacing: 0) { - Text("Preference: ") - .foregroundColor(.gray) - Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) - .foregroundColor(.gray) - } - - Button { - isShowingPreferenceScreen = true - } label: { - Text("Change Notification Preference") - .font(.headline) - .foregroundColor(.orange) - } - .sheet( - isPresented: $isShowingPreferenceScreen, - onDismiss: { - NotificationHelper.removeExistingNotifications(for: contact) - NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) - } - ) { - PreferenceScreen(contact: contact) - } - } - } -} - -#Preview { - NamePreferenceChangeStack(contact: SelectedContact.sampleData, isShowingPreferenceScreen: .constant(false)) -} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift new file mode 100644 index 0000000..86e1e4a --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -0,0 +1,123 @@ +// +// NotificationPreferenceView.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/29/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NotificationPreferenceView: View { + @Environment(\.modelContext) var modelContext + + @State private var notificationPreferenceTime = Date() + @State private var notificationPreferenceCustomDate = Date() + @State private var whatDayText = "" + + @Bindable var contact: SelectedContact + + let notificationOptions = ["Never", "Daily", "Weekly", "Monthly", "Custom"] + let dayOptions = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + + var body: some View { + Group { + Picker(selection: $contact.notification_preference, label: Text("How often?")) { + ForEach(0.. Date: Fri, 29 Mar 2024 22:53:24 -0600 Subject: [PATCH 26/46] Fix mailto links --- .../Utilities/NotificationHelper.swift | 17 ++++++++--------- .../DetailScreen Subviews/ContactInfoView.swift | 5 +++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index c8b02b9..99242ba 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -22,11 +22,11 @@ struct NotificationHelper { } if ContactHelper.contactHasBirthday(contact) { - addBirthdayNotification(for: contact, modelContext: modelContext) + addBirthdayNotification(for: contact) } if ContactHelper.contactHasAnniversary(contact) { - addAnniversaryNotification(for: contact, modelContext: modelContext) + addAnniversaryNotification(for: contact) } } @@ -51,10 +51,10 @@ struct NotificationHelper { try? modelContext.save() } - scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent, modelContext: modelContext) + scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent) } - static func addBirthdayNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addBirthdayNotification(for contact: SelectedContact) { let birthdayNotificationContent = UNMutableNotificationContent() birthdayNotificationContent.title = "🥳 Today is \(contact.name)'s birthday!" birthdayNotificationContent.body = "Be sure to CatchUp and wish them a great one!" @@ -64,10 +64,10 @@ struct NotificationHelper { let birthdayIdentifier = UUID() let birthdayDateComponents = getBirthdayDateComponents(for: contact) - scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent, modelContext: modelContext) + scheduleNotification(for: contact, dateComponents: birthdayDateComponents, identifier: birthdayIdentifier, content: birthdayNotificationContent) } - static func addAnniversaryNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addAnniversaryNotification(for contact: SelectedContact) { let anniversaryNotificationContent = UNMutableNotificationContent() anniversaryNotificationContent.title = "😍 Tomorrow is \(contact.name)'s anniversary!" anniversaryNotificationContent.body = "Be sure to CatchUp and wish them the best." @@ -77,7 +77,7 @@ struct NotificationHelper { let anniversaryIdentifier = UUID() let anniversaryDateComponents = getAnniversaryDateComponents(for: contact) - scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent, modelContext: modelContext) + scheduleNotification(for: contact, dateComponents: anniversaryDateComponents, identifier: anniversaryIdentifier, content: anniversaryNotificationContent) } static func checkNotificationAuthorizationStatusAndAddRequest(action: @escaping() -> Void) { @@ -167,7 +167,7 @@ struct NotificationHelper { return anniversaryDateComponents } - static func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent, modelContext: ModelContext) { + static func scheduleNotification(for contact: SelectedContact, dateComponents: DateComponents, identifier: UUID, content: UNMutableNotificationContent) { let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let request = UNNotificationRequest(identifier: identifier.uuidString, content: content, trigger: trigger) @@ -181,7 +181,6 @@ struct NotificationHelper { } print("scheduling notification for contact: \(contact.name)") - try? modelContext.save() UNUserNotificationCenter.current().add(request) } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift index 98ae23f..1cf8138 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift @@ -28,11 +28,11 @@ struct ContactInfoView: View { } var tappablePrimaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.email) + Converter.getTappableEmail(from: contact.email) } var tappableSecondaryEmail: URL { - Converter.getTappablePhoneNumber(from: contact.secondary_email) + Converter.getTappableEmail(from: contact.secondary_email) } var body: some View { @@ -64,6 +64,7 @@ struct ContactInfoView: View { .font(.caption) Button(contact.email) { + print("tapping email \(tappablePrimaryEmail)") UIApplication.shared.open(tappablePrimaryEmail) } .foregroundColor(.blue) From 084ad84feaa89f07c8b1145db959019a84e7c0d3 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 30 Mar 2024 00:01:17 -0600 Subject: [PATCH 27/46] Added 'Next CatchUp' to each person's detail screen; Fixed mailto links; Removed a bunch of unnecessary modelContext objects --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 +++ CatchUp-SwiftUI/Utilities/ContactHelper.swift | 22 +++++++++++++ .../Utilities/NotificationHelper.swift | 32 ++++++------------- CatchUp-SwiftUI/Views/AboutScreen.swift | 10 +++--- .../ContactInfoView.swift | 31 ++++++++++++++---- .../NameAndPreferenceStack.swift | 9 +++--- .../NextCatchUpRow.swift | 28 ++++++++++++++++ .../NotificationPreferenceView.swift | 15 +++++++-- CatchUp-SwiftUI/Views/DetailScreen.swift | 12 +++++-- .../HomeScreen Subviews/ContactRowView.swift | 2 +- .../NextCatchUpsGridView.swift | 24 +------------- .../OpenContactPickerButtonView.swift | 2 +- CatchUp-SwiftUI/Views/HomeScreen.swift | 4 +-- CatchUp-SwiftUI/Views/UpdatesScreen.swift | 6 ++-- 14 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 9bdedf9..01349ac 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ F4118B4D2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */; }; F4118B4F2B9EA168001BC8C7 /* ContactInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */; }; F435D42F2BB78A6F00C43586 /* NotificationPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */; }; + F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -46,6 +47,7 @@ F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameAndPreferenceStack.swift; sourceTree = ""; }; F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInfoView.swift; sourceTree = ""; }; F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferenceView.swift; sourceTree = ""; }; + F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpRow.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -112,6 +114,7 @@ F4118B4C2B9EA11A001BC8C7 /* NameAndPreferenceStack.swift */, F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */, F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */, + F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */, ); path = "DetailScreen Subviews"; sourceTree = ""; @@ -290,6 +293,7 @@ F4AD59B0244CA12C00296568 /* AboutScreen.swift in Sources */, F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, + F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 8034fb0..554b8d0 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -231,6 +231,28 @@ struct ContactHelper { return anniversaryString } + static func getFriendlyNextCatchUpTime(for contact: SelectedContact) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + if let date = dateFormatter.date(from: contact.next_notification_date_time) { + let friendlyFormatter = DateFormatter() + + if Calendar.current.isDateInToday(date) { + friendlyFormatter.dateFormat = "h:mm a" + return "Today at \(friendlyFormatter.string(from: date))" + } else if Calendar.current.isDateInTomorrow(date) { + friendlyFormatter.dateFormat = "h:mm a" + return "Tomorrow at \(friendlyFormatter.string(from: date))" + } else { + friendlyFormatter.dateFormat = "MMMM d 'at' h:mm a" + return friendlyFormatter.string(from: date) + } + } else { + return "Unknown" + } + } + static func createSelectedContact(contact: CNContact) -> SelectedContact { let currentMinute = Calendar.current.component(.minute, from: Date()) let currentHour = Calendar.current.component(.hour, from: Date()) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 99242ba..7b81318 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -13,12 +13,12 @@ import CoreData struct NotificationHelper { @MainActor - static func createNewNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func createNewNotification(for contact: SelectedContact) { updateNextNotificationDateTimeFor(contact: contact) let addRequest = { if preferenceIsNotSetToNever(for: contact) { - addGeneralNotification(for: contact, modelContext: modelContext) + addGeneralNotification(for: contact) } if ContactHelper.contactHasBirthday(contact) { @@ -37,7 +37,7 @@ struct NotificationHelper { return contact.notification_preference != 0 ? true : false } - static func addGeneralNotification(for contact: SelectedContact, modelContext: ModelContext) { + static func addGeneralNotification(for contact: SelectedContact) { let notificationContent = UNMutableNotificationContent() notificationContent.title = "👋 CatchUp with \(contact.name)" notificationContent.body = generateRandomNotificationBody() @@ -46,10 +46,6 @@ struct NotificationHelper { let identifier = UUID() let dateComponents = getNotificationDateComponents(for: contact) - if let weekOfMonth = dateComponents.weekOfMonth { - contact.notification_preference_week_of_month = weekOfMonth - try? modelContext.save() - } scheduleNotification(for: contact, dateComponents: dateComponents, identifier: identifier, content: notificationContent) } @@ -117,7 +113,7 @@ struct NotificationHelper { dateComponents.hour = Int(contact.notification_preference_hour) dateComponents.minute = Int(contact.notification_preference_minute) dateComponents.weekday = Int(contact.notification_preference_weekday)+1 - dateComponents.weekOfMonth = Int.random(in: 2..<5) + dateComponents.weekOfMonth = Int(contact.notification_preference_week_of_month) break case 4: // Custom Date dateComponents.month = Int(contact.notification_preference_custom_month) @@ -234,38 +230,30 @@ struct NotificationHelper { } } - static func updateNotificationPreference(for contact: SelectedContact, selection: Int, modelContext: ModelContext) { + static func updateNotificationPreference(for contact: SelectedContact, selection: Int) { let newPreference = selection contact.notification_preference = newPreference - - try? modelContext.save() } - static func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int, modelContext: ModelContext) { + static func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int) { let newHour = hour let newMinute = minute contact.notification_preference_hour = newHour contact.notification_preference_minute = newMinute - - try? modelContext.save() } - static func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int, modelContext: ModelContext) { + static func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int) { let newWeekday = weekday contact.notification_preference_weekday = newWeekday - - try? modelContext.save() } - static func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int, modelContext: ModelContext) { + static func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int) { let customMonth = month let customDay = day let customYear = year contact.notification_preference_custom_month = customMonth contact.notification_preference_custom_day = customDay contact.notification_preference_custom_year = customYear - - try? modelContext.save() } static func requestAuthorizationForNotifications() { @@ -377,13 +365,13 @@ struct NotificationHelper { } @MainActor - static func resetNotifications(for selectedContacts: [SelectedContact], modelContext: ModelContext) { + static func resetNotifications(for selectedContacts: [SelectedContact]) { DispatchQueue.main.asyncAfter(deadline: .now() + 3) { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() for contact in selectedContacts { if contact.notification_preference != 0 { - NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + NotificationHelper.createNewNotification(for: contact) } } } diff --git a/CatchUp-SwiftUI/Views/AboutScreen.swift b/CatchUp-SwiftUI/Views/AboutScreen.swift index c2924a0..04aed85 100644 --- a/CatchUp-SwiftUI/Views/AboutScreen.swift +++ b/CatchUp-SwiftUI/Views/AboutScreen.swift @@ -28,7 +28,7 @@ struct AboutScreen: View { .shadow(radius: 10) Text("CatchUp") - .foregroundColor(.orange) + .foregroundStyle(.orange) .font(.largeTitle) .bold() @@ -59,7 +59,7 @@ struct AboutScreen: View { tappedSmallTip() } .font(.headline) - .foregroundColor(.white) + .foregroundStyle(.white) .padding() .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) ) @@ -71,7 +71,7 @@ struct AboutScreen: View { tappedMediumTip() } .font(.headline) - .foregroundColor(.white) + .foregroundStyle(.white) .padding() .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) ) @@ -83,7 +83,7 @@ struct AboutScreen: View { tappedLargeTip() } .font(.headline) - .foregroundColor(.white) + .foregroundStyle(.white) .padding() .background(RoundedRectangle(cornerRadius: 20).fill(LinearGradient(gradient: Gradient(colors: [.orange, .red]), startPoint: .top, endPoint: .bottom)) ) @@ -108,7 +108,7 @@ struct AboutScreen: View { } label: { Text("Show Latest Update Details") .font(.headline) - .foregroundColor(.blue) + .foregroundStyle(.blue) } } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift index 1cf8138..7f58bc2 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift @@ -9,6 +9,10 @@ import SwiftUI struct ContactInfoView: View { + @State private var isShowingEmailAlert = false + @State private var emailString = "" + @State private var emailUrlForAlert: URL? + let contact: SelectedContact var formattedPrimaryPhoneNumber: String { @@ -44,7 +48,7 @@ struct ContactInfoView: View { Button(formattedPrimaryPhoneNumber) { UIApplication.shared.open(tappablePrimaryPhoneNumber) } - .foregroundColor(.blue) + .foregroundStyle(.blue) } } if ContactHelper.contactHasSecondaryPhone(contact) { @@ -55,7 +59,7 @@ struct ContactInfoView: View { Button(formattedSecondaryPhoneNumber) { UIApplication.shared.open(tappableSecondaryPhoneNumber) } - .foregroundColor(.blue) + .foregroundStyle(.blue) } } if ContactHelper.contactHasEmail(contact) { @@ -64,10 +68,21 @@ struct ContactInfoView: View { .font(.caption) Button(contact.email) { - print("tapping email \(tappablePrimaryEmail)") - UIApplication.shared.open(tappablePrimaryEmail) + emailString = contact.email + emailUrlForAlert = tappablePrimaryEmail + isShowingEmailAlert = true } - .foregroundColor(.blue) + .foregroundStyle(.blue) + } + + .alert("Email \(emailString)?", isPresented: $isShowingEmailAlert) { + if let emailUrlForAlert { + Button("Yes") { + UIApplication.shared.open(emailUrlForAlert) + } + } + + Button("Cancel", role: .cancel) {} } } if ContactHelper.contactHasSecondaryEmail(contact) { @@ -76,9 +91,11 @@ struct ContactInfoView: View { .font(.caption) Button(contact.secondary_email) { - UIApplication.shared.open(tappableSecondaryEmail) + emailString = contact.secondary_email + emailUrlForAlert = tappableSecondaryEmail + isShowingEmailAlert = true } - .foregroundColor(.blue) + .foregroundStyle(.blue) } } if ContactHelper.contactHasAddress(contact) { diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift index 4522d53..5239f79 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NameAndPreferenceStack.swift @@ -9,8 +9,6 @@ import SwiftUI struct NameAndPreferenceStack: View { - @Environment(\.modelContext) var modelContext - let contact: SelectedContact var body: some View { @@ -21,9 +19,10 @@ struct NameAndPreferenceStack: View { HStack(spacing: 0) { Text("Preference: ") - .foregroundColor(.gray) - Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) - .foregroundColor(.gray) + .foregroundStyle(.gray) + + Text(Converter.convertNotificationPreferenceIntToString(preference: contact.notification_preference, contact: contact)) + .foregroundStyle(.gray) } } .padding(.bottom, 5) diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift new file mode 100644 index 0000000..46b44ba --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift @@ -0,0 +1,28 @@ +// +// NextCatchUpRow.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/29/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NextCatchUpRow: View { + let nextCatchUpTime: String + + var body: some View { + HStack { + Text("Next CatchUp:") + + Spacer() + + Text(nextCatchUpTime) + .foregroundStyle(.gray) + } + } +} + +#Preview { + NextCatchUpRow() +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift index 86e1e4a..59a9060 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -89,19 +89,30 @@ struct NotificationPreferenceView: View { } else if contact.notification_preference == 3 { // monthly whatDayText = "What day? We'll pick a random week." } + + NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + } + + .onChange(of: contact.notification_preference_weekday) { + contact.notification_preference_week_of_month = .random(in: 1..<5) + NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) } .onChange(of: notificationPreferenceTime) { initialTime, newTime in let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute], from : newTime) - NotificationHelper.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!, modelContext: modelContext) + NotificationHelper.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!) + + NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) } .onChange(of: notificationPreferenceCustomDate) { initialDate, newDate in let year = Calendar.current.component(.year, from: newDate) let month = Calendar.current.component(.month, from: newDate) let day = Calendar.current.component(.day, from: newDate) - NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year, modelContext: modelContext) + NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year) + + NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 2e03d66..30f5756 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -9,10 +9,13 @@ import SwiftUI struct DetailScreen: View { - @Environment(\.modelContext) var modelContext - @Bindable var contact: SelectedContact + var nextCatchUpTime: String { + print("recalculating nextCatchUpTime") + return ContactHelper.getFriendlyNextCatchUpTime(for: contact) + } + var body: some View { VStack { GradientView() @@ -26,6 +29,9 @@ struct DetailScreen: View { NameAndPreferenceStack(contact: contact) List { + Section { + NextCatchUpRow(nextCatchUpTime: nextCatchUpTime) + } Section("Notification Preference") { NotificationPreferenceView(contact: contact) } @@ -40,7 +46,7 @@ struct DetailScreen: View { .onDisappear { NotificationHelper.removeExistingNotifications(for: contact) - NotificationHelper.createNewNotification(for: contact, modelContext: modelContext) + NotificationHelper.createNewNotification(for: contact) } .navigationBarTitleDisplayMode(.inline) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index 6c2a885..720d8e7 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -20,7 +20,7 @@ struct ContactRowView: View { .font(.headline) Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) .font(.caption) - .foregroundColor(.gray) + .foregroundStyle(.gray) } } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index 56d540f..5279ca2 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -32,7 +32,7 @@ struct NextCatchUpsGridView: View { Text(contact.name.components(separatedBy: " ").first ?? contact.name) .font(.headline) - Text(friendlyNextCatchUpTime(for: contact)) + Text(ContactHelper.getFriendlyNextCatchUpTime(for: contact)) .foregroundStyle(.gray) .font(.caption) } @@ -55,28 +55,6 @@ struct NextCatchUpsGridView: View { .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0)) .listRowBackground(Color.clear) } - - func friendlyNextCatchUpTime(for contact: SelectedContact) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - if let date = dateFormatter.date(from: contact.next_notification_date_time) { - let friendlyFormatter = DateFormatter() - - if Calendar.current.isDateInToday(date) { - friendlyFormatter.dateFormat = "h:mm a" - return "Today at \(friendlyFormatter.string(from: date))" - } else if Calendar.current.isDateInTomorrow(date) { - friendlyFormatter.dateFormat = "h:mm a" - return "Tomorrow at \(friendlyFormatter.string(from: date))" - } else { - friendlyFormatter.dateFormat = "MMMM d 'at' h:mm a" - return friendlyFormatter.string(from: date) - } - } else { - return "Unknown" - } - } } #Preview { diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift index 7e13a59..dcd90b9 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/OpenContactPickerButtonView.swift @@ -16,7 +16,7 @@ struct OpenContactPickerButtonView: View { Text("Add Contacts") } .font(.headline) - .foregroundColor(.blue) + .foregroundStyle(.blue) .padding(.top, 10) } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index c49ffe2..8238256 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -84,7 +84,7 @@ struct HomeScreen : View { isShowingAboutSheet = true } label: { Image(systemName: "person.crop.square") - .foregroundColor(.blue) + .foregroundStyle(.blue) } .sheet(isPresented: $isShowingAboutSheet) { AboutScreen() @@ -108,7 +108,7 @@ struct HomeScreen : View { if isColdLaunch { print("resetting notifications") - NotificationHelper.resetNotifications(for: selectedContacts, modelContext: modelContext) + NotificationHelper.resetNotifications(for: selectedContacts) } } diff --git a/CatchUp-SwiftUI/Views/UpdatesScreen.swift b/CatchUp-SwiftUI/Views/UpdatesScreen.swift index 8ba4be2..5ba91c4 100644 --- a/CatchUp-SwiftUI/Views/UpdatesScreen.swift +++ b/CatchUp-SwiftUI/Views/UpdatesScreen.swift @@ -20,11 +20,11 @@ struct UpdatesScreen: View { Text("New Update") .font(.largeTitle) .bold() - .foregroundColor(.orange) - + .foregroundStyle(.orange) + Text("Version \(Utils.getCurrentAppVersion())") .font(.headline) - .foregroundColor(.blue) + .foregroundStyle(.blue) Text("Release Notes:") From cd0454510bf0d2b5163acb8153edd7b65aae372f Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 30 Mar 2024 18:43:53 -0600 Subject: [PATCH 28/46] Added a 'remove contact' button on DetailScreen --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 ++ .../NextCatchUpRow.swift | 2 +- .../RemoveContactButton.swift | 60 +++++++++++++++++++ CatchUp-SwiftUI/Views/DetailScreen.swift | 2 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/RemoveContactButton.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 01349ac..9273982 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ F4118B4F2B9EA168001BC8C7 /* ContactInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */; }; F435D42F2BB78A6F00C43586 /* NotificationPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */; }; F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */; }; + F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -48,6 +49,7 @@ F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInfoView.swift; sourceTree = ""; }; F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferenceView.swift; sourceTree = ""; }; F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpRow.swift; sourceTree = ""; }; + F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveContactButton.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -115,6 +117,7 @@ F4118B4E2B9EA168001BC8C7 /* ContactInfoView.swift */, F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */, F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */, + F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */, ); path = "DetailScreen Subviews"; sourceTree = ""; @@ -294,6 +297,7 @@ F4BAD42D2B94E45D0009CD50 /* SelectedContact.swift in Sources */, F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */, + F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift index 46b44ba..53ffea4 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift @@ -24,5 +24,5 @@ struct NextCatchUpRow: View { } #Preview { - NextCatchUpRow() + NextCatchUpRow(nextCatchUpTime: "April 3 at 7:15 AM") } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/RemoveContactButton.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/RemoveContactButton.swift new file mode 100644 index 0000000..7b1a2fd --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/RemoveContactButton.swift @@ -0,0 +1,60 @@ +// +// RemoveContactButton.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/30/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct RemoveContactButton: View { + @Environment(\.modelContext) var modelContext + @Environment(\.dismiss) var dismiss + + @State private var isShowingDeleteContactAlert = false + @Bindable var contact: SelectedContact + + var body: some View { + Button { + isShowingDeleteContactAlert = true + } label: { + HStack { + Spacer() + + Text("Remove \(contact.name)") + + Spacer() + } + .fontWeight(.semibold) + .padding(.vertical, 12) + .foregroundStyle(.white) + .background( + RoundedRectangle( + cornerRadius: 20, + style: .continuous + ) + .fill(.red) + ) + } + .listRowBackground(Color.clear) + + .alert("Remove \(contact.name)?", isPresented: $isShowingDeleteContactAlert) { + Button("Remove", role: .destructive) { + deleteContactAndDismiss() + } + + Button("Cancel", role: .cancel) {} + } + } + + func deleteContactAndDismiss() { + NotificationHelper.removeExistingNotifications(for: contact) + modelContext.delete(contact) + dismiss() + } +} + +#Preview { + RemoveContactButton(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 30f5756..573e083 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -38,6 +38,8 @@ struct DetailScreen: View { Section("Contact Information") { ContactInfoView(contact: contact) } + + RemoveContactButton(contact: contact) } } .onAppear { From efcef2877bce79df68add992712915f622ed6d06 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 30 Mar 2024 18:49:23 -0600 Subject: [PATCH 29/46] Open addresses in Maps --- .../ContactInfoView.swift | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift index 7f58bc2..90247a4 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/ContactInfoView.swift @@ -6,6 +6,7 @@ // Copyright © 2024 Token Solutions. All rights reserved. // +import MapKit import SwiftUI struct ContactInfoView: View { @@ -102,14 +103,20 @@ struct ContactInfoView: View { VStack(alignment: .leading, spacing: 3) { Text("Address") .font(.caption) - Text(contact.address) + Button(contact.address) { + openAddressInMaps(address: contact.address) + } + .foregroundStyle(.blue) } } if ContactHelper.contactHasSecondaryAddress(contact) { VStack(alignment: .leading, spacing: 3) { Text("Secondary Address") .font(.caption) - Text(contact.secondary_address) + Button(contact.secondary_address) { + openAddressInMaps(address: contact.secondary_address) + } + .foregroundStyle(.blue) } } if ContactHelper.contactHasBirthday(contact) { @@ -127,6 +134,26 @@ struct ContactInfoView: View { } } } + + func openAddressInMaps(address: String){ + let geocoder = CLGeocoder() + geocoder.geocodeAddressString(address) { (placemarks, error) in + guard let placemarks = placemarks?.first else { + return + } + + let location = placemarks.location?.coordinate + + if let lat = location?.latitude, let long = location?.longitude{ + let destination = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: lat, longitude: long))) + destination.name = address + + MKMapItem.openMaps( + with: [destination] + ) + } + } + } } #Preview { From 44887df37eedf7b02f8e37deb1ea7ded9e894769 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 30 Mar 2024 18:55:55 -0600 Subject: [PATCH 30/46] Add request for review after the 5th time the user cold launches the app --- CatchUp-SwiftUI/Views/HomeScreen.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 8238256..de0577a 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -6,18 +6,21 @@ // Copyright © 2019 Token Solutions. All rights reserved. // +import ContactsUI +import StoreKit import SwiftData import SwiftUI -import ContactsUI struct HomeScreen : View { @Environment(\.modelContext) var modelContext @Environment(\.scenePhase) var scenePhase + @Environment(\.requestReview) var requestReview @Query(sort: \SelectedContact.name) var selectedContacts: [SelectedContact] @Query(sort: \SelectedContact.next_notification_date_time) var nextCatchups: [SelectedContact] @AppStorage("savedVersion") var savedVersion = "2.0.0" + @AppStorage("timesUserHasLaunchedApp") var timesUserHasLaunchedApp = 0 @State private var isColdLaunch = true @State private var isShowingUpdatesSheet = false @@ -102,13 +105,16 @@ struct HomeScreen : View { .accentColor(.orange) .onAppear { - print("sqlite path: \(modelContext.sqliteCommand)") clearNotificationBadgeAndCheckForUpdate() NotificationHelper.requestAuthorizationForNotifications() + if timesUserHasLaunchedApp > 5 { + requestReview() + } if isColdLaunch { print("resetting notifications") NotificationHelper.resetNotifications(for: selectedContacts) + timesUserHasLaunchedApp += 1 } } From 1e99395740b70c1cbffaf697e06b6a7b51b0cf0f Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sat, 30 Mar 2024 18:56:39 -0600 Subject: [PATCH 31/46] Only request after cold launches --- CatchUp-SwiftUI/Views/HomeScreen.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index de0577a..fdbf185 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -107,11 +107,12 @@ struct HomeScreen : View { .onAppear { clearNotificationBadgeAndCheckForUpdate() NotificationHelper.requestAuthorizationForNotifications() - if timesUserHasLaunchedApp > 5 { - requestReview() - } if isColdLaunch { + if timesUserHasLaunchedApp > 5 { + requestReview() + } + print("resetting notifications") NotificationHelper.resetNotifications(for: selectedContacts) timesUserHasLaunchedApp += 1 From f08c63c870dc36f7ade897f1cc28e00bb64e8c6a Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 31 Mar 2024 19:34:39 -0600 Subject: [PATCH 32/46] Birthday or anniversary notification indicator for next catchup --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 ++ CatchUp-SwiftUI/Utilities/ContactHelper.swift | 4 ++ .../BirthdayOrAnniversaryRow.swift | 53 +++++++++++++++++++ .../NextCatchUpRow.swift | 1 + CatchUp-SwiftUI/Views/DetailScreen.swift | 1 + .../NextCatchUpsGridView.swift | 2 +- 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 9273982..761d915 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ F435D42F2BB78A6F00C43586 /* NotificationPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */; }; F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */; }; F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */; }; + F435D4352BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -50,6 +51,7 @@ F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferenceView.swift; sourceTree = ""; }; F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpRow.swift; sourceTree = ""; }; F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveContactButton.swift; sourceTree = ""; }; + F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BirthdayOrAnniversaryRow.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -118,6 +120,7 @@ F435D42E2BB78A6F00C43586 /* NotificationPreferenceView.swift */, F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */, F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */, + F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */, ); path = "DetailScreen Subviews"; sourceTree = ""; @@ -298,6 +301,7 @@ F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */, F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */, F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */, + F435D4352BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 554b8d0..6dc3932 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -231,6 +231,10 @@ struct ContactHelper { return anniversaryString } + static func getFirstName(for contact: SelectedContact) -> String { + contact.name.components(separatedBy: " ").first ?? contact.name + } + static func getFriendlyNextCatchUpTime(for contact: SelectedContact) -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift new file mode 100644 index 0000000..06d0256 --- /dev/null +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift @@ -0,0 +1,53 @@ +// +// BirthdayOrAnniversaryRow.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/31/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct BirthdayOrAnniversaryRow: View { + let contact: SelectedContact + + var body: some View { + if contact.next_notification_date_time.contains(contact.birthday) { + VStack { + HStack { + Spacer() + Text("🥳 \(ContactHelper.getFirstName(for: contact))'s birthday!") + .foregroundStyle(.orange) + Spacer() + } + .padding(.top, 2) + + Spacer() + } + } else if contact.next_notification_date_time == dayBeforeAnniversaryString() { + VStack { + HStack { + Spacer() + Text("🧡 The day before your anniversary!") + .foregroundStyle(.orange) + Spacer() + } + .padding(.top, 2) + + Spacer() + } + } + } + + func dayBeforeAnniversaryString() -> String { + if ContactHelper.contactHasAnniversary(contact) { + return NotificationHelper.calculateDateFromComponents(NotificationHelper.getAnniversaryDateComponents(for: contact)) + } + + return "" + } +} + +#Preview { + BirthdayOrAnniversaryRow(contact: SelectedContact.sampleData) +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift index 53ffea4..e1ea123 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NextCatchUpRow.swift @@ -20,6 +20,7 @@ struct NextCatchUpRow: View { Text(nextCatchUpTime) .foregroundStyle(.gray) } + .listRowSeparator(.hidden) } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 573e083..6311ee1 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -31,6 +31,7 @@ struct DetailScreen: View { List { Section { NextCatchUpRow(nextCatchUpTime: nextCatchUpTime) + BirthdayOrAnniversaryRow(contact: contact) } Section("Notification Preference") { NotificationPreferenceView(contact: contact) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index 5279ca2..dbfd80d 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -29,7 +29,7 @@ struct NextCatchUpsGridView: View { .padding(.trailing, 5) VStack(alignment: .leading, spacing: 2) { - Text(contact.name.components(separatedBy: " ").first ?? contact.name) + Text(ContactHelper.getFirstName(for: contact)) .font(.headline) Text(ContactHelper.getFriendlyNextCatchUpTime(for: contact)) From 0de9721c48f7654935b1b357fad34189e7eb416c Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 31 Mar 2024 19:39:17 -0600 Subject: [PATCH 33/46] request review 33% of the time after the user has launched the app 5 times --- CatchUp-SwiftUI/Views/HomeScreen.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index fdbf185..a5b3c2a 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -109,7 +109,7 @@ struct HomeScreen : View { NotificationHelper.requestAuthorizationForNotifications() if isColdLaunch { - if timesUserHasLaunchedApp > 5 { + if timesUserHasLaunchedApp > 5 && Int.random(in: 1...3) == 2 { requestReview() } From 24aace0f187c40adc8e606d3d30336d702e5007f Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 31 Mar 2024 20:03:10 -0600 Subject: [PATCH 34/46] Updated the Updates screen --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 +-- CatchUp-SwiftUI/Views/AboutScreen.swift | 6 ++-- CatchUp-SwiftUI/Views/HomeScreen.swift | 36 +++++++++++++---------- CatchUp-SwiftUI/Views/UpdatesScreen.swift | 25 +++++++++------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 761d915..1a23ff5 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -456,7 +456,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.3; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.tokensolutions.CatchUp; PRODUCT_NAME = CatchUp; SUPPORTS_MACCATALYST = NO; @@ -484,7 +484,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.3; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.tokensolutions.CatchUp; PRODUCT_NAME = CatchUp; SUPPORTS_MACCATALYST = NO; diff --git a/CatchUp-SwiftUI/Views/AboutScreen.swift b/CatchUp-SwiftUI/Views/AboutScreen.swift index 04aed85..8c0be96 100644 --- a/CatchUp-SwiftUI/Views/AboutScreen.swift +++ b/CatchUp-SwiftUI/Views/AboutScreen.swift @@ -9,7 +9,7 @@ import SwiftUI struct AboutScreen: View { - @State private var showingUpdateScreen = false + @State private var isShowingUpdateScreen = false let smallTip = IAPService.shared.getSmallTipAmount() let mediumTip = IAPService.shared.getMediumTipAmount() @@ -104,7 +104,7 @@ struct AboutScreen: View { Group { Button { - showingUpdateScreen = true + isShowingUpdateScreen = true } label: { Text("Show Latest Update Details") .font(.headline) @@ -114,7 +114,7 @@ struct AboutScreen: View { } .padding() - .sheet(isPresented: $showingUpdateScreen) { + .sheet(isPresented: $isShowingUpdateScreen) { UpdatesScreen() } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index a5b3c2a..c1f6c01 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -74,24 +74,24 @@ struct HomeScreen : View { } label: { OpenContactPickerButtonView() } + } + .navigationBarTitle("CatchUp") + + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + .foregroundStyle(.blue) + } - .navigationBarTitle("CatchUp") - .toolbar { - ToolbarItem(placement: .topBarLeading) { - EditButton() + ToolbarItem(placement: .topBarTrailing) { + Button { + isShowingAboutSheet = true + } label: { + Image(systemName: "person.crop.square") .foregroundStyle(.blue) } - - ToolbarItem(placement: .topBarTrailing) { - Button { - isShowingAboutSheet = true - } label: { - Image(systemName: "person.crop.square") - .foregroundStyle(.blue) - } - .sheet(isPresented: $isShowingAboutSheet) { - AboutScreen() - } + .sheet(isPresented: $isShowingAboutSheet) { + AboutScreen() } } } @@ -101,6 +101,10 @@ struct HomeScreen : View { DetailScreen(contact: tappedGridContact) } } + + .sheet(isPresented: $isShowingUpdatesSheet) { + UpdatesScreen() + } } .accentColor(.orange) @@ -190,7 +194,7 @@ struct HomeScreen : View { if savedVersion == version { print("App is up to date!") } else { - if Utils.updateIsMajor() { + if Utils.updateIsMajor() && timesUserHasLaunchedApp > 0 { // Toggle to show UpdatesScreen as a sheet print("Major update detected, showing UpdatesScreen...") isShowingUpdatesSheet = true diff --git a/CatchUp-SwiftUI/Views/UpdatesScreen.swift b/CatchUp-SwiftUI/Views/UpdatesScreen.swift index 5ba91c4..0ebbe81 100644 --- a/CatchUp-SwiftUI/Views/UpdatesScreen.swift +++ b/CatchUp-SwiftUI/Views/UpdatesScreen.swift @@ -13,9 +13,8 @@ struct UpdatesScreen: View { ScrollView { VStack(alignment: .leading, spacing: 10) { Group { - Spacer() - .frame(height: 45) + .frame(height: 10) Text("New Update") .font(.largeTitle) @@ -25,26 +24,32 @@ struct UpdatesScreen: View { Text("Version \(Utils.getCurrentAppVersion())") .font(.headline) .foregroundStyle(.blue) - Text("Release Notes:") .font(.headline) Divider() Spacer() - } Group { - Text("– iOS 15 compatibility") - + Text("– A grid of your next CatchUps") + + Spacer() + + Text("– Pull-to-refresh photo & contact information for your selected contacts") + + Spacer() + + Text("– Automatic cloud syncing with other Apple devices") + Spacer() - - Text("– CatchUp now requires iOS 14 or newer") - + + Text("– UI redesign") + Spacer() - Text("– More to come soon!") + Text("– Significant under-the-hood improvements") } Spacer() From a23767b5cada178b3552223cbb6bdaf7dba363c3 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Sun, 31 Mar 2024 20:25:31 -0600 Subject: [PATCH 35/46] Update to NavigationSplitView and make sure the mac app works alright --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 + CatchUp-SwiftUI/CatchUpApp.swift | 11 ++- CatchUp-SwiftUI/Data/DataController.swift | 14 +++ CatchUp-SwiftUI/Views/DetailScreen.swift | 3 + CatchUp-SwiftUI/Views/HomeScreen.swift | 108 +++++++++++----------- 5 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 CatchUp-SwiftUI/Data/DataController.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index 1a23ff5..ae35ab6 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */; }; F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */; }; F435D4352BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */; }; + F435D4372BBA4EC600C43586 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4362BBA4EC600C43586 /* DataController.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -52,6 +53,7 @@ F435D4302BB7E1D300C43586 /* NextCatchUpRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCatchUpRow.swift; sourceTree = ""; }; F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveContactButton.swift; sourceTree = ""; }; F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BirthdayOrAnniversaryRow.swift; sourceTree = ""; }; + F435D4362BBA4EC600C43586 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -199,6 +201,7 @@ F4BAD4292B94E41F0009CD50 /* Data */ = { isa = PBXGroup; children = ( + F435D4362BBA4EC600C43586 /* DataController.swift */, F4BAD42C2B94E45D0009CD50 /* SelectedContact.swift */, ); path = Data; @@ -302,6 +305,7 @@ F435D4312BB7E1D300C43586 /* NextCatchUpRow.swift in Sources */, F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */, F435D4352BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift in Sources */, + F435D4372BBA4EC600C43586 /* DataController.swift in Sources */, F4AD59AE244C9FF600296568 /* IAPService.swift in Sources */, F4F7535F2BB0969A00B20090 /* ContactPictureView.swift in Sources */, F4BAD4312B94F5680009CD50 /* ModelContext+sqliteCommand.swift in Sources */, diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index e6451ad..8efc20f 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -15,6 +15,8 @@ struct CatchUpApp: App { let url = URL.applicationSupportDirectory.appending(path: "CatchUp-SwiftUI.sqlite") let modelContainer: ModelContainer + @State private var dataController = DataController() + init() { do { modelContainer = try ModelContainer( @@ -27,8 +29,15 @@ struct CatchUpApp: App { var body: some Scene { WindowGroup { - HomeScreen() + NavigationSplitView { + HomeScreen() + } detail: { + if let selectedContact = dataController.selectedContact { + DetailScreen(contact: selectedContact) + } + } } .modelContainer(modelContainer) + .environment(dataController) } } diff --git a/CatchUp-SwiftUI/Data/DataController.swift b/CatchUp-SwiftUI/Data/DataController.swift new file mode 100644 index 0000000..efb02d0 --- /dev/null +++ b/CatchUp-SwiftUI/Data/DataController.swift @@ -0,0 +1,14 @@ +// +// DataController.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 3/31/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import Foundation + +@Observable +class DataController { + var selectedContact: SelectedContact? +} diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 6311ee1..1e876b1 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -9,6 +9,7 @@ import SwiftUI struct DetailScreen: View { + @Environment(DataController.self) var dataController @Bindable var contact: SelectedContact var nextCatchUpTime: String { @@ -45,11 +46,13 @@ struct DetailScreen: View { } .onAppear { Utils.clearNotificationBadge() + dataController.selectedContact = contact } .onDisappear { NotificationHelper.removeExistingNotifications(for: contact) NotificationHelper.createNewNotification(for: contact) + dataController.selectedContact = nil } .navigationBarTitleDisplayMode(.inline) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index c1f6c01..163e765 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -42,71 +42,40 @@ struct HomeScreen : View { } var body: some View { - NavigationStack { - VStack { - List { - Section("Next CatchUps") { - NextCatchUpsGridView(nextCatchUps: filteredNextCatchups, shouldNavigateViaGrid: $shouldNavigateViaGrid, tappedGridContact: $tappedGridContact) - } - - Section("All CatchUps") { - ForEach(selectedContacts) { contact in - NavigationLink(destination: DetailScreen(contact: contact)) { - ContactRowView(contact: contact) - } - } - .onDelete(perform: removePendingNotificationsAndDeleteContact) - } - } - .refreshable { - ContactHelper.updateSelectedContacts(selectedContacts) + VStack { + List { + Section("Next CatchUps") { + NextCatchUpsGridView(nextCatchUps: filteredNextCatchups, shouldNavigateViaGrid: $shouldNavigateViaGrid, tappedGridContact: $tappedGridContact) } - .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in - if !contacts.isEmpty { - saveSelectedContact(for: contacts) + Section("All CatchUps") { + ForEach(selectedContacts) { contact in + NavigationLink(destination: DetailScreen(contact: contact)) { + ContactRowView(contact: contact) + } } - contactPicker.chosenContacts = [] - } - - Button { - openContactPicker() - } label: { - OpenContactPickerButtonView() + .onDelete(perform: removePendingNotificationsAndDeleteContact) } } - .navigationBarTitle("CatchUp") - - .toolbar { - ToolbarItem(placement: .topBarLeading) { - EditButton() - .foregroundStyle(.blue) - } - - ToolbarItem(placement: .topBarTrailing) { - Button { - isShowingAboutSheet = true - } label: { - Image(systemName: "person.crop.square") - .foregroundStyle(.blue) - } - .sheet(isPresented: $isShowingAboutSheet) { - AboutScreen() - } - } + .refreshable { + ContactHelper.updateSelectedContacts(selectedContacts) } - .navigationDestination(isPresented: $shouldNavigateViaGrid) { - if let tappedGridContact { - DetailScreen(contact: tappedGridContact) + .onChange(of: contactPicker.chosenContacts) { initialContacts, contacts in + if !contacts.isEmpty { + saveSelectedContact(for: contacts) } + contactPicker.chosenContacts = [] } - - .sheet(isPresented: $isShowingUpdatesSheet) { - UpdatesScreen() + + Button { + openContactPicker() + } label: { + OpenContactPickerButtonView() } - } - .accentColor(.orange) + } + .accentColor(.orange) + .navigationBarTitle("CatchUp") .onAppear { clearNotificationBadgeAndCheckForUpdate() @@ -131,6 +100,35 @@ struct HomeScreen : View { } isColdLaunch = false } + + .sheet(isPresented: $isShowingUpdatesSheet) { + UpdatesScreen() + } + + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + .foregroundStyle(.blue) + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + isShowingAboutSheet = true + } label: { + Image(systemName: "person.crop.square") + .foregroundStyle(.blue) + } + .sheet(isPresented: $isShowingAboutSheet) { + AboutScreen() + } + } + } + + .navigationDestination(isPresented: $shouldNavigateViaGrid) { + if let tappedGridContact { + DetailScreen(contact: tappedGridContact) + } + } } @MainActor From 6da6d0d60097a236cbc9912e7f05b5ff4a31dbf6 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 09:40:07 -0600 Subject: [PATCH 36/46] Fix accent color --- CatchUp-SwiftUI/CatchUpApp.swift | 1 + CatchUp-SwiftUI/Views/HomeScreen.swift | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index 8efc20f..65eadd2 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -36,6 +36,7 @@ struct CatchUpApp: App { DetailScreen(contact: selectedContact) } } + .accentColor(.orange) } .modelContainer(modelContainer) .environment(dataController) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 163e765..0a61526 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -74,7 +74,6 @@ struct HomeScreen : View { OpenContactPickerButtonView() } } - .accentColor(.orange) .navigationBarTitle("CatchUp") .onAppear { From 87ca0af638da99b5922ba42e1c5daaa410b9e600 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 17:43:15 -0600 Subject: [PATCH 37/46] Added NoContactSelectedScreen for iPad --- CatchUp-SwiftUI.xcodeproj/project.pbxproj | 4 ++ CatchUp-SwiftUI/CatchUpApp.swift | 2 + .../Views/NoContactSelectedScreen.swift | 40 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 CatchUp-SwiftUI/Views/NoContactSelectedScreen.swift diff --git a/CatchUp-SwiftUI.xcodeproj/project.pbxproj b/CatchUp-SwiftUI.xcodeproj/project.pbxproj index ae35ab6..d2df634 100644 --- a/CatchUp-SwiftUI.xcodeproj/project.pbxproj +++ b/CatchUp-SwiftUI.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ F435D4332BB8E7E700C43586 /* RemoveContactButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */; }; F435D4352BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */; }; F435D4372BBA4EC600C43586 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4362BBA4EC600C43586 /* DataController.swift */; }; + F435D4392BBB7B7800C43586 /* NoContactSelectedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435D4382BBB7B7800C43586 /* NoContactSelectedScreen.swift */; }; F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4871DA0244FC43C00925392 /* NotificationHelper.swift */; }; F48E37F922C455C3008B0B8B /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E37F822C455C3008B0B8B /* HomeScreen.swift */; }; F48E37FB22C455CB008B0B8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F48E37FA22C455CB008B0B8B /* Assets.xcassets */; }; @@ -54,6 +55,7 @@ F435D4322BB8E7E700C43586 /* RemoveContactButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveContactButton.swift; sourceTree = ""; }; F435D4342BBA3EB100C43586 /* BirthdayOrAnniversaryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BirthdayOrAnniversaryRow.swift; sourceTree = ""; }; F435D4362BBA4EC600C43586 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = ""; }; + F435D4382BBB7B7800C43586 /* NoContactSelectedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoContactSelectedScreen.swift; sourceTree = ""; }; F4871DA0244FC43C00925392 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; }; F48E37EE22C455C3008B0B8B /* CatchUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CatchUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; F48E37F822C455C3008B0B8B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; @@ -97,6 +99,7 @@ F4118B512B9EA2F7001BC8C7 /* DetailScreen Subviews */, F4AD59AF244CA12C00296568 /* AboutScreen.swift */, 14BE3AD62459F610004F72DE /* UpdatesScreen.swift */, + F435D4382BBB7B7800C43586 /* NoContactSelectedScreen.swift */, ); path = Views; sourceTree = ""; @@ -289,6 +292,7 @@ F4A9B8572442A1F7001D8C55 /* GradientView.swift in Sources */, F4A9B85B2443FFF3001D8C55 /* Conversions.swift in Sources */, 14BE3AD324593556004F72DE /* Utils.swift in Sources */, + F435D4392BBB7B7800C43586 /* NoContactSelectedScreen.swift in Sources */, F4F753652BB11FA300B20090 /* View+if.swift in Sources */, F4871DA1244FC43C00925392 /* NotificationHelper.swift in Sources */, F4F7535D2BB0956800B20090 /* NextCatchUpsGridView.swift in Sources */, diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index 65eadd2..e7417a0 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -34,6 +34,8 @@ struct CatchUpApp: App { } detail: { if let selectedContact = dataController.selectedContact { DetailScreen(contact: selectedContact) + } else { + NoContactSelectedScreen() } } .accentColor(.orange) diff --git a/CatchUp-SwiftUI/Views/NoContactSelectedScreen.swift b/CatchUp-SwiftUI/Views/NoContactSelectedScreen.swift new file mode 100644 index 0000000..a880bdf --- /dev/null +++ b/CatchUp-SwiftUI/Views/NoContactSelectedScreen.swift @@ -0,0 +1,40 @@ +// +// NoContactSelectedScreen.swift +// CatchUp-SwiftUI +// +// Created by Ryan Token on 4/1/24. +// Copyright © 2024 Token Solutions. All rights reserved. +// + +import SwiftUI + +struct NoContactSelectedScreen: View { + var body: some View { + HStack { + Spacer() + + VStack { + Spacer() + + Image("CatchUp") + .resizable() + .frame(width: 100, height: 100) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .shadow(radius: 15) + .padding(.bottom) + + Text("Select a contact from the left sidebar to get started.") + .fontWeight(.semibold) + + Spacer() + Spacer() + } + + Spacer() + } + } +} + +#Preview { + NoContactSelectedScreen() +} From 46229e534e5bc660f25454a3bb1c09fddb78c459 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 19:36:08 -0600 Subject: [PATCH 38/46] Only schedule new notifications when the pickers actually change --- .../xcschemes/CatchUp-SwiftUI.xcscheme | 4 +- CatchUp-SwiftUI/CatchUpApp.swift | 2 +- .../Utilities/NotificationHelper.swift | 5 +- .../NotificationPreferenceView.swift | 91 +++++++++++++------ CatchUp-SwiftUI/Views/DetailScreen.swift | 2 - CatchUp-SwiftUI/Views/HomeScreen.swift | 11 ++- 6 files changed, 77 insertions(+), 38 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme index bc0b26a..2dece56 100644 --- a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme +++ b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme @@ -47,11 +47,11 @@ + isEnabled = "NO"> + isEnabled = "NO"> diff --git a/CatchUp-SwiftUI/CatchUpApp.swift b/CatchUp-SwiftUI/CatchUpApp.swift index e7417a0..616111e 100644 --- a/CatchUp-SwiftUI/CatchUpApp.swift +++ b/CatchUp-SwiftUI/CatchUpApp.swift @@ -38,9 +38,9 @@ struct CatchUpApp: App { NoContactSelectedScreen() } } + .environment(dataController) .accentColor(.orange) } .modelContainer(modelContainer) - .environment(dataController) } } diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 7b81318..dc33c3e 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -365,8 +365,9 @@ struct NotificationHelper { } @MainActor - static func resetNotifications(for selectedContacts: [SelectedContact]) { - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + static func resetNotifications(for selectedContacts: [SelectedContact], delayTime: Double) { + DispatchQueue.main.asyncAfter(deadline: .now() + delayTime) { + print("resetting notifications") UNUserNotificationCenter.current().removeAllPendingNotificationRequests() for contact in selectedContacts { diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift index 59a9060..7ce356a 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -11,9 +11,16 @@ import SwiftUI struct NotificationPreferenceView: View { @Environment(\.modelContext) var modelContext + @State private var initialNotificationPreference = 0 + @State private var initialNotificationPreferenceWeekday = 0 + @State private var initialNotificationPreferenceTime = Date() + @State private var initialNotificationPreferenceCustomDate = Date() + @State private var notificationPreferenceTime = Date() @State private var notificationPreferenceCustomDate = Date() + @State private var whatDayText = "" + @State private var viewDidAppear = false @Bindable var contact: SelectedContact @@ -72,47 +79,66 @@ struct NotificationPreferenceView: View { } } .onAppear { - setInitialNotificateDateTime() - - if contact.notification_preference == 2 { // weekly - whatDayText = "What day?" - } else if contact.notification_preference == 3 { // monthly - whatDayText = "What day? We'll pick a random week." - } + setInitialState() + } - print("contact notification preference: \(contact.notification_preference)") + .onChange(of: contact) { + setInitialState() } .onChange(of: contact.notification_preference) { - if contact.notification_preference == 2 { // weekly - whatDayText = "What day?" - } else if contact.notification_preference == 3 { // monthly - whatDayText = "What day? We'll pick a random week." - } + if contact.notification_preference != initialNotificationPreference { + print("contact.notification_preference changed") + if contact.notification_preference == 2 { // weekly + whatDayText = "What day?" + } else if contact.notification_preference == 3 { // monthly + whatDayText = "What day? We'll pick a random week." + } - NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + resetNotificationsForContact() + } } - .onChange(of: contact.notification_preference_weekday) { - contact.notification_preference_week_of_month = .random(in: 1..<5) - NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + .onChange(of: contact.notification_preference_weekday) { initialValue, newValue in + if contact.notification_preference_weekday != initialNotificationPreferenceWeekday { + print("contact.notification_preference_weekday changed from \(initialValue) to \(newValue)") + contact.notification_preference_week_of_month = .random(in: 1..<5) + + resetNotificationsForContact() + } } .onChange(of: notificationPreferenceTime) { initialTime, newTime in - let calendar = Calendar.current - let components = calendar.dateComponents([.hour, .minute], from : newTime) - NotificationHelper.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!) + if notificationPreferenceTime != initialNotificationPreferenceTime { + print("notificationPreferenceTime changed") + let calendar = Calendar.current + let components = calendar.dateComponents([.hour, .minute], from : newTime) + NotificationHelper.updateNotificationTime(for: contact, hour: components.hour!, minute: components.minute!) - NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + resetNotificationsForContact() + } } .onChange(of: notificationPreferenceCustomDate) { initialDate, newDate in - let year = Calendar.current.component(.year, from: newDate) - let month = Calendar.current.component(.month, from: newDate) - let day = Calendar.current.component(.day, from: newDate) - NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year) + if notificationPreferenceCustomDate != initialNotificationPreferenceCustomDate { + print("notificationPreferenceCustomDate changed") + let year = Calendar.current.component(.year, from: newDate) + let month = Calendar.current.component(.month, from: newDate) + let day = Calendar.current.component(.day, from: newDate) + NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year) + + resetNotificationsForContact() + } + } + } - NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + func setInitialState() { + setInitialNotificateDateTime() + + if contact.notification_preference == 2 { // weekly + whatDayText = "What day?" + } else if contact.notification_preference == 3 { // monthly + whatDayText = "What day? We'll pick a random week." } } @@ -124,9 +150,22 @@ struct NotificationPreferenceView: View { let customDateComponents = DateComponents(calendar: calendar, year: Int(contact.notification_preference_custom_year), month: Int(contact.notification_preference_custom_month), day: Int(contact.notification_preference_custom_day)) let customDate = Calendar.current.date(from: customDateComponents) + initialNotificationPreference = contact.notification_preference + initialNotificationPreferenceWeekday = contact.notification_preference_weekday + initialNotificationPreferenceTime = time ?? Date() + initialNotificationPreferenceCustomDate = customDate ?? Date() + notificationPreferenceTime = time ?? Date() notificationPreferenceCustomDate = customDate ?? Date() } + + @MainActor + func resetNotificationsForContact() { + print("resetting notifications for \(contact.name)") + NotificationHelper.removeExistingNotifications(for: contact) + NotificationHelper.createNewNotification(for: contact) + NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) + } } #Preview { diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index 1e876b1..e38d252 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -50,8 +50,6 @@ struct DetailScreen: View { } .onDisappear { - NotificationHelper.removeExistingNotifications(for: contact) - NotificationHelper.createNewNotification(for: contact) dataController.selectedContact = nil } diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 0a61526..dee3a84 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -85,19 +85,20 @@ struct HomeScreen : View { requestReview() } - print("resetting notifications") - NotificationHelper.resetNotifications(for: selectedContacts) + NotificationHelper.resetNotifications(for: selectedContacts, delayTime: 3) timesUserHasLaunchedApp += 1 } } .onChange(of: scenePhase) { initialPhase, newPhase in - if !isColdLaunch { - if newPhase == .active { + if newPhase == .active { + Utils.clearNotificationBadge() + if !isColdLaunch { + NotificationHelper.resetNotifications(for: selectedContacts, delayTime: 3) updateNextNotificationTime(for: selectedContacts) } + isColdLaunch = false } - isColdLaunch = false } .sheet(isPresented: $isShowingUpdatesSheet) { From 318f2942f0024ff3e5d16b77071c2492a88b9130 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 20:19:44 -0600 Subject: [PATCH 39/46] Unread indicators --- .../xcschemes/CatchUp-SwiftUI.xcscheme | 4 +-- CatchUp-SwiftUI/Data/SelectedContact.swift | 8 +++-- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 3 +- .../Utilities/NotificationHelper.swift | 5 +++ CatchUp-SwiftUI/Utilities/Utils.swift | 8 +++-- CatchUp-SwiftUI/Views/DetailScreen.swift | 3 +- .../HomeScreen Subviews/ContactRowView.swift | 34 +++++++++++++++++++ CatchUp-SwiftUI/Views/HomeScreen.swift | 4 +-- 8 files changed, 59 insertions(+), 10 deletions(-) diff --git a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme index 2dece56..bc0b26a 100644 --- a/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme +++ b/CatchUp-SwiftUI.xcodeproj/xcshareddata/xcschemes/CatchUp-SwiftUI.xcscheme @@ -47,11 +47,11 @@ + isEnabled = "YES"> + isEnabled = "YES"> diff --git a/CatchUp-SwiftUI/Data/SelectedContact.swift b/CatchUp-SwiftUI/Data/SelectedContact.swift index 7b9a37c..a940234 100644 --- a/CatchUp-SwiftUI/Data/SelectedContact.swift +++ b/CatchUp-SwiftUI/Data/SelectedContact.swift @@ -35,6 +35,7 @@ class SelectedContact { var secondary_email: String = "" var secondary_phone: String = "" var next_notification_date_time: String = "" + var unread_badge_date_time: String = "" init( address: String, @@ -59,7 +60,8 @@ class SelectedContact { picture: String, secondary_address: String, secondary_email: String, - secondary_phone: String + secondary_phone: String, + unread_badge_date_time: String ) { self.address = address self.anniversary = anniversary @@ -84,6 +86,7 @@ class SelectedContact { self.secondary_address = secondary_address self.secondary_email = secondary_email self.secondary_phone = secondary_phone + self.unread_badge_date_time = unread_badge_date_time } static let sampleData = SelectedContact( @@ -109,6 +112,7 @@ class SelectedContact { picture: "photo-as-data-string", secondary_address: "", secondary_email: "", - secondary_phone: "" + secondary_phone: "", + unread_badge_date_time: "" ) } diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 6dc3932..86c6316 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -310,7 +310,8 @@ struct ContactHelper { picture: picture, secondary_address: secondary_address, secondary_email: secondary_email, - secondary_phone: secondary_phone + secondary_phone: secondary_phone, + unread_badge_date_time: "" ) return selectedContact diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index dc33c3e..2aa26e7 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -374,6 +374,11 @@ struct NotificationHelper { if contact.notification_preference != 0 { NotificationHelper.createNewNotification(for: contact) } + + if contact.unread_badge_date_time == "" { + print("updating unread badge date time for \(contact.name)") + contact.unread_badge_date_time = contact.next_notification_date_time + } } } } diff --git a/CatchUp-SwiftUI/Utilities/Utils.swift b/CatchUp-SwiftUI/Utilities/Utils.swift index 11be633..313861c 100644 --- a/CatchUp-SwiftUI/Utilities/Utils.swift +++ b/CatchUp-SwiftUI/Utilities/Utils.swift @@ -12,10 +12,14 @@ import CoreData import UserNotifications struct Utils { - static func clearNotificationBadge() { + static func clearAppIconNotificationBadge() { UNUserNotificationCenter.current().setBadgeCount(0) } - + + static func clearUnreadBadge(for contact: SelectedContact) { + contact.unread_badge_date_time = contact.next_notification_date_time + } + static func getCurrentAppVersion() -> String { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] let version = (appVersion as! String) diff --git a/CatchUp-SwiftUI/Views/DetailScreen.swift b/CatchUp-SwiftUI/Views/DetailScreen.swift index e38d252..b97298b 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen.swift @@ -45,7 +45,8 @@ struct DetailScreen: View { } } .onAppear { - Utils.clearNotificationBadge() + Utils.clearAppIconNotificationBadge() + Utils.clearUnreadBadge(for: contact) dataController.selectedContact = contact } diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index 720d8e7..0fe4475 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -9,8 +9,11 @@ import SwiftUI struct ContactRowView: View { + @Environment(\.scenePhase) var scenePhase let contact: SelectedContact + @State private var shouldShowUnreadIndicator = false + var body: some View { HStack { ContactPictureView(contact: contact) @@ -22,6 +25,37 @@ struct ContactRowView: View { .font(.caption) .foregroundStyle(.gray) } + + Spacer() + + if shouldShowUnreadIndicator { + Circle() + .foregroundStyle(.orange) + .frame(width: 15, height: 15) + .padding(.horizontal) + } + } + .onAppear { + shouldShowUnreadIndicator = determineIfShouldShowIndicator() + } + + .onChange(of: scenePhase) { + if scenePhase == .active { + shouldShowUnreadIndicator = determineIfShouldShowIndicator() + } + } + } + + func determineIfShouldShowIndicator() -> Bool { + let today = Date.now + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let formattedTodayDate = formatter.string(from: today) + + if formattedTodayDate >= contact.unread_badge_date_time { + return true + } else { + return false } } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index dee3a84..326d41d 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -92,7 +92,7 @@ struct HomeScreen : View { .onChange(of: scenePhase) { initialPhase, newPhase in if newPhase == .active { - Utils.clearNotificationBadge() + Utils.clearAppIconNotificationBadge() if !isColdLaunch { NotificationHelper.resetNotifications(for: selectedContacts, delayTime: 3) updateNextNotificationTime(for: selectedContacts) @@ -171,7 +171,7 @@ struct HomeScreen : View { func clearNotificationBadgeAndCheckForUpdate() { Utils.fetchAvailableIAPs() - Utils.clearNotificationBadge() + Utils.clearAppIconNotificationBadge() checkForUpdate() } From 43d93063d04e06bba1f3805d235e4175dd5eabb4 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 20:34:52 -0600 Subject: [PATCH 40/46] Update UpdatesScreen --- .../Views/HomeScreen Subviews/ContactRowView.swift | 2 +- CatchUp-SwiftUI/Views/UpdatesScreen.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index 0fe4475..7636046 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -52,7 +52,7 @@ struct ContactRowView: View { formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let formattedTodayDate = formatter.string(from: today) - if formattedTodayDate >= contact.unread_badge_date_time { + if contact.unread_badge_date_time != "" && formattedTodayDate >= contact.unread_badge_date_time { return true } else { return false diff --git a/CatchUp-SwiftUI/Views/UpdatesScreen.swift b/CatchUp-SwiftUI/Views/UpdatesScreen.swift index 0ebbe81..19adf58 100644 --- a/CatchUp-SwiftUI/Views/UpdatesScreen.swift +++ b/CatchUp-SwiftUI/Views/UpdatesScreen.swift @@ -39,6 +39,10 @@ struct UpdatesScreen: View { Text("– Pull-to-refresh photo & contact information for your selected contacts") + Spacer() + + Text("– Unread indicators for contacts it's time to CatchUp with") + Spacer() Text("– Automatic cloud syncing with other Apple devices") From 0ced4613bf77d03ec623204a569e3d33fa83d6b3 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Mon, 1 Apr 2024 21:48:06 -0600 Subject: [PATCH 41/46] Several fixes after testing new devices, empty contact lists, and more heavy testing in general --- CatchUp-SwiftUI/Utilities/ContactHelper.swift | 4 ++-- .../Utilities/NotificationHelper.swift | 2 +- .../BirthdayOrAnniversaryRow.swift | 6 ++--- .../NotificationPreferenceView.swift | 24 +++++++++++-------- CatchUp-SwiftUI/Views/HomeScreen.swift | 22 ++++++++++------- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/ContactHelper.swift b/CatchUp-SwiftUI/Utilities/ContactHelper.swift index 86c6316..f52458c 100644 --- a/CatchUp-SwiftUI/Utilities/ContactHelper.swift +++ b/CatchUp-SwiftUI/Utilities/ContactHelper.swift @@ -253,7 +253,7 @@ struct ContactHelper { return friendlyFormatter.string(from: date) } } else { - return "Unknown" + return "None" } } @@ -263,7 +263,7 @@ struct ContactHelper { let currentDay = Calendar.current.component(.day, from: Date()) let currentMonth = Calendar.current.component(.month, from: Date()) let currentYear = Calendar.current.component(.year, from: Date()) - let currentWeekOfMonth = Calendar.current.component(.weekOfYear, from: Date()) + let currentWeekOfMonth = Calendar.current.component(.weekOfMonth, from: Date()) let id = UUID() let address = ContactHelper.getContactPrimaryAddress(for: contact) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 2aa26e7..33944ab 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -325,6 +325,7 @@ struct NotificationHelper { } var soonestUpcomingNotificationDateString = "Unknown" + print("components for \(contact.name): \(components)") soonestUpcomingNotificationDateString = calculateDateFromComponents(components) if ContactHelper.contactHasBirthday(contact) { @@ -351,7 +352,6 @@ struct NotificationHelper { // Calculate the date based on the provided components and current date if let calculatedDate = calendar.nextDate(after: currentDate, matching: dateComponents, matchingPolicy: .nextTime) { - // Create a DateFormatter instance let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // Define the desired date format diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift index 06d0256..934e630 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/BirthdayOrAnniversaryRow.swift @@ -39,12 +39,12 @@ struct BirthdayOrAnniversaryRow: View { } } - func dayBeforeAnniversaryString() -> String { + func dayBeforeAnniversaryString() -> String? { if ContactHelper.contactHasAnniversary(contact) { return NotificationHelper.calculateDateFromComponents(NotificationHelper.getAnniversaryDateComponents(for: contact)) } - - return "" + + return nil } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift index 7ce356a..c302778 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -86,12 +86,16 @@ struct NotificationPreferenceView: View { setInitialState() } - .onChange(of: contact.notification_preference) { - if contact.notification_preference != initialNotificationPreference { + .onChange(of: contact.notification_preference) { oldValue, newValue in + if newValue != initialNotificationPreference { print("contact.notification_preference changed") - if contact.notification_preference == 2 { // weekly + initialNotificationPreference = 999 + contact.notification_preference_week_of_month = .random(in: 2..<5) + + if newValue == 2 { // weekly whatDayText = "What day?" - } else if contact.notification_preference == 3 { // monthly + contact.notification_preference_week_of_month = 0 + } else if newValue == 3 { // monthly whatDayText = "What day? We'll pick a random week." } @@ -100,16 +104,15 @@ struct NotificationPreferenceView: View { } .onChange(of: contact.notification_preference_weekday) { initialValue, newValue in - if contact.notification_preference_weekday != initialNotificationPreferenceWeekday { - print("contact.notification_preference_weekday changed from \(initialValue) to \(newValue)") - contact.notification_preference_week_of_month = .random(in: 1..<5) - + if newValue != initialNotificationPreferenceWeekday { + initialNotificationPreferenceWeekday = 999 resetNotificationsForContact() } } .onChange(of: notificationPreferenceTime) { initialTime, newTime in - if notificationPreferenceTime != initialNotificationPreferenceTime { + if newTime != initialNotificationPreferenceTime { + initialNotificationPreferenceTime = Date() print("notificationPreferenceTime changed") let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute], from : newTime) @@ -120,7 +123,8 @@ struct NotificationPreferenceView: View { } .onChange(of: notificationPreferenceCustomDate) { initialDate, newDate in - if notificationPreferenceCustomDate != initialNotificationPreferenceCustomDate { + if newDate != initialNotificationPreferenceCustomDate { + initialNotificationPreferenceCustomDate = Date() print("notificationPreferenceCustomDate changed") let year = Calendar.current.component(.year, from: newDate) let month = Calendar.current.component(.month, from: newDate) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 326d41d..38ba2e3 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -44,17 +44,23 @@ struct HomeScreen : View { var body: some View { VStack { List { - Section("Next CatchUps") { - NextCatchUpsGridView(nextCatchUps: filteredNextCatchups, shouldNavigateViaGrid: $shouldNavigateViaGrid, tappedGridContact: $tappedGridContact) - } + if selectedContacts.count > 0 { + if selectedContacts.contains(where: { $0.notification_preference != 0 }) { + Section("Next CatchUps") { + NextCatchUpsGridView(nextCatchUps: filteredNextCatchups, shouldNavigateViaGrid: $shouldNavigateViaGrid, tappedGridContact: $tappedGridContact) + } + } - Section("All CatchUps") { - ForEach(selectedContacts) { contact in - NavigationLink(destination: DetailScreen(contact: contact)) { - ContactRowView(contact: contact) + Section("All CatchUps") { + ForEach(selectedContacts) { contact in + NavigationLink(destination: DetailScreen(contact: contact)) { + ContactRowView(contact: contact) + } } + .onDelete(perform: removePendingNotificationsAndDeleteContact) } - .onDelete(perform: removePendingNotificationsAndDeleteContact) + } else { + Text("No CatchUps yet! Tap the 'Add Contacts' button to add some.") } } .refreshable { From 237b14afb72943c9f55bd7c5df19c5f80a672442 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Tue, 2 Apr 2024 10:24:59 -0600 Subject: [PATCH 42/46] Only reset notifications on cold launches and pull-to-refresh --- .../Utilities/NotificationHelper.swift | 3 --- CatchUp-SwiftUI/Utilities/Utils.swift | 10 ++++++++++ .../NotificationPreferenceView.swift | 2 ++ .../HomeScreen Subviews/ContactRowView.swift | 17 ++++++++++++++++- CatchUp-SwiftUI/Views/HomeScreen.swift | 11 +++++------ 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 33944ab..7d76ca0 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -325,7 +325,6 @@ struct NotificationHelper { } var soonestUpcomingNotificationDateString = "Unknown" - print("components for \(contact.name): \(components)") soonestUpcomingNotificationDateString = calculateDateFromComponents(components) if ContactHelper.contactHasBirthday(contact) { @@ -342,7 +341,6 @@ struct NotificationHelper { } } - print("soonestUpcomingNotification for \(contact.name): \(soonestUpcomingNotificationDateString)") return soonestUpcomingNotificationDateString } @@ -360,7 +358,6 @@ struct NotificationHelper { return formattedDate } - print("returning Unknown for soonestUpcomingNotification") return "Unknown" } diff --git a/CatchUp-SwiftUI/Utilities/Utils.swift b/CatchUp-SwiftUI/Utilities/Utils.swift index 313861c..92c4fe4 100644 --- a/CatchUp-SwiftUI/Utilities/Utils.swift +++ b/CatchUp-SwiftUI/Utilities/Utils.swift @@ -41,4 +41,14 @@ struct Utils { print("fetching IAPs") IAPService.shared.fetchAvailableProducts() } + + @MainActor + static func isPhone() -> Bool { + return UIDevice.current.userInterfaceIdiom == .phone + } + + @MainActor + static func isiPadOrMac() -> Bool { + return UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac + } } diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift index c302778..7b5c726 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -9,6 +9,7 @@ import SwiftUI struct NotificationPreferenceView: View { + @Environment(DataController.self) var dataController @Environment(\.modelContext) var modelContext @State private var initialNotificationPreference = 0 @@ -137,6 +138,7 @@ struct NotificationPreferenceView: View { } func setInitialState() { + dataController.selectedContact = contact setInitialNotificateDateTime() if contact.notification_preference == 2 { // weekly diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index 7636046..6a2392c 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -9,10 +9,12 @@ import SwiftUI struct ContactRowView: View { + @Environment(DataController.self) var dataController @Environment(\.scenePhase) var scenePhase let contact: SelectedContact @State private var shouldShowUnreadIndicator = false + @State private var captionTextColor = Color(.gray) var body: some View { HStack { @@ -23,7 +25,12 @@ struct ContactRowView: View { .font(.headline) Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) .font(.caption) - .foregroundStyle(.gray) + .if(Utils.isPhone()) { view in + view.foregroundStyle(.gray) + } + .if(Utils.isiPadOrMac()) { view in + view.foregroundStyle(dataController.selectedContact == contact ? .white : .gray) + } } Spacer() @@ -44,6 +51,14 @@ struct ContactRowView: View { shouldShowUnreadIndicator = determineIfShouldShowIndicator() } } + + .onChange(of: dataController.selectedContact) { + if contact == dataController.selectedContact { + captionTextColor = .white + } else { + captionTextColor = .gray + } + } } func determineIfShouldShowIndicator() -> Bool { diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index 38ba2e3..cb79a23 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -64,6 +64,7 @@ struct HomeScreen : View { } } .refreshable { + NotificationHelper.resetNotifications(for: selectedContacts, delayTime: 0) ContactHelper.updateSelectedContacts(selectedContacts) } @@ -84,9 +85,11 @@ struct HomeScreen : View { .onAppear { clearNotificationBadgeAndCheckForUpdate() - NotificationHelper.requestAuthorizationForNotifications() if isColdLaunch { + isColdLaunch = false + NotificationHelper.requestAuthorizationForNotifications() + if timesUserHasLaunchedApp > 5 && Int.random(in: 1...3) == 2 { requestReview() } @@ -99,11 +102,7 @@ struct HomeScreen : View { .onChange(of: scenePhase) { initialPhase, newPhase in if newPhase == .active { Utils.clearAppIconNotificationBadge() - if !isColdLaunch { - NotificationHelper.resetNotifications(for: selectedContacts, delayTime: 3) - updateNextNotificationTime(for: selectedContacts) - } - isColdLaunch = false + updateNextNotificationTime(for: selectedContacts) } } From e2d1dffb5f6f70ccc38dd9ed439b78ea24012a6c Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Tue, 2 Apr 2024 18:41:05 -0600 Subject: [PATCH 43/46] Custom date notification fixes --- .../Utilities/NotificationHelper.swift | 51 ++++++++----------- .../NotificationPreferenceView.swift | 17 +++++-- .../HomeScreen Subviews/ContactRowView.swift | 8 +-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift index 7d76ca0..8a29083 100644 --- a/CatchUp-SwiftUI/Utilities/NotificationHelper.swift +++ b/CatchUp-SwiftUI/Utilities/NotificationHelper.swift @@ -100,27 +100,27 @@ struct NotificationHelper { case 0: // Never break case 1: // Daily - dateComponents.hour = Int(contact.notification_preference_hour) - dateComponents.minute = Int(contact.notification_preference_minute) + dateComponents.hour = contact.notification_preference_hour + dateComponents.minute = contact.notification_preference_minute break case 2: // Weekly - dateComponents.hour = Int(contact.notification_preference_hour) - dateComponents.minute = Int(contact.notification_preference_minute) + dateComponents.hour = contact.notification_preference_hour + dateComponents.minute = contact.notification_preference_minute // weekday units are 1-7, I store them as 0-6 though. Need to add 1 - dateComponents.weekday = Int(contact.notification_preference_weekday)+1 + dateComponents.weekday = contact.notification_preference_weekday+1 break case 3: // Monthly - dateComponents.hour = Int(contact.notification_preference_hour) - dateComponents.minute = Int(contact.notification_preference_minute) - dateComponents.weekday = Int(contact.notification_preference_weekday)+1 - dateComponents.weekOfMonth = Int(contact.notification_preference_week_of_month) + dateComponents.hour = contact.notification_preference_hour + dateComponents.minute = contact.notification_preference_minute + dateComponents.weekday = contact.notification_preference_weekday+1 + dateComponents.weekOfMonth = contact.notification_preference_week_of_month break case 4: // Custom Date - dateComponents.month = Int(contact.notification_preference_custom_month) - dateComponents.day = Int(contact.notification_preference_custom_day) - dateComponents.year = Int(contact.notification_preference_custom_year) - dateComponents.hour = 12 - dateComponents.minute = 30 + dateComponents.month = contact.notification_preference_custom_month + dateComponents.day = contact.notification_preference_custom_day + dateComponents.year = contact.notification_preference_custom_year + dateComponents.hour = contact.notification_preference_hour + dateComponents.minute = contact.notification_preference_minute break default: print("It's impossible to get here") @@ -176,7 +176,7 @@ struct NotificationHelper { contact.anniversary_notification_id = identifier } - print("scheduling notification for contact: \(contact.name)") + print("scheduling notification for \(contact.name) with date components: \(trigger.dateComponents)") UNUserNotificationCenter.current().add(request) } @@ -231,29 +231,22 @@ struct NotificationHelper { } static func updateNotificationPreference(for contact: SelectedContact, selection: Int) { - let newPreference = selection - contact.notification_preference = newPreference + contact.notification_preference = selection } static func updateNotificationTime(for contact: SelectedContact, hour: Int, minute: Int) { - let newHour = hour - let newMinute = minute - contact.notification_preference_hour = newHour - contact.notification_preference_minute = newMinute + contact.notification_preference_hour = hour + contact.notification_preference_minute = minute } static func updateNotificationPreferenceWeekday(for contact: SelectedContact, weekday: Int) { - let newWeekday = weekday - contact.notification_preference_weekday = newWeekday + contact.notification_preference_weekday = weekday } static func updateNotificationCustomDate(for contact: SelectedContact, month: Int, day: Int, year: Int) { - let customMonth = month - let customDay = day - let customYear = year - contact.notification_preference_custom_month = customMonth - contact.notification_preference_custom_day = customDay - contact.notification_preference_custom_year = customYear + contact.notification_preference_custom_month = month + contact.notification_preference_custom_day = day + contact.notification_preference_custom_year = year } static func requestAuthorizationForNotifications() { diff --git a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift index 7b5c726..c279956 100644 --- a/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift +++ b/CatchUp-SwiftUI/Views/DetailScreen Subviews/NotificationPreferenceView.swift @@ -130,6 +130,7 @@ struct NotificationPreferenceView: View { let year = Calendar.current.component(.year, from: newDate) let month = Calendar.current.component(.month, from: newDate) let day = Calendar.current.component(.day, from: newDate) + NotificationHelper.updateNotificationTime(for: contact, hour: 12, minute: 30) NotificationHelper.updateNotificationCustomDate(for: contact, month: month, day: day, year: year) resetNotificationsForContact() @@ -146,14 +147,25 @@ struct NotificationPreferenceView: View { } else if contact.notification_preference == 3 { // monthly whatDayText = "What day? We'll pick a random week." } + + Utils.clearUnreadBadge(for: contact) } func setInitialNotificateDateTime() { let calendar = Calendar.current - let timeComponents = DateComponents(calendar: calendar, hour: Int(contact.notification_preference_hour), minute: Int(contact.notification_preference_minute)) + let timeComponents = DateComponents( + calendar: calendar, + hour: Int(contact.notification_preference_hour), + minute: Int(contact.notification_preference_minute) + ) let time = Calendar.current.date(from: timeComponents) - let customDateComponents = DateComponents(calendar: calendar, year: Int(contact.notification_preference_custom_year), month: Int(contact.notification_preference_custom_month), day: Int(contact.notification_preference_custom_day)) + let customDateComponents = DateComponents( + calendar: calendar, + year: Int(contact.notification_preference_custom_year), + month: Int(contact.notification_preference_custom_month), + day: Int(contact.notification_preference_custom_day) + ) let customDate = Calendar.current.date(from: customDateComponents) initialNotificationPreference = contact.notification_preference @@ -170,7 +182,6 @@ struct NotificationPreferenceView: View { print("resetting notifications for \(contact.name)") NotificationHelper.removeExistingNotifications(for: contact) NotificationHelper.createNewNotification(for: contact) - NotificationHelper.updateNextNotificationDateTimeFor(contact: contact) } } diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift index 6a2392c..c07a40a 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/ContactRowView.swift @@ -14,7 +14,6 @@ struct ContactRowView: View { let contact: SelectedContact @State private var shouldShowUnreadIndicator = false - @State private var captionTextColor = Color(.gray) var body: some View { HStack { @@ -25,6 +24,7 @@ struct ContactRowView: View { .font(.headline) Text(Converter.convertNotificationPreferenceIntToString(preference: Int(contact.notification_preference), contact: contact)) .font(.caption) + .if(Utils.isPhone()) { view in view.foregroundStyle(.gray) } @@ -53,11 +53,7 @@ struct ContactRowView: View { } .onChange(of: dataController.selectedContact) { - if contact == dataController.selectedContact { - captionTextColor = .white - } else { - captionTextColor = .gray - } + shouldShowUnreadIndicator = determineIfShouldShowIndicator() } } From fdedebf4c57719dae3074abe232d7d4d4c4e536a Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Tue, 2 Apr 2024 18:55:58 -0600 Subject: [PATCH 44/46] Cleanup --- CatchUp-SwiftUI/Views/HomeScreen.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CatchUp-SwiftUI/Views/HomeScreen.swift b/CatchUp-SwiftUI/Views/HomeScreen.swift index cb79a23..863e535 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen.swift @@ -191,10 +191,10 @@ struct HomeScreen : View { } func checkForUpdate() { - let version = Utils.getCurrentAppVersion() - print("latest version: \(version)") + let latestVersion = Utils.getCurrentAppVersion() + print("latest version: \(latestVersion)") - if savedVersion == version { + if savedVersion == latestVersion { print("App is up to date!") } else { if Utils.updateIsMajor() && timesUserHasLaunchedApp > 0 { @@ -202,7 +202,7 @@ struct HomeScreen : View { print("Major update detected, showing UpdatesScreen...") isShowingUpdatesSheet = true } - savedVersion = version + savedVersion = latestVersion } } } From 1745da0d8ddbb4b5dda908091e4b9106c028df15 Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Tue, 2 Apr 2024 19:01:44 -0600 Subject: [PATCH 45/46] Add 'Bundle OS Type code' to Info.plist --- CatchUp-SwiftUI/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CatchUp-SwiftUI/Info.plist b/CatchUp-SwiftUI/Info.plist index 7aff5c3..dc118c6 100644 --- a/CatchUp-SwiftUI/Info.plist +++ b/CatchUp-SwiftUI/Info.plist @@ -12,6 +12,8 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) NSContactsUsageDescription CatchUp needs access to your Contacts in order to remind you to catch up with the people you choose. CatchUp does not store or save anything. UIBackgroundModes From 8a823ecc146cd9fb915c34b8055742dfb33fd1cb Mon Sep 17 00:00:00 2001 From: Ryan Token Date: Tue, 2 Apr 2024 20:17:16 -0600 Subject: [PATCH 46/46] Clean up the grid sizing with a fixed height --- .../Views/HomeScreen Subviews/NextCatchUpsGridView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift index dbfd80d..cc05706 100644 --- a/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift +++ b/CatchUp-SwiftUI/Views/HomeScreen Subviews/NextCatchUpsGridView.swift @@ -38,6 +38,7 @@ struct NextCatchUpsGridView: View { } } .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .frame(minHeight: 55, maxHeight: 65) .padding(10) .background(colorScheme == .light ? Color.white : Color(UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 1))) .cornerRadius(10)