diff --git a/iOS/Inventory.xcodeproj/project.pbxproj b/iOS/Inventory.xcodeproj/project.pbxproj index 9221e82..6f34f34 100644 --- a/iOS/Inventory.xcodeproj/project.pbxproj +++ b/iOS/Inventory.xcodeproj/project.pbxproj @@ -3,45 +3,46 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ - 0EC95D252B0BE9D8006A35A0 /* DittoDataBrowser in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D242B0BE9D8006A35A0 /* DittoDataBrowser */; }; - 0EC95D272B0BE9D8006A35A0 /* DittoDiskUsage in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D262B0BE9D8006A35A0 /* DittoDiskUsage */; }; - 0EC95D2B2B0BE9D8006A35A0 /* DittoExportLogs in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D2A2B0BE9D8006A35A0 /* DittoExportLogs */; }; - 0EC95D2F2B0BE9D8006A35A0 /* DittoPresenceViewer in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D2E2B0BE9D8006A35A0 /* DittoPresenceViewer */; }; + 074103972C8B3E8600E96480 /* RowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074103962C8B3E8600E96480 /* RowItem.swift */; }; + 0741039A2C8B3F4800E96480 /* DittoAllToolsMenu in Frameworks */ = {isa = PBXBuildFile; productRef = 074103992C8B3F4800E96480 /* DittoAllToolsMenu */; }; + 0741039C2C8B441B00E96480 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0741039B2C8B441B00E96480 /* Strings.swift */; }; + 07AE55332C876C5A00E1FCBD /* DittoAllToolsMenu in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55322C876C5A00E1FCBD /* DittoAllToolsMenu */; }; + 07AE55352C876C5A00E1FCBD /* DittoDataBrowser in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55342C876C5A00E1FCBD /* DittoDataBrowser */; }; + 07AE55372C876C5A00E1FCBD /* DittoDiskUsage in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55362C876C5A00E1FCBD /* DittoDiskUsage */; }; + 07AE55392C876C5A00E1FCBD /* DittoExportData in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55382C876C5A00E1FCBD /* DittoExportData */; }; + 07AE553B2C876C5A00E1FCBD /* DittoExportLogs in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE553A2C876C5A00E1FCBD /* DittoExportLogs */; }; + 07AE553D2C876C5A00E1FCBD /* DittoHealthMetrics in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE553C2C876C5A00E1FCBD /* DittoHealthMetrics */; }; + 07AE553F2C876C5A00E1FCBD /* DittoHeartbeat in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE553E2C876C5A00E1FCBD /* DittoHeartbeat */; }; + 07AE55412C876C5A00E1FCBD /* DittoPeersList in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55402C876C5A00E1FCBD /* DittoPeersList */; }; + 07AE55432C876C5A00E1FCBD /* DittoPermissionsHealth in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55422C876C5A00E1FCBD /* DittoPermissionsHealth */; }; + 07AE55452C876C5A00E1FCBD /* DittoPresenceDegradation in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55442C876C5A00E1FCBD /* DittoPresenceDegradation */; }; + 07AE55472C876C5A00E1FCBD /* DittoPresenceViewer in Frameworks */ = {isa = PBXBuildFile; productRef = 07AE55462C876C5A00E1FCBD /* DittoPresenceViewer */; }; + 07AE554D2C87721300E1FCBD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07AE554C2C87721300E1FCBD /* ContentView.swift */; }; 0EC95D322B0BEA1D006A35A0 /* DittoObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D312B0BEA1D006A35A0 /* DittoObjC */; }; 0EC95D342B0BEA1D006A35A0 /* DittoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC95D332B0BEA1D006A35A0 /* DittoSwift */; }; CC371EF429794A9700430C6F /* DittoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC371EF329794A9700430C6F /* DittoManager.swift */; }; CC371EF62979573200430C6F /* ItemDittoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC371EF52979573200430C6F /* ItemDittoModel.swift */; }; - CC481D6C24B073230007C4D2 /* DittoSDKInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC481D5E24B073230007C4D2 /* DittoSDKInfoViewController.swift */; }; - CC481D6D24B073230007C4D2 /* DittoInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC481D5F24B073230007C4D2 /* DittoInfoViewController.swift */; }; - CC481D6E24B073230007C4D2 /* DittoInfoView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC481D6024B073230007C4D2 /* DittoInfoView.storyboard */; }; CC626BF7298A655B0065E143 /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC626BF6298A655B0065E143 /* Env.swift */; }; - CCD3A728297EA27F00A7EFED /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3A727297EA27F00A7EFED /* Extensions.swift */; }; CCE8646529E59037007D822C /* Cartography in Frameworks */ = {isa = PBXBuildFile; productRef = CCE8646429E59037007D822C /* Cartography */; }; - CCF7CB1824FED62300B5ED70 /* BackgroundSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF7CB1724FED62300B5ED70 /* BackgroundSync.swift */; }; - E32461EA20E47D400006C628 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E32461E920E47D400006C628 /* MainViewController.swift */; }; E32461EC20E47DFC0006C628 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E32461EB20E47DFC0006C628 /* Constants.swift */; }; - E32461F020E488560006C628 /* ItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E32461EF20E488560006C628 /* ItemTableViewCell.swift */; }; E3ED324020E439FD005FC8B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3ED323F20E439FD005FC8B1 /* AppDelegate.swift */; }; E3ED324720E439FE005FC8B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E3ED324620E439FE005FC8B1 /* Assets.xcassets */; }; - E3ED324A20E439FE005FC8B1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E3ED324820E439FE005FC8B1 /* LaunchScreen.storyboard */; }; + E3ED324A20E439FE005FC8B1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E3ED324820E439FE005FC8B1 /* LaunchScreen.storyboard */; platformFilters = (ios, maccatalyst, ); }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 074103962C8B3E8600E96480 /* RowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowItem.swift; sourceTree = ""; }; + 0741039B2C8B441B00E96480 /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; + 07AE54FD2C82305600E1FCBD /* Inventory.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Inventory.entitlements; sourceTree = ""; }; + 07AE554C2C87721300E1FCBD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; CC371EF329794A9700430C6F /* DittoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DittoManager.swift; sourceTree = ""; }; CC371EF52979573200430C6F /* ItemDittoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDittoModel.swift; sourceTree = ""; }; - CC481D5E24B073230007C4D2 /* DittoSDKInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DittoSDKInfoViewController.swift; sourceTree = ""; }; - CC481D5F24B073230007C4D2 /* DittoInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DittoInfoViewController.swift; sourceTree = ""; }; - CC481D6024B073230007C4D2 /* DittoInfoView.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DittoInfoView.storyboard; sourceTree = ""; }; CC626BF6298A655B0065E143 /* Env.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = ""; }; - CCD3A727297EA27F00A7EFED /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; - CCF7CB1724FED62300B5ED70 /* BackgroundSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundSync.swift; sourceTree = ""; }; - E32461E920E47D400006C628 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; E32461EB20E47DFC0006C628 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - E32461EF20E488560006C628 /* ItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTableViewCell.swift; sourceTree = ""; }; E3ED323C20E439FD005FC8B1 /* Inventory.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Inventory.app; sourceTree = BUILT_PRODUCTS_DIR; }; E3ED323F20E439FD005FC8B1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E3ED324620E439FE005FC8B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -54,13 +55,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0EC95D272B0BE9D8006A35A0 /* DittoDiskUsage in Frameworks */, - 0EC95D2B2B0BE9D8006A35A0 /* DittoExportLogs in Frameworks */, - 0EC95D252B0BE9D8006A35A0 /* DittoDataBrowser in Frameworks */, + 07AE55372C876C5A00E1FCBD /* DittoDiskUsage in Frameworks */, + 07AE55332C876C5A00E1FCBD /* DittoAllToolsMenu in Frameworks */, + 07AE553F2C876C5A00E1FCBD /* DittoHeartbeat in Frameworks */, + 07AE55412C876C5A00E1FCBD /* DittoPeersList in Frameworks */, + 07AE55472C876C5A00E1FCBD /* DittoPresenceViewer in Frameworks */, + 07AE55432C876C5A00E1FCBD /* DittoPermissionsHealth in Frameworks */, + 07AE553B2C876C5A00E1FCBD /* DittoExportLogs in Frameworks */, 0EC95D322B0BEA1D006A35A0 /* DittoObjC in Frameworks */, CCE8646529E59037007D822C /* Cartography in Frameworks */, - 0EC95D2F2B0BE9D8006A35A0 /* DittoPresenceViewer in Frameworks */, 0EC95D342B0BEA1D006A35A0 /* DittoSwift in Frameworks */, + 07AE55352C876C5A00E1FCBD /* DittoDataBrowser in Frameworks */, + 07AE55452C876C5A00E1FCBD /* DittoPresenceDegradation in Frameworks */, + 07AE55392C876C5A00E1FCBD /* DittoExportData in Frameworks */, + 07AE553D2C876C5A00E1FCBD /* DittoHealthMetrics in Frameworks */, + 0741039A2C8B3F4800E96480 /* DittoAllToolsMenu in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -74,17 +83,6 @@ name = Frameworks; sourceTree = ""; }; - CC481D5D24B073230007C4D2 /* DittoInfoView */ = { - isa = PBXGroup; - children = ( - CC481D5E24B073230007C4D2 /* DittoSDKInfoViewController.swift */, - CC481D5F24B073230007C4D2 /* DittoInfoViewController.swift */, - CC481D6024B073230007C4D2 /* DittoInfoView.storyboard */, - CCF7CB1724FED62300B5ED70 /* BackgroundSync.swift */, - ); - path = DittoInfoView; - sourceTree = ""; - }; E3ED323320E439FD005FC8B1 = { isa = PBXGroup; children = ( @@ -105,18 +103,18 @@ E3ED323E20E439FD005FC8B1 /* Inventory */ = { isa = PBXGroup; children = ( - CC481D5D24B073230007C4D2 /* DittoInfoView */, + 07AE54FD2C82305600E1FCBD /* Inventory.entitlements */, E3ED323F20E439FD005FC8B1 /* AppDelegate.swift */, E3ED324620E439FE005FC8B1 /* Assets.xcassets */, E3ED324820E439FE005FC8B1 /* LaunchScreen.storyboard */, E3ED324B20E439FF005FC8B1 /* Info.plist */, - E32461E920E47D400006C628 /* MainViewController.swift */, E32461EB20E47DFC0006C628 /* Constants.swift */, - E32461EF20E488560006C628 /* ItemTableViewCell.swift */, CC371EF329794A9700430C6F /* DittoManager.swift */, CC371EF52979573200430C6F /* ItemDittoModel.swift */, - CCD3A727297EA27F00A7EFED /* Extensions.swift */, CC626BF6298A655B0065E143 /* Env.swift */, + 07AE554C2C87721300E1FCBD /* ContentView.swift */, + 074103962C8B3E8600E96480 /* RowItem.swift */, + 0741039B2C8B441B00E96480 /* Strings.swift */, ); path = Inventory; sourceTree = ""; @@ -140,12 +138,20 @@ name = Inventory; packageProductDependencies = ( CCE8646429E59037007D822C /* Cartography */, - 0EC95D242B0BE9D8006A35A0 /* DittoDataBrowser */, - 0EC95D262B0BE9D8006A35A0 /* DittoDiskUsage */, - 0EC95D2A2B0BE9D8006A35A0 /* DittoExportLogs */, - 0EC95D2E2B0BE9D8006A35A0 /* DittoPresenceViewer */, 0EC95D312B0BEA1D006A35A0 /* DittoObjC */, 0EC95D332B0BEA1D006A35A0 /* DittoSwift */, + 07AE55322C876C5A00E1FCBD /* DittoAllToolsMenu */, + 07AE55342C876C5A00E1FCBD /* DittoDataBrowser */, + 07AE55362C876C5A00E1FCBD /* DittoDiskUsage */, + 07AE55382C876C5A00E1FCBD /* DittoExportData */, + 07AE553A2C876C5A00E1FCBD /* DittoExportLogs */, + 07AE553C2C876C5A00E1FCBD /* DittoHealthMetrics */, + 07AE553E2C876C5A00E1FCBD /* DittoHeartbeat */, + 07AE55402C876C5A00E1FCBD /* DittoPeersList */, + 07AE55422C876C5A00E1FCBD /* DittoPermissionsHealth */, + 07AE55442C876C5A00E1FCBD /* DittoPresenceDegradation */, + 07AE55462C876C5A00E1FCBD /* DittoPresenceViewer */, + 074103992C8B3F4800E96480 /* DittoAllToolsMenu */, ); productName = Inventory; productReference = E3ED323C20E439FD005FC8B1 /* Inventory.app */; @@ -182,8 +188,8 @@ mainGroup = E3ED323320E439FD005FC8B1; packageReferences = ( CCE8646329E59037007D822C /* XCRemoteSwiftPackageReference "Cartography" */, - 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */, 0EC95D302B0BEA1D006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftPackage" */, + 074103982C8B3F4800E96480 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */, ); productRefGroup = E3ED323D20E439FD005FC8B1 /* Products */; projectDirPath = ""; @@ -199,7 +205,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CC481D6E24B073230007C4D2 /* DittoInfoView.storyboard in Resources */, E3ED324A20E439FE005FC8B1 /* LaunchScreen.storyboard in Resources */, E3ED324720E439FE005FC8B1 /* Assets.xcassets in Resources */, ); @@ -216,11 +221,14 @@ inputFileListPaths = ( ); inputPaths = ( + "$(SRCROOT)/buildEnv.sh", + "${SRCROOT}/.env", ); name = "Generate Env.swift"; outputFileListPaths = ( ); outputPaths = ( + "${SRCROOT}/${PROJECT}/Env.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -233,17 +241,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CC481D6C24B073230007C4D2 /* DittoSDKInfoViewController.swift in Sources */, - E32461EA20E47D400006C628 /* MainViewController.swift in Sources */, + 07AE554D2C87721300E1FCBD /* ContentView.swift in Sources */, E3ED324020E439FD005FC8B1 /* AppDelegate.swift in Sources */, - CCF7CB1824FED62300B5ED70 /* BackgroundSync.swift in Sources */, + 0741039C2C8B441B00E96480 /* Strings.swift in Sources */, CC371EF62979573200430C6F /* ItemDittoModel.swift in Sources */, - CCD3A728297EA27F00A7EFED /* Extensions.swift in Sources */, - E32461F020E488560006C628 /* ItemTableViewCell.swift in Sources */, E32461EC20E47DFC0006C628 /* Constants.swift in Sources */, - CC481D6D24B073230007C4D2 /* DittoInfoViewController.swift in Sources */, CC371EF429794A9700430C6F /* DittoManager.swift in Sources */, CC626BF7298A655B0065E143 /* Env.swift in Sources */, + 074103972C8B3E8600E96480 /* RowItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -381,6 +386,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Inventory/Inventory.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -401,8 +407,10 @@ PRODUCT_BUNDLE_IDENTIFIER = com.dittolive.InventoryP2PTest; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; }; name = Debug; }; @@ -410,6 +418,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Inventory/Inventory.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; @@ -431,8 +440,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore *"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore * 1718532340"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; }; name = Release; }; @@ -460,12 +471,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */ = { + 074103982C8B3F4800E96480 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/getditto/DittoSwiftTools"; + repositoryURL = "https://github.com/getditto/DittoSwiftTools.git"; requirement = { - branch = v4; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 6.0.0; }; }; 0EC95D302B0BEA1D006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftPackage" */ = { @@ -473,7 +484,7 @@ repositoryURL = "https://github.com/getditto/DittoSwiftPackage"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.7.0; + minimumVersion = 4.8.0; }; }; CCE8646329E59037007D822C /* XCRemoteSwiftPackageReference "Cartography" */ = { @@ -487,24 +498,53 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 0EC95D242B0BE9D8006A35A0 /* DittoDataBrowser */ = { + 074103992C8B3F4800E96480 /* DittoAllToolsMenu */ = { + isa = XCSwiftPackageProductDependency; + package = 074103982C8B3F4800E96480 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */; + productName = DittoAllToolsMenu; + }; + 07AE55322C876C5A00E1FCBD /* DittoAllToolsMenu */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoAllToolsMenu; + }; + 07AE55342C876C5A00E1FCBD /* DittoDataBrowser */ = { isa = XCSwiftPackageProductDependency; - package = 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */; productName = DittoDataBrowser; }; - 0EC95D262B0BE9D8006A35A0 /* DittoDiskUsage */ = { + 07AE55362C876C5A00E1FCBD /* DittoDiskUsage */ = { isa = XCSwiftPackageProductDependency; - package = 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */; productName = DittoDiskUsage; }; - 0EC95D2A2B0BE9D8006A35A0 /* DittoExportLogs */ = { + 07AE55382C876C5A00E1FCBD /* DittoExportData */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoExportData; + }; + 07AE553A2C876C5A00E1FCBD /* DittoExportLogs */ = { isa = XCSwiftPackageProductDependency; - package = 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */; productName = DittoExportLogs; }; - 0EC95D2E2B0BE9D8006A35A0 /* DittoPresenceViewer */ = { + 07AE553C2C876C5A00E1FCBD /* DittoHealthMetrics */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoHealthMetrics; + }; + 07AE553E2C876C5A00E1FCBD /* DittoHeartbeat */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoHeartbeat; + }; + 07AE55402C876C5A00E1FCBD /* DittoPeersList */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoPeersList; + }; + 07AE55422C876C5A00E1FCBD /* DittoPermissionsHealth */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoPermissionsHealth; + }; + 07AE55442C876C5A00E1FCBD /* DittoPresenceDegradation */ = { + isa = XCSwiftPackageProductDependency; + productName = DittoPresenceDegradation; + }; + 07AE55462C876C5A00E1FCBD /* DittoPresenceViewer */ = { isa = XCSwiftPackageProductDependency; - package = 0EC95D232B0BE9D8006A35A0 /* XCRemoteSwiftPackageReference "DittoSwiftTools" */; productName = DittoPresenceViewer; }; 0EC95D312B0BEA1D006A35A0 /* DittoObjC */ = { diff --git a/iOS/Inventory.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iOS/Inventory.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 42feb79..24e832f 100644 --- a/iOS/Inventory.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iOS/Inventory.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e46e5400f3661256bd0197e1612c81282831b17087168009e217d707f188d805", + "originHash" : "09ffcf3308b792bb9a632f175aa47929f479bf53a008132242e1e081b175a854", "pins" : [ { "identity" : "cartography", @@ -15,17 +15,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/getditto/DittoSwiftPackage", "state" : { - "revision" : "8f32d91b4a8d903e2e1da9abdae9f685c152f65d", - "version" : "4.7.2" + "revision" : "fb4ee210d85380084f9bdd26cd5bb987db971da2", + "version" : "4.8.0" } }, { "identity" : "dittoswifttools", "kind" : "remoteSourceControl", - "location" : "https://github.com/getditto/DittoSwiftTools", + "location" : "https://github.com/getditto/DittoSwiftTools.git", "state" : { - "branch" : "v4", - "revision" : "600e3b82dbdf5d70ee35d1b470f3e47f2b69bb54" + "revision" : "756a3bf761da352646851f7d45481dc4a790d0d2", + "version" : "6.0.0" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", - "version" : "1.1.1" + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" } } ], diff --git a/iOS/Inventory/AppDelegate.swift b/iOS/Inventory/AppDelegate.swift index 23d8175..b65de42 100644 --- a/iOS/Inventory/AppDelegate.swift +++ b/iOS/Inventory/AppDelegate.swift @@ -7,6 +7,7 @@ // import UIKit +import SwiftUI @UIApplicationMain final class AppDelegate: UIResponder, UIApplicationDelegate { @@ -14,19 +15,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let navigationController = UINavigationController(rootViewController: MainViewController()) - navigationController.navigationBar.prefersLargeTitles = true window?.tintColor = Constants.Colors.mainColor - window!.rootViewController = navigationController + window!.rootViewController = UIHostingController(rootView: ContentView()) window!.makeKeyAndVisible() return true } - - func applicationWillResignActive(_ application: UIApplication) { - BackgroundSync.shared.start() - } - - func applicationWillEnterForeground(_ application: UIApplication) { - BackgroundSync.shared.stop() - } } diff --git a/iOS/Inventory/Assets.xcassets/blt.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/blt.imageset/Contents.json index 748906a..a1138d4 100644 --- a/iOS/Inventory/Assets.xcassets/blt.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/blt.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "blt.png", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Assets.xcassets/brownies.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/brownies.imageset/Contents.json index 9687106..fe8b336 100644 --- a/iOS/Inventory/Assets.xcassets/brownies.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/brownies.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "brownies.jpg", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Assets.xcassets/coke.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/coke.imageset/Contents.json index 6cb7a2c..987ccda 100644 --- a/iOS/Inventory/Assets.xcassets/coke.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/coke.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "Coke.jpg", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Assets.xcassets/drpepper.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/drpepper.imageset/Contents.json index de4b9a1..bfc175f 100644 --- a/iOS/Inventory/Assets.xcassets/drpepper.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/drpepper.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "drpepper.jpeg", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Assets.xcassets/inventory_launch.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/inventory_launch.imageset/Contents.json index 7a181ed..52e103e 100644 --- a/iOS/Inventory/Assets.xcassets/inventory_launch.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/inventory_launch.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "inventory_launch@3x.png", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Assets.xcassets/lays.imageset/Contents.json b/iOS/Inventory/Assets.xcassets/lays.imageset/Contents.json index 3156c4f..f72d917 100644 --- a/iOS/Inventory/Assets.xcassets/lays.imageset/Contents.json +++ b/iOS/Inventory/Assets.xcassets/lays.imageset/Contents.json @@ -1,21 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", "filename" : "lays.jpg", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Inventory/Constants.swift b/iOS/Inventory/Constants.swift index 16c51b4..1502f34 100644 --- a/iOS/Inventory/Constants.swift +++ b/iOS/Inventory/Constants.swift @@ -9,7 +9,6 @@ import UIKit struct Constants { - struct Colors { static let mainColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1) static let lightHighlightColor = UIColor(red: 210/255, green: 224/255, blue: 253/255, alpha: 1) diff --git a/iOS/Inventory/ContentView.swift b/iOS/Inventory/ContentView.swift new file mode 100644 index 0000000..5e44414 --- /dev/null +++ b/iOS/Inventory/ContentView.swift @@ -0,0 +1,60 @@ +// +// ContentView.swift +// Inventory +// +// Created by Erik Everson on 9/3/24. +// Copyright © 2024 Ditto. All rights reserved. +// + +import SwiftUI +import DittoAllToolsMenu +import DittoSwift + +struct ContentView: View { + @ObservedObject var dittoManager = DittoManager.shared + + // Items to initially populate data. These will not be used after data has been populated. + private var viewItems: [ItemDittoModel] { + return [ + ItemDittoModel(_id: "coke", counter: DittoCounter(), itemId: 0, imageName: "coke", title: "Coca-Cola", price: 2.50, detail: "A Can of Coca-Cola"), + ItemDittoModel(_id: "drpepper", counter: DittoCounter(), itemId: 1, imageName: "drpepper", title: "Dr. Pepper", price: 2.50, detail: "A Can of Dr. Pepper"), + ItemDittoModel(_id: "lays", counter: DittoCounter(), itemId: 2, imageName: "lays", title: "Lay's Classic", price: 3.99, detail: "Original Classic Lay's Bag of Chips"), + ItemDittoModel(_id: "brownies", counter: DittoCounter(), itemId: 3, imageName: "brownies", title: "Brownies", price: 6.50, detail: "Brownies, Diet Sugar Free Version"), + ItemDittoModel(_id: "blt", counter: DittoCounter(), itemId: 4, imageName: "blt", title: "Classic BLT Egg", price: 2.50, detail: "Contains Egg, Meats and Dairy") + ] + } + + var body: some View { + NavigationView { + List { + ForEach(dittoManager.items) { item in + RowItem(dittoManager: dittoManager, item: item, count: item.counter.value) + } + } +#if os(tvOS) + .listStyle(.grouped) +#else + .listStyle(.insetGrouped) +#endif + .navigationTitle("Inventory") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + NavigationLink { + AllToolsMenu(ditto: DittoManager.shared.ditto) + } label: { + Image(systemName: "info.circle") + } + } + } + .onAppear { + dittoManager.prepopulateItemsIfAbsent(items: viewItems) + dittoManager.subscribeAllInventoryItems() + } + } + .navigationViewStyle(.stack) + } +} + +#Preview { + ContentView() +} diff --git a/iOS/Inventory/DittoInfoView/BackgroundSync.swift b/iOS/Inventory/DittoInfoView/BackgroundSync.swift deleted file mode 100644 index 5dbd745..0000000 --- a/iOS/Inventory/DittoInfoView/BackgroundSync.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation -import AVFoundation - -/// For playing empty music to keep the app active -final public class BackgroundSync { - public static let shared = BackgroundSync() - private let player: AVAudioPlayer - private let base64AudioString = "UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAD8/w==" - - public var isOn = false - - private init() { - let audioData = Data(base64Encoded: base64AudioString)! - try! AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, - mode: .default, - options: .mixWithOthers) - try! AVAudioSession.sharedInstance().setActive(true) - player = try! AVAudioPlayer(data: audioData, fileTypeHint: "wav") - player.numberOfLoops = -1 - player.volume = 0.01 - player.prepareToPlay() - } - - public func start() { - guard isOn else { return } - - NotificationCenter.default.addObserver(self, selector: #selector(interuptedAudio), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance()) - self.player.play() - print("BackgroundSync started") - } - - public func stop() { - NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil) - if player.isPlaying { - player.stop() - print("BackgroundSync stopped") - } - } - - @objc private func interuptedAudio(_ notification: Notification) { - if notification.name == AVAudioSession.interruptionNotification && notification.userInfo != nil { - let info = notification.userInfo! - var intValue = 0 - (info[AVAudioSessionInterruptionTypeKey]! as AnyObject).getValue(&intValue) - if intValue == 1 { self.player.play() } - } - } -} diff --git a/iOS/Inventory/DittoInfoView/DittoInfoView.storyboard b/iOS/Inventory/DittoInfoView/DittoInfoView.storyboard deleted file mode 100644 index 3a32da1..0000000 --- a/iOS/Inventory/DittoInfoView/DittoInfoView.storyboard +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Inventory/DittoInfoView/DittoInfoViewController.swift b/iOS/Inventory/DittoInfoView/DittoInfoViewController.swift deleted file mode 100644 index f643b81..0000000 --- a/iOS/Inventory/DittoInfoView/DittoInfoViewController.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// DittoInfoViewController.swift -// ToDo -// -// Created by kndoshn on 2020/07/02. -// Copyright © 2020 DittoLive Incorporated. All rights reserved. -// - -import UIKit -import DittoSwift -import DittoPresenceViewer -import DittoExportLogs -import SwiftUI - -public struct DittoInfoViewFactory { - private init() {} - static func create(ditto: Ditto, bundle: Bundle = Bundle.main) -> DittoInfoViewController { - let vc = DittoInfoViewController.instantiate() - vc.ditto = ditto - vc.bundle = bundle - return vc - } -} - -fileprivate enum CellInfo: String, CaseIterable { - case presenceView = "Presence View" - case sdkInfo = "Ditto SDK Info" - case prolonged = "Prolonged Background Sync" - case exportLogs = "Export Logs" - - - var index: Int { - return CellInfo.allCases.firstIndex(of: self)! - } - - var accessoryType: UITableViewCell.AccessoryType { - switch self { - case .presenceView, .sdkInfo: - return .disclosureIndicator - case .prolonged: - return BackgroundSync.shared.isOn ? .checkmark : .none - case .exportLogs: - return.none - } - } -} - -final class DittoInfoViewController: UIViewController { - @IBOutlet private weak var tableView: UITableView! - fileprivate var ditto: Ditto! - fileprivate var bundle: Bundle! - - fileprivate static func instantiate() -> Self { - let sb = UIStoryboard(name: String(describing: "DittoInfoView"), bundle: Bundle(for: DittoInfoViewController.self)) - let vc = sb.instantiateViewController(withIdentifier: String(describing: self)) - return vc as! Self - - } - - override func viewDidLoad() { - super.viewDidLoad() - - DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { - self.navigationItem.largeTitleDisplayMode = .never - self.navigationController?.navigationBar.prefersLargeTitles = false - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - if let selected = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: selected, animated: true) - } - } -} - -extension DittoInfoViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return CellInfo.allCases.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "dittoInfoCell", for: indexPath) - let info = toInfo(indexPath) - cell.textLabel?.text = info.rawValue - cell.accessoryType = info.accessoryType - - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let info = toInfo(indexPath) - switch info { - case .presenceView: - present(DittoPresenceView(ditto: ditto).viewController, animated: true) { - if let selected = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: selected, animated: true) - } - } - case .sdkInfo: - let storyboard = UIStoryboard(name: "DittoInfoView", bundle: Bundle(for: DittoInfoViewController.self)) - let destination = storyboard.instantiateViewController(withIdentifier: "DittoSDKInfoViewController") as! DittoSDKInfoViewController - destination.ditto = ditto - navigationController?.pushViewController(destination, animated: true) - case .prolonged: - BackgroundSync.shared.isOn = !BackgroundSync.shared.isOn - tableView.reloadRows(at: [indexPath], with: .automatic) - case .exportLogs: - shareDittoLogs() - } - } - - func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return 80 - } - - func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - let footer = UILabel() - let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String - let build = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as! String - footer.text = "App Version: \(version)(\(build))" - footer.textAlignment = .center - footer.textColor = .darkGray - return footer - } - - private func toInfo(_ indexPath: IndexPath) -> CellInfo { - return CellInfo.allCases[indexPath.row] - } - - private func shareDittoLogs() { - let alert = UIAlertController(title: "Export Logs", message: "Compressing the logs may take a few seconds.", preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: "Export", style: .default) { [weak self] _ in - self?.exportLogs() - }) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - - present(alert, animated: true) - } - - private func exportLogs() { - - let vc = UIHostingController(rootView: ExportLogs()) - - present(vc, animated: true) - } -} diff --git a/iOS/Inventory/DittoInfoView/DittoSDKInfoViewController.swift b/iOS/Inventory/DittoInfoView/DittoSDKInfoViewController.swift deleted file mode 100644 index 365de85..0000000 --- a/iOS/Inventory/DittoInfoView/DittoSDKInfoViewController.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// DittoSDKInfoViewController.swift -// ToDo -// -// Created by kndoshn on 2020/07/02. -// Copyright © 2020 DittoLive Incorporated. All rights reserved. -// - -import UIKit -import DittoSwift - -final class DittoSDKInfoViewController: UIViewController { - @IBOutlet private weak var textView: UITextView! - var ditto: Ditto! - - override func viewDidLoad() { - super.viewDidLoad() - - let sdkVersion = ditto.sdkVersion - let platform = sdkVersion.prefix(4) - let versions = sdkVersion.dropFirst(4).split(separator: "_") - let semVer = String(versions[0]) - let commitHash = String(versions[1]) - - textView.text = """ - Platform: \(platform) - SDK Version: \(semVer) - Commit Hash: \(commitHash) - """ - - } -} diff --git a/iOS/Inventory/DittoManager.swift b/iOS/Inventory/DittoManager.swift index fdb1ced..f6f0115 100644 --- a/iOS/Inventory/DittoManager.swift +++ b/iOS/Inventory/DittoManager.swift @@ -13,7 +13,9 @@ import Combine // MARK: - Class Implementation -final class DittoManager { +/// Singleton implementation of the DittoManager. +/// This class keeps the persistant connection with Ditto and allows any view to be able to interact with Ditto when it needs to +final class DittoManager: ObservableObject { // MARK: - Ditto Collections @@ -21,17 +23,13 @@ final class DittoManager { let inventories: DittoCollection init(_ store: DittoStore) { - self.inventories = store.collection(ItemDittoModel.collectionName) + self.inventories = store.collection(collectionNameKey) } } // MARK: - Models for Views - struct Models { - var items = [ItemDittoModel]() - } - - private(set) var models = Models() + @Published var items = [ItemDittoModel]() // MARK: - Singleton object @@ -39,15 +37,12 @@ final class DittoManager { // MARK: - Private Ditto properties - private lazy var ditto: Ditto! = { startDitto() }() + lazy var ditto: Ditto! = { startDitto() }() private lazy var collections = { Collections(ditto.store) }() private var subscriptions = [DittoSyncSubscription]() private var liveQueries = [DittoLiveQuery]() - // MARK: - Combine Subjects (to be observed from outside of this class) - let itemsUpdated = PassthroughSubject<(indices: [Int], event: DittoLiveQueryEvent), Never>() - // constructor is private because this is a singleton class private init() {} @@ -59,10 +54,6 @@ final class DittoManager { do { // Disable sync with V3 Ditto try ditto.disableSyncWithV3() - // Disable avoid_redundant_bluetooth - Task { - try await ditto.store.execute(query: "ALTER SYSTEM SET mesh_chooser_avoid_redundant_bluetooth = false") - } try ditto.startSync() } catch { let dittoErr = (error as? DittoSwiftError)?.errorDescription @@ -71,10 +62,6 @@ final class DittoManager { return ditto } - - var dittoInfoView: DittoInfoViewController { - DittoInfoViewFactory.create(ditto: ditto) - } } @@ -82,46 +69,30 @@ final class DittoManager { extension DittoManager { - func subscribeAllInventoryItems() { + /// Creates a subscription to sync changes with other peers in the mesh. + /// Adds a live query which will load the now synced data into memory so that views can use it. + func subscribeAllInventoryItems() { do { - subscriptions.append(try ditto.sync.registerSubscription(query: "SELECT * FROM inventories")) + subscriptions.append(try ditto.sync.registerSubscription(query: "SELECT * FROM \(collectionNameKey)")) } catch { print("Query Error: \(error)") } - + liveQueries.append( collections.inventories.findAll().observeLocal { [weak self] docs, event in - guard let self = self else { return } - - let allItems = docs.map { ItemDittoModel($0) } - self.models.items = allItems - - switch event { - case .initial: - - self.itemsUpdated.send((indices: allItems.indexes, event: event)) - - case .update(let detail): - - self.itemsUpdated.send((indices: detail.updates, event: event)) - - @unknown default: break - } + self?.items = docs.map { ItemDittoModel($0) } } ) } - func prepopulateItemsIfAbsent(itemIds: [Int]) { - - let allItems = itemIds.map { - ["_id": $0, - "counter": DittoCounter()] - } + /// Takes the hard coded documents and adds them if they do not already exist in the local store. + /// - Parameter items: The Ditto items that will be used in the views. + func prepopulateItemsIfAbsent(items: [ItemDittoModel]) { ditto.store.write { transaction in - let scope = transaction.scoped(toCollectionNamed: ItemDittoModel.collectionName) - allItems.forEach { + let scope = transaction.scoped(toCollectionNamed: collectionNameKey) + items.map{ $0.document() }.forEach { do { try scope.upsert($0, writeStrategy: .insertDefaultIfAbsent) } catch { @@ -133,17 +104,21 @@ extension DittoManager { } } - func incrementCounterFor(id: Int) { + /// Adds one to the Ditto counter on the Ditto document. This will update the local DB and then automatically sync the change with other peers if it can. + /// - Parameter id: The id of the document to update + func incrementCounterFor(id: String) { collections.inventories.findByID(id).update { doc in - doc?["counter"].counter?.increment(by: 1) + doc?[counterKey].counter?.increment(by: 1) } } - func decrementCounterFor(id: Int) { + //// Subtracts one from the Ditto counter on the Ditto document. This will update the local DB and then automatically sync the change with other peers if it can. + /// - Parameter id: The id of the document to update + func decrementCounterFor(id: String) { collections.inventories.findByID(id).update { doc in - doc?["counter"].counter?.increment(by: -1) + doc?[counterKey].counter?.increment(by: -1) } } } diff --git a/iOS/Inventory/Extensions.swift b/iOS/Inventory/Extensions.swift deleted file mode 100644 index 98d9232..0000000 --- a/iOS/Inventory/Extensions.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Extensions.swift -// Inventory -// -// Created by Shunsuke Kondo on 2023/01/23. -// Copyright © 2023 Ditto. All rights reserved. -// - -import Foundation - -extension Array { - - var indexes: [Int] { - return (Array)(indices) - } -} diff --git a/iOS/Inventory/Inventory.entitlements b/iOS/Inventory/Inventory.entitlements new file mode 100644 index 0000000..a476370 --- /dev/null +++ b/iOS/Inventory/Inventory.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.bluetooth + + com.apple.security.network.client + + + diff --git a/iOS/Inventory/ItemDittoModel.swift b/iOS/Inventory/ItemDittoModel.swift index 6a6d145..a5588cb 100644 --- a/iOS/Inventory/ItemDittoModel.swift +++ b/iOS/Inventory/ItemDittoModel.swift @@ -11,27 +11,48 @@ import DittoSwift /* This is a model to store in Ditto database. - We don't store as `ItemViewModel` because some of the data don't need to be transmitted. - For this case, Ditto only syncs its ID and counter. + We store all of the data into Ditto even though the only data that will change is the counter. + For this case, Ditto could also only sync its ID and counter. Separating models for Ditto and views could make sync performance better. */ -struct ItemDittoModel { - - // MARK: - Collection Name - - static let collectionName = "inventories" +/// The item model that ditto will be replicated between Ditto Peers. +struct ItemDittoModel: Identifiable { // MARK: - Properties + var id: String { _id } - let _id: Int + let _id: String let counter: DittoCounter + let itemId: Int + let imageName: String + let title: String + let price: Double + let detail: String +} - // MARK: - Initialization +extension ItemDittoModel { + // MARK: - Initialization from Ditto object init(_ doc: DittoDocument) { - self._id = doc["_id"].intValue - self.counter = doc["counter"].counter ?? DittoCounter() + self._id = doc[_idKey].stringValue + self.counter = doc[counterKey].counter ?? DittoCounter() + self.itemId = doc[itemIdKey].intValue + self.imageName = doc[imageNameKey].stringValue + self.title = doc[titleKey].stringValue + self.price = doc[priceKey].doubleValue + self.detail = doc[detailKey].stringValue } + func document() -> [String: Any?] { + return [ + _idKey: _id, + counterKey: counter, + itemIdKey: itemId, + imageNameKey: imageName, + titleKey: title, + priceKey: price, + detailKey: detail, + ] + } } diff --git a/iOS/Inventory/ItemTableViewCell.swift b/iOS/Inventory/ItemTableViewCell.swift deleted file mode 100644 index 0bd068b..0000000 --- a/iOS/Inventory/ItemTableViewCell.swift +++ /dev/null @@ -1,211 +0,0 @@ -// -// ItemTableViewCell.swift -// Inventory -// -// Created by Ditto on 6/27/18. -// Copyright © 2018 Ditto. All rights reserved. -// - -import UIKit -import Cartography - -final class ItemViewModel { - - let itemId: Int - let image: UIImage? - let title: String - let price: Double - let detail: String - var count: Int = 0 - - init(itemId: Int, image: UIImage?, title: String, price: Double, detail: String) { - self.itemId = itemId; - self.image = image - self.title = title - self.price = price - self.detail = detail - } -} - -protocol ItemTableViewCellDelegate: AnyObject { - func plusButtonDidClick(itemId: Int) - func minusButtonDidClick(itemId: Int) -} - - -final class ItemTableViewCell: UITableViewCell { - - static let REUSE_ID = "ItemTableViewCell" - static let HEIGHT: CGFloat = 150 - - weak var delegate: ItemTableViewCellDelegate? - var item: ItemViewModel? - - lazy var itemImageView: UIImageView = { - let i = UIImageView() - i.backgroundColor = .gray - i.contentMode = .scaleAspectFill - i.layer.cornerRadius = 5.0 - i.layer.masksToBounds = true - return i - }() - - lazy var itemTitleLabel: UILabel = { - let label = UILabel() - label.font = UIFont.boldSystemFont(ofSize: 25) - label.numberOfLines = 0 - return label - }() - - lazy var itemCounterLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.numberOfLines = 0 - label.font = UIFont.boldSystemFont(ofSize: 25) - return label - }() - - lazy var plusButton: UIButton = { - let button = UIButton() - button.setTitle("+", for: .normal) - button.backgroundColor = Constants.Colors.mainColor - button.layer.cornerRadius = 5.0 - button.layer.masksToBounds = true - button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30) - return button - }() - - lazy var minusButton: UIButton = { - let button = UIButton() - button.setTitle("-", for: .normal) - button.backgroundColor = Constants.Colors.mainColor - button.layer.cornerRadius = 5.0 - button.layer.masksToBounds = true - button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30) - return button - }() - - var currentCount: Int? - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - contentView.addSubview(itemImageView) - contentView.addSubview(itemTitleLabel) - contentView.addSubview(itemCounterLabel) - - contentView.addSubview(plusButton) - contentView.addSubview(minusButton) - - selectionStyle = .none - constrain([itemImageView, itemTitleLabel, plusButton, minusButton, itemCounterLabel]) { (proxies) in - let itemImageView = proxies[0] - let itemTitleLabel = proxies[1] - let plusButton = proxies[2] - let minusButton = proxies[3] - let itemCounterLabel = proxies[4] - - itemImageView.left == itemImageView.superview!.left + 16 - itemImageView.top == itemImageView.superview!.top + 16 - itemImageView.height == 120 - itemImageView.width == 120 - - plusButton.height == 40 - plusButton.width == 40 - plusButton.bottom == itemImageView.bottom - plusButton.right == plusButton.superview!.right - 16 - - minusButton.height == 40 - minusButton.width == 40 - minusButton.bottom == itemImageView.bottom - minusButton.right == plusButton.left - 16 - - itemCounterLabel.right == plusButton.right - itemCounterLabel.left == minusButton.left - itemCounterLabel.top == itemImageView.top - itemCounterLabel.bottom == plusButton.top - 8 - - itemTitleLabel.left == itemImageView.right + 8 - itemTitleLabel.top == itemImageView.top - itemTitleLabel.bottom == itemImageView.bottom - itemTitleLabel.right == itemCounterLabel.left - 8 - } - - plusButton.addTarget(self, action: #selector(plusButtonDidClick), for: .touchUpInside) - minusButton.addTarget(self, action: #selector(minusButtonDidClick), for: .touchUpInside) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setup(item: ItemViewModel) { - self.item = item - itemImageView.image = item.image - itemTitleLabel.attributedText = { - let attributedText = NSMutableAttributedString() - attributedText.append(NSAttributedString(string: item.title, attributes: [ - NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 25) - ])) - attributedText.append(NSAttributedString(string: "\n\(item.detail.uppercased())", attributes: [ - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: .light), - NSAttributedString.Key.foregroundColor: UIColor(red: 127/255, green: 140/255, blue: 141/255, alpha: 1) - ])) - - attributedText.append(NSAttributedString(string: "\n\(String(format: "$%.02f", item.price))", attributes: [ - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18) - ])) - return attributedText - }() - updateItemCount(count: item.count) - } - - func updateItemCount(count: Int) { - itemCounterLabel.attributedText = { - let attributedText = NSMutableAttributedString() - attributedText.append(NSAttributedString(string: "Quantity", attributes: [ - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: .light), - NSAttributedString.Key.foregroundColor: UIColor(red: 127/255, green: 140/255, blue: 141/255, alpha: 1), - NSAttributedString.Key.paragraphStyle: { - let p = NSMutableParagraphStyle() - p.alignment = .center - return p - }() - ])) - attributedText.append(NSAttributedString(string: "\n\(count)", attributes: [ - NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 25), - NSAttributedString.Key.paragraphStyle: { - let p = NSMutableParagraphStyle() - p.alignment = .center - return p - }() - ])) - return attributedText - }() - } - - func animateBackground() { - UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction, .transitionCrossDissolve], animations: { - self.backgroundColor = Constants.Colors.lightHighlightColor - }, completion: nil) - UIView.animate(withDuration: 0.25, delay: 0.25, options: [.allowUserInteraction, .transitionCrossDissolve], animations: { - self.backgroundColor = .white - }, completion: nil) - } - - @objc func plusButtonDidClick() { - guard let delegate = self.delegate, let item = self.item else { return } - delegate.plusButtonDidClick(itemId: item.itemId) - } - - @objc func minusButtonDidClick() { - guard let delegate = self.delegate, let item = self.item else { return } - delegate.minusButtonDidClick(itemId: item.itemId) - } - - override func prepareForReuse() { - super.prepareForReuse() - self.currentCount = nil - self.item = nil - self.backgroundColor = .white - } -} diff --git a/iOS/Inventory/MainViewController.swift b/iOS/Inventory/MainViewController.swift deleted file mode 100644 index 2ac0fe4..0000000 --- a/iOS/Inventory/MainViewController.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// MainViewController.swift -// Inventory -// -// Created by Ditto on 6/27/18. -// Copyright © 2018 Ditto. All rights reserved. -// - -import UIKit -import Cartography -import Combine - -final class MainViewController: UIViewController { - - // MARK: - Properties - - private let tableView = UITableView() - private var searchController: UISearchController! - - private let dittoManager = DittoManager.shared - private var cancellables = Set() - - private var viewItems: [ItemViewModel] { - return [ - ItemViewModel(itemId: 0, image: UIImage(named: "coke"), title: "Coca-Cola", price: 2.50, detail: "A Can of Coca-Cola"), - ItemViewModel(itemId: 1, image: UIImage(named: "drpepper"), title: "Dr. Pepper", price: 2.50, detail: "A Can of Dr. Pepper"), - ItemViewModel(itemId: 2, image: UIImage(named: "lays"), title: "Lay's Classic", price: 3.99, detail: "Original Classic Lay's Bag of Chips"), - ItemViewModel(itemId: 3, image: UIImage(named: "brownies"), title: "Brownies", price: 6.50, detail: "Brownies, Diet Sugar Free Version"), - ItemViewModel(itemId: 4, image: UIImage(named: "blt"), title: "Classic BLT Egg", price: 2.50, detail: "Contains Egg, Meats and Dairy") - ] - } - - // MARK: - View Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - - setupTableView() - setupNavBar() - populateItems() - observeItems() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - navigationController?.navigationBar.prefersLargeTitles = true - navigationItem.largeTitleDisplayMode = .always - } - - // MARK: - Private Functions - - private func populateItems() { - dittoManager.prepopulateItemsIfAbsent(itemIds: viewItems.indexes) - } - - private func observeItems() { - - dittoManager.subscribeAllInventoryItems() - - dittoManager.itemsUpdated - .sink { [weak self] indices, event in - guard let self = self else { return } - - DispatchQueue.main.async { - - switch event { - case .initial: - self.tableView.reloadData() - - case .update: - self.updateViewFor(indices: indices) - - @unknown default: break - } - } - - }.store(in: &cancellables) - } - - private func updateViewFor(indices: [Int]) { - let indexPaths = indices.map { IndexPath(row: $0, section: 0) } - UIView.performWithoutAnimation { - tableView.reloadRows(at: indexPaths, with: .automatic) - } - indexPaths.forEach { indexPath in - let cell = tableView.cellForRow(at: indexPath) as? ItemTableViewCell - cell?.animateBackground() - } - } - - private func setupNavBar() { - title = "Inventory" - - let infoButton = UIButton(type: .infoLight) - infoButton.addTarget(self, action: #selector(pushToInfoPage), for: .touchUpInside) - let barButtonItem = UIBarButtonItem(customView: infoButton) - navigationItem.leftBarButtonItem = barButtonItem - } - - private func setupTableView() { - view.addSubview(tableView) - constrain(tableView) { (tableView) in - tableView.left == tableView.superview!.left - tableView.right == tableView.superview!.right - tableView.bottom == tableView.superview!.bottom - tableView.top == tableView.superview!.top - } - - tableView.dataSource = self - tableView.delegate = self - tableView.register(ItemTableViewCell.self, forCellReuseIdentifier: ItemTableViewCell.REUSE_ID) - } - - @objc private func pushToInfoPage() { - navigationController?.pushViewController(dittoManager.dittoInfoView, animated: true) - } -} - -// MARK: - ItemTableViewCellDelegate - -extension MainViewController: ItemTableViewCellDelegate { - - func plusButtonDidClick(itemId: Int) { - dittoManager.incrementCounterFor(id: itemId) - UIImpactFeedbackGenerator(style: .medium).impactOccurred() - } - - func minusButtonDidClick(itemId: Int) { - dittoManager.decrementCounterFor(id: itemId) - UIImpactFeedbackGenerator(style: .medium).impactOccurred() - } - -} - - -// MARK: - UITableViewDataSource - -extension MainViewController: UITableViewDataSource { - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: ItemTableViewCell.REUSE_ID, for: indexPath) as! ItemTableViewCell - let item = viewItems[indexPath.row] - item.count = Int(dittoManager.models.items[indexPath.row].counter.value) - cell.setup(item: item) - cell.delegate = self - return cell - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return dittoManager.models.items.count - } - -} - -// MARK: - UITableViewDelegate - -extension MainViewController: UITableViewDelegate { - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return ItemTableViewCell.HEIGHT - } - -} diff --git a/iOS/Inventory/RowItem.swift b/iOS/Inventory/RowItem.swift new file mode 100644 index 0000000..a7769cb --- /dev/null +++ b/iOS/Inventory/RowItem.swift @@ -0,0 +1,120 @@ +// +// RowItem.swift +// Inventory +// +// Created by Erik Everson on 9/6/24. +// Copyright © 2024 Ditto. All rights reserved. +// + +import Foundation +import SwiftUI +import DittoSwift + +struct RowItem: View { + @ObservedObject var dittoManager: DittoManager + var item: ItemDittoModel + @State private var shouldAnimate = false + @State private var backgroundColor = Color.white + var count: Double + + var body: some View { + ZStack { + Rectangle() + .foregroundColor(backgroundColor) + .cornerRadius(10) + .onChange(of: count) { _ in + animateBackground() + } + .animation(.easeInOut(duration: 0.25), value: backgroundColor) // Animation on value change + + HStack(alignment: .top) { + Image(item.imageName, bundle: .main) + .resizable() + .scaledToFit() + .cornerRadius(5) + .padding(5) +#if os(tvOS) + .frame(maxWidth: 225, maxHeight: 225) +#else + .frame(maxWidth: 100, maxHeight: 100) +#endif + + VStack(alignment: .leading) { + Text(item.title) + .font(.title3) + .bold() + Text(item.detail) + .fontWeight(.ultraLight) + .font(.footnote) + Text(String(format: "$%.02f", item.price)) + } + .padding(5) + + Spacer() + + VStack(alignment: .center) { + Text("Quantity") + .fontWeight(.ultraLight) + HStack { + Text("\(Int(count))") + .font(.title3) + .bold() + .padding(.bottom, 10) + } + + HStack { + Button { + dittoManager.decrementCounterFor(id: item._id) +#if !os(tvOS) + UIImpactFeedbackGenerator(style: .medium).impactOccurred() +#endif + } label: { + Text("-") + .bold() + } + .buttonStyle(.borderedProminent) + + Button { + dittoManager.incrementCounterFor(id: item._id) +#if !os(tvOS) + UIImpactFeedbackGenerator(style: .medium).impactOccurred() +#endif + } label: { + Text("+") + .bold() + } + .buttonStyle(.borderedProminent) + } + } + .padding(5) + } + } + .onAppear { + self.shouldAnimate = true + } + } + + /// Animates the background color with a delay + func animateBackground() { + // First animation + withAnimation(.easeInOut(duration: 0.25)) { + self.backgroundColor = Color(Constants.Colors.lightHighlightColor) + } + + // Delay the second animation to cancel out first + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + withAnimation(.easeInOut(duration: 0.25)) { + self.backgroundColor = Color.white + } + } + } +} + +#Preview { + let item = ItemDittoModel(_id: UUID().uuidString, counter: DittoCounter(), itemId: 0, imageName: "coke", title: "Coca-Cola", price: 2.50, detail: "A Can of Coca-Cola") + RowItem( + dittoManager: DittoManager.shared, + item: item, + count: item.counter.value + ) +} diff --git a/iOS/Inventory/Strings.swift b/iOS/Inventory/Strings.swift new file mode 100644 index 0000000..a187308 --- /dev/null +++ b/iOS/Inventory/Strings.swift @@ -0,0 +1,20 @@ +// +// Strings.swift +// Inventory +// +// Created by Erik Everson on 9/6/24. +// Copyright © 2024 Ditto. All rights reserved. +// + +import Foundation + +// MARK: - Static keys + +let collectionNameKey = "inventories" +let _idKey = "_id" +let counterKey = "counter" +let itemIdKey = "itemId" +let imageNameKey = "imageName" +let titleKey = "title" +let priceKey = "price" +let detailKey = "detail"