diff --git a/MacMagazine/Features/SettingsLibrary/Tests/SettingsLibraryTests/DatabaseExtensionsTests.swift b/MacMagazine/Features/SettingsLibrary/Tests/SettingsLibraryTests/DatabaseExtensionsTests.swift index 6f8b8d39..1b3ef620 100644 --- a/MacMagazine/Features/SettingsLibrary/Tests/SettingsLibraryTests/DatabaseExtensionsTests.swift +++ b/MacMagazine/Features/SettingsLibrary/Tests/SettingsLibraryTests/DatabaseExtensionsTests.swift @@ -109,7 +109,6 @@ struct DatabaseExtensionsTests { // Then #expect(customization != nil, "Should return a customization object") #expect(database.fetch(CustomizationDB.self).count == 1, "Should delete duplicates, leaving only one") - #expect(customization?.tabs == [.settings], "Should return the first customization") } // MARK: - Update isPatrao Date Calculation Tests diff --git a/MacMagazine/MacMagazine.xcodeproj/project.pbxproj b/MacMagazine/MacMagazine.xcodeproj/project.pbxproj index bca99bc0..422ae39f 100644 --- a/MacMagazine/MacMagazine.xcodeproj/project.pbxproj +++ b/MacMagazine/MacMagazine.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 88C8B2E72EF476E100044DE9 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BDD424E2EE9E82F00A9BD85 /* WidgetKit.framework */; }; + 88C8B2E82EF476E100044DE9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BDD42502EE9E82F00A9BD85 /* SwiftUI.framework */; }; + 88C8B2F72EF476E200044DE9 /* WatchWidget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 88C8B2E62EF476E100044DE9 /* WatchWidget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 88C8B2FE2EF48DC500044DE9 /* FeedLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 88C8B2FD2EF48DC500044DE9 /* FeedLibrary */; }; 9B024FDC2EC2719700D66056 /* SettingsLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 9B024FDB2EC2719700D66056 /* SettingsLibrary */; }; 9B0283C42EF61A08002611AB /* OnboardingLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 9B0283C32EF61A08002611AB /* OnboardingLibrary */; }; 9B1A43032EE0C14E00E56784 /* PodcastLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1A43022EE0C14E00E56784 /* PodcastLibrary */; }; @@ -20,10 +24,17 @@ 9BCD94C62E9971C0007C44B6 /* MacMagazineLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 9BCD94C52E9971C0007C44B6 /* MacMagazineLibrary */; }; 9BDD424F2EE9E82F00A9BD85 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BDD424E2EE9E82F00A9BD85 /* WidgetKit.framework */; }; 9BDD42512EE9E82F00A9BD85 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BDD42502EE9E82F00A9BD85 /* SwiftUI.framework */; }; - 9BDD42602EE9E83100A9BD85 /* WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9BDD424D2EE9E82F00A9BD85 /* WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9BDD42602EE9E83100A9BD85 /* Widget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9BDD424D2EE9E82F00A9BD85 /* Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 88C8B2F52EF476E200044DE9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B9E9DBC2E986F8400255820 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 88C8B2E52EF476E100044DE9; + remoteInfo = WidgetWatchExtension; + }; 9BA828AA2EEC7AAB00F26FCB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9B9E9DBC2E986F8400255820 /* Project object */; @@ -41,6 +52,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 88C8B2FC2EF476E200044DE9 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 88C8B2F72EF476E200044DE9 /* WatchWidget.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; 9BA828AD2EEC7AAB00F26FCB /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -58,7 +80,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 9BDD42602EE9E83100A9BD85 /* WidgetExtension.appex in Embed Foundation Extensions */, + 9BDD42602EE9E83100A9BD85 /* Widget.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -66,6 +88,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 88C8B2E62EF476E100044DE9 /* WatchWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WatchWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 9B024FDA2EC2717200D66056 /* SettingsLibrary */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SettingsLibrary; sourceTree = ""; }; 9B0283C22EF619D0002611AB /* OnboardingLibrary */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = OnboardingLibrary; sourceTree = ""; }; 9B1A43012EE0C04D00E56784 /* PodcastLibrary */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PodcastLibrary; sourceTree = ""; }; @@ -77,12 +100,26 @@ 9BA8288C2EEC7AAA00F26FCB /* WatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9BB3888E2EE0B2E4007AC71C /* MacMagazine.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MacMagazine.xctestplan; sourceTree = ""; }; 9BCD94C42E997032007C44B6 /* MacMagazineLibrary */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MacMagazineLibrary; sourceTree = ""; }; - 9BDD424D2EE9E82F00A9BD85 /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 9BDD424D2EE9E82F00A9BD85 /* Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Widget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 9BDD424E2EE9E82F00A9BD85 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 9BDD42502EE9E82F00A9BD85 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 88C8B2FB2EF476E200044DE9 /* Exceptions for "WatchWidget" folder in "WatchWidget" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 88C8B2E52EF476E100044DE9 /* WatchWidget */; + }; + 88C8B3052EF577EE00044DE9 /* Exceptions for "WatchApp" folder in "WatchWidget" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + WatchWidgetShared.swift, + ); + target = 88C8B2E52EF476E100044DE9 /* WatchWidget */; + }; 9B9E9DD12E986F8500255820 /* Exceptions for "MacMagazine" folder in "MacMagazine" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -97,16 +134,24 @@ ); target = 9BA8288B2EEC7AAA00F26FCB /* WatchApp */; }; - 9BDD42652EE9E83100A9BD85 /* Exceptions for "Widget" folder in "WidgetExtension" target */ = { + 9BDD42652EE9E83100A9BD85 /* Exceptions for "Widget" folder in "Widget" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, ); - target = 9BDD424C2EE9E82F00A9BD85 /* WidgetExtension */; + target = 9BDD424C2EE9E82F00A9BD85 /* Widget */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 88C8B2E92EF476E100044DE9 /* WatchWidget */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 88C8B2FB2EF476E200044DE9 /* Exceptions for "WatchWidget" folder in "WatchWidget" target */, + ); + path = WatchWidget; + sourceTree = ""; + }; 9B9E9DC62E986F8400255820 /* MacMagazine */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -118,13 +163,16 @@ }; 9BA8288D2EEC7AAA00F26FCB /* WatchApp */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 88C8B3052EF577EE00044DE9 /* Exceptions for "WatchApp" folder in "WatchWidget" target */, + ); path = WatchApp; sourceTree = ""; }; 9BDD42522EE9E82F00A9BD85 /* Widget */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 9BDD42652EE9E83100A9BD85 /* Exceptions for "Widget" folder in "WidgetExtension" target */, + 9BDD42652EE9E83100A9BD85 /* Exceptions for "Widget" folder in "Widget" target */, ); path = Widget; sourceTree = ""; @@ -132,6 +180,16 @@ /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ + 88C8B2E32EF476E100044DE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88C8B2FE2EF48DC500044DE9 /* FeedLibrary in Frameworks */, + 88C8B2E82EF476E100044DE9 /* SwiftUI.framework in Frameworks */, + 88C8B2E72EF476E100044DE9 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9B9E9DC12E986F8400255820 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -176,6 +234,7 @@ 9B9E9DC62E986F8400255820 /* MacMagazine */, 9BDD42522EE9E82F00A9BD85 /* Widget */, 9BA8288D2EEC7AAA00F26FCB /* WatchApp */, + 88C8B2E92EF476E100044DE9 /* WatchWidget */, 9B9E9DDE2E98709000255820 /* Frameworks */, 9B9E9DC52E986F8400255820 /* Products */, ); @@ -185,8 +244,9 @@ isa = PBXGroup; children = ( 9B9E9DC42E986F8400255820 /* MacMagazine.app */, - 9BDD424D2EE9E82F00A9BD85 /* WidgetExtension.appex */, + 9BDD424D2EE9E82F00A9BD85 /* Widget.appex */, 9BA8288C2EEC7AAA00F26FCB /* WatchApp.app */, + 88C8B2E62EF476E100044DE9 /* WatchWidget.appex */, ); name = Products; sourceTree = ""; @@ -218,6 +278,29 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 88C8B2E52EF476E100044DE9 /* WatchWidget */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88C8B2F82EF476E200044DE9 /* Build configuration list for PBXNativeTarget "WatchWidget" */; + buildPhases = ( + 88C8B2E22EF476E100044DE9 /* Sources */, + 88C8B2E32EF476E100044DE9 /* Frameworks */, + 88C8B2E42EF476E100044DE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 88C8B2E92EF476E100044DE9 /* WatchWidget */, + ); + name = WatchWidget; + packageProductDependencies = ( + 88C8B2FD2EF48DC500044DE9 /* FeedLibrary */, + ); + productName = WidgetWatchExtension; + productReference = 88C8B2E62EF476E100044DE9 /* WatchWidget.appex */; + productType = "com.apple.product-type.app-extension"; + }; 9B9E9DC32E986F8400255820 /* MacMagazine */ = { isa = PBXNativeTarget; buildConfigurationList = 9B9E9DD22E986F8500255820 /* Build configuration list for PBXNativeTarget "MacMagazine" */; @@ -259,10 +342,12 @@ 9BA828882EEC7AAA00F26FCB /* Sources */, 9BA828892EEC7AAA00F26FCB /* Frameworks */, 9BA8288A2EEC7AAA00F26FCB /* Resources */, + 88C8B2FC2EF476E200044DE9 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 88C8B2F62EF476E200044DE9 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 9BA8288D2EEC7AAA00F26FCB /* WatchApp */, @@ -275,9 +360,9 @@ productReference = 9BA8288C2EEC7AAA00F26FCB /* WatchApp.app */; productType = "com.apple.product-type.application"; }; - 9BDD424C2EE9E82F00A9BD85 /* WidgetExtension */ = { + 9BDD424C2EE9E82F00A9BD85 /* Widget */ = { isa = PBXNativeTarget; - buildConfigurationList = 9BDD42642EE9E83100A9BD85 /* Build configuration list for PBXNativeTarget "WidgetExtension" */; + buildConfigurationList = 9BDD42642EE9E83100A9BD85 /* Build configuration list for PBXNativeTarget "Widget" */; buildPhases = ( 9BDD42492EE9E82F00A9BD85 /* Sources */, 9BDD424A2EE9E82F00A9BD85 /* Frameworks */, @@ -290,13 +375,13 @@ fileSystemSynchronizedGroups = ( 9BDD42522EE9E82F00A9BD85 /* Widget */, ); - name = WidgetExtension; + name = Widget; packageProductDependencies = ( 9B4F666E2EE9F223008ACACB /* FeedLibrary */, 9B5143C42EF4CE9800ED4F39 /* MacMagazineLibrary */, ); productName = WidgetExtension; - productReference = 9BDD424D2EE9E82F00A9BD85 /* WidgetExtension.appex */; + productReference = 9BDD424D2EE9E82F00A9BD85 /* Widget.appex */; productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ @@ -306,9 +391,12 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2610; + LastSwiftUpdateCheck = 2620; LastUpgradeCheck = 2610; TargetAttributes = { + 88C8B2E52EF476E100044DE9 = { + CreatedOnToolsVersion = 26.2; + }; 9B9E9DC32E986F8400255820 = { CreatedOnToolsVersion = 26.0.1; }; @@ -337,13 +425,21 @@ projectRoot = ""; targets = ( 9B9E9DC32E986F8400255820 /* MacMagazine */, - 9BDD424C2EE9E82F00A9BD85 /* WidgetExtension */, + 9BDD424C2EE9E82F00A9BD85 /* Widget */, 9BA8288B2EEC7AAA00F26FCB /* WatchApp */, + 88C8B2E52EF476E100044DE9 /* WatchWidget */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 88C8B2E42EF476E100044DE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9B9E9DC22E986F8400255820 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -390,6 +486,13 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 88C8B2E22EF476E100044DE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9B9E9DC02E986F8400255820 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -414,6 +517,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 88C8B2F62EF476E200044DE9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 88C8B2E52EF476E100044DE9 /* WatchWidget */; + targetProxy = 88C8B2F52EF476E200044DE9 /* PBXContainerItemProxy */; + }; 9BA828AB2EEC7AAB00F26FCB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9BA8288B2EEC7AAA00F26FCB /* WatchApp */; @@ -421,12 +529,80 @@ }; 9BDD425F2EE9E83100A9BD85 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 9BDD424C2EE9E82F00A9BD85 /* WidgetExtension */; + target = 9BDD424C2EE9E82F00A9BD85 /* Widget */; targetProxy = 9BDD425E2EE9E83100A9BD85 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 88C8B2F92EF476E200044DE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = WatchWidget/WatchWidget.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5.1.0; + DEVELOPMENT_TEAM = A5VW9QUF9L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WatchWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MacMagazine; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 5.1; + PRODUCT_BUNDLE_IDENTIFIER = com.brit.beta.macmagazine.watchkitapp.WidgetWatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; + }; + 88C8B2FA2EF476E200044DE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = WatchWidget/WatchWidget.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5.1.0; + DEVELOPMENT_TEAM = A5VW9QUF9L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WatchWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MacMagazine; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 5.1; + PRODUCT_BUNDLE_IDENTIFIER = com.brit.beta.macmagazine.watchkitapp.WidgetWatch; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Release; + }; 9B9E9DD32E986F8500255820 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -655,7 +831,7 @@ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 26.1; + WATCHOS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -690,7 +866,7 @@ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 26.1; + WATCHOS_DEPLOYMENT_TARGET = 26.0; }; name = Release; }; @@ -759,6 +935,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 88C8B2F82EF476E200044DE9 /* Build configuration list for PBXNativeTarget "WatchWidget" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88C8B2F92EF476E200044DE9 /* Debug */, + 88C8B2FA2EF476E200044DE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9B9E9DBF2E986F8400255820 /* Build configuration list for PBXProject "MacMagazine" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -786,7 +971,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9BDD42642EE9E83100A9BD85 /* Build configuration list for PBXNativeTarget "WidgetExtension" */ = { + 9BDD42642EE9E83100A9BD85 /* Build configuration list for PBXNativeTarget "Widget" */ = { isa = XCConfigurationList; buildConfigurations = ( 9BDD42622EE9E83100A9BD85 /* Debug */, @@ -798,6 +983,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 88C8B2FD2EF48DC500044DE9 /* FeedLibrary */ = { + isa = XCSwiftPackageProductDependency; + productName = FeedLibrary; + }; 9B024FDB2EC2719700D66056 /* SettingsLibrary */ = { isa = XCSwiftPackageProductDependency; productName = SettingsLibrary; diff --git a/MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/Widget.xcscheme b/MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme similarity index 66% rename from MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/Widget.xcscheme rename to MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme index be633c9d..06198254 100644 --- a/MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/Widget.xcscheme +++ b/MacMagazine/MacMagazine.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme @@ -1,8 +1,7 @@ + version = "1.7"> @@ -47,59 +46,38 @@ + allowLocationSimulation = "YES"> - - - - - - - - + debugDocumentVersioning = "YES"> diff --git a/MacMagazine/MacMagazine.xcodeproj/xcuserdata/cassiorossi.xcuserdatad/xcschemes/xcschememanagement.plist b/MacMagazine/MacMagazine.xcodeproj/xcuserdata/cassiorossi.xcuserdatad/xcschemes/xcschememanagement.plist index 002a34cb..7e34d12b 100644 --- a/MacMagazine/MacMagazine.xcodeproj/xcuserdata/cassiorossi.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/MacMagazine/MacMagazine.xcodeproj/xcuserdata/cassiorossi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -17,7 +17,7 @@ WatchApp.xcscheme_^#shared#^_ orderHint - 1 + 2 WatchkApp Watch App (Notification).xcscheme_^#shared#^_ @@ -34,14 +34,14 @@ orderHint 4 - Widget.xcscheme_^#shared#^_ - - orderHint - 1 - SuppressBuildableAutocreation + 88C8B2E52EF476E100044DE9 + + primary + + 9B9E9DC32E986F8400255820 primary @@ -52,6 +52,11 @@ primary + 9BDD424C2EE9E82F00A9BD85 + + primary + + diff --git a/MacMagazine/WatchApp/MainApp/WatchApp.swift b/MacMagazine/WatchApp/MainApp/WatchApp.swift index a2e87953..b8a424b5 100644 --- a/MacMagazine/WatchApp/MainApp/WatchApp.swift +++ b/MacMagazine/WatchApp/MainApp/WatchApp.swift @@ -2,12 +2,17 @@ import FeedLibrary import StorageLibrary import SwiftData import SwiftUI +import UserNotifications +import WatchKit @main struct WatchApp: App { private let database = Database(models: [FeedDB.self], inMemory: false) + @WKApplicationDelegateAdaptor(WatchNotificationsDelegate.self) + private var notifDelegate + var body: some Scene { WindowGroup { FeedMainView( @@ -16,6 +21,23 @@ struct WatchApp: App { ) ) .modelContainer(database.sharedModelContainer) + .onOpenURL { url in + handleDeepLink(url) + } + } + } + + @MainActor + private func handleDeepLink(_ url: URL) { + guard url.scheme == "macmagazine" else { return } + + let host = url.host ?? "" + let pathComponents = url.pathComponents.filter { $0 != "/" } + + guard host == "news" else { return } + + if pathComponents.count >= 2, pathComponents[0] == "post" { + _ = pathComponents[1] } } } diff --git a/MacMagazine/WatchApp/MainApp/WatchNotificationsDelegate.swift b/MacMagazine/WatchApp/MainApp/WatchNotificationsDelegate.swift new file mode 100644 index 00000000..3710742a --- /dev/null +++ b/MacMagazine/WatchApp/MainApp/WatchNotificationsDelegate.swift @@ -0,0 +1,55 @@ +import Foundation +import UserNotifications +import WatchKit +import WidgetKit + +final class WatchNotificationsDelegate: NSObject, + WKApplicationDelegate, + UNUserNotificationCenterDelegate { + + func applicationDidFinishLaunching() { + UNUserNotificationCenter.current().delegate = self + } + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse + ) async { + handlePush(userInfo: response.notification.request.content.userInfo) + } + + // MARK: - Push handling + + private func handlePush(userInfo: [AnyHashable: Any]) { + guard + let postId = userInfo["postId"] as? String, + let title = userInfo["title"] as? String, + !postId.isEmpty, + !title.isEmpty + else { + return + } + + let date: Date = { + if let time = userInfo["date"] as? TimeInterval { + return Date(timeIntervalSince1970: time) + } + if let iso = userInfo["date"] as? String, + let date = ISO8601DateFormatter().date(from: iso) { + return date + } + return Date() + }() + + MacMagazineWidgetSharedStore.write( + post: .init( + id: postId, + title: title, + date: date + ) + ) + + // Atualiza as complicações + WidgetCenter.shared.reloadTimelines(ofKind: "WatchWidget") + } +} diff --git a/MacMagazine/WatchApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/MacMagazine/WatchApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/MacMagazine/WatchApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchApp/ViewModel/FeedMainViewModel.swift b/MacMagazine/WatchApp/ViewModel/FeedMainViewModel.swift index 912a4f24..6f70fcdc 100644 --- a/MacMagazine/WatchApp/ViewModel/FeedMainViewModel.swift +++ b/MacMagazine/WatchApp/ViewModel/FeedMainViewModel.swift @@ -3,6 +3,7 @@ import Foundation import StorageLibrary import SwiftData import WatchKit +import WidgetKit @MainActor @Observable @@ -12,7 +13,6 @@ final class FeedMainViewModel { private(set) var status: FeedViewModel.Status = .loading var selectedIndex: Int = 0 - var showActions: Bool = false var showContextMenu: Bool = false var selectedPostForDetail: FeedDB? private(set) var isRefreshing: Bool = false @@ -22,6 +22,7 @@ final class FeedMainViewModel { private let feedViewModel: FeedViewModel // MARK: - Init + init(feedViewModel: FeedViewModel) { self.feedViewModel = feedViewModel status = feedViewModel.status @@ -32,19 +33,38 @@ final class FeedMainViewModel { func refresh(modelContext: ModelContext) async { guard !isRefreshing else { return } isRefreshing = true - - defer { - isRefreshing = false - } + defer { isRefreshing = false } _ = try? await feedViewModel.getWatchFeed() status = feedViewModel.status + + persistLatestPostSnapshot(from: modelContext) + + WidgetCenter.shared.reloadTimelines(ofKind: "WatchWidget") } func toggleFavorite(post: FeedDB, modelContext: ModelContext) { post.favorite.toggle() try? modelContext.save() - showActions = true + } + + // MARK: - Snapshot para Widget + + private func persistLatestPostSnapshot(from modelContext: ModelContext) { + var descriptor = FetchDescriptor( + sortBy: [SortDescriptor(\.pubDate, order: .reverse)] + ) + descriptor.fetchLimit = 1 + + guard let last = try? modelContext.fetch(descriptor).first else { return } + + MacMagazineWidgetSharedStore.write( + post: .init( + id: last.postId, + title: last.title, + date: last.pubDate + ) + ) } // MARK: - Index / Helpers @@ -74,6 +94,15 @@ final class FeedMainViewModel { guard quantity > 0 else { return 0 } return min(max(index, 0), quantity - 1) } + + func openPost(withId postId: String, modelContext: ModelContext) { + let predicate = #Predicate { $0.postId == postId } + let descriptor = FetchDescriptor(predicate: predicate) + + if let post = try? modelContext.fetch(descriptor).first { + selectedPostForDetail = post + } + } } #if DEBUG diff --git a/MacMagazine/WatchApp/Views/FeedMainView.swift b/MacMagazine/WatchApp/Views/FeedMainView.swift index 32744b7f..8345482f 100644 --- a/MacMagazine/WatchApp/Views/FeedMainView.swift +++ b/MacMagazine/WatchApp/Views/FeedMainView.swift @@ -31,8 +31,17 @@ struct FeedMainView: View { var body: some View { NavigationStack { rootContent - .navigationDestination(item: $viewModel.selectedPostForDetail) { payload in - FeedDetailView(viewModel: viewModel, post: payload) + .navigationDestination(item: $viewModel.selectedPostForDetail) { post in + FeedDetailView(viewModel: viewModel, post: post) + } + .onOpenURL { url in + guard url.scheme == "macmagazine", url.host == "news" else { return } + + let parts = url.pathComponents.filter { $0 != "/" } + if parts.count >= 2, parts[0] == "post" { + let postId = parts[1] + viewModel.openPost(withId: postId, modelContext: modelContext) + } } } .task { @@ -193,6 +202,7 @@ struct FeedMainView: View { ) .frame(maxHeight: .infinity, alignment: .trailing) .opacity(viewModel.isRefreshing ? 0 : 1) + .padding(.trailing, 4) } .overlay { if viewModel.isRefreshing { diff --git a/MacMagazine/WatchApp/Views/Row/FeedRowView.swift b/MacMagazine/WatchApp/Views/Row/FeedRowView.swift index e5c1f128..184c65d8 100644 --- a/MacMagazine/WatchApp/Views/Row/FeedRowView.swift +++ b/MacMagazine/WatchApp/Views/Row/FeedRowView.swift @@ -62,7 +62,7 @@ struct FeedRowView: View { .foregroundStyle(.primary) .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(3) - .padding(.trailing, 12) + .padding(.trailing, 22) Text(post.dateText) .font(.caption2) diff --git a/MacMagazine/WatchApp/WatchApp.entitlements b/MacMagazine/WatchApp/WatchApp.entitlements index 2284d00f..a44bc9b3 100644 --- a/MacMagazine/WatchApp/WatchApp.entitlements +++ b/MacMagazine/WatchApp/WatchApp.entitlements @@ -12,5 +12,9 @@ CloudKit + com.apple.security.application-groups + + group.com.brit.beta.macmagazine + diff --git a/MacMagazine/WatchApp/WatchWidgetShared.swift b/MacMagazine/WatchApp/WatchWidgetShared.swift new file mode 100644 index 00000000..59d88167 --- /dev/null +++ b/MacMagazine/WatchApp/WatchWidgetShared.swift @@ -0,0 +1,42 @@ +import Foundation + +enum MacMagazineWidgetSharedStore { + + static let appGroupID = "group.com.brit.beta.macmagazine" + + private enum Keys { + static let postId = "macmagazine.widget.post.id" + static let postTitle = "macmagazine.widget.post.title" + static let postDate = "macmagazine.widget.post.date" + } + + struct LastPost: Equatable { + let id: String + let title: String + let date: Date + } + + static func write(post: LastPost) { + guard let defaults = UserDefaults(suiteName: appGroupID) else { return } + + defaults.set(post.id, forKey: Keys.postId) + defaults.set(post.title, forKey: Keys.postTitle) + defaults.set(post.date, forKey: Keys.postDate) + } + + static func readPost() -> LastPost? { + guard let defaults = UserDefaults(suiteName: appGroupID) else { return nil } + + guard + let id = defaults.string(forKey: Keys.postId), + let title = defaults.string(forKey: Keys.postTitle), + let date = defaults.object(forKey: Keys.postDate) as? Date, + !id.isEmpty, + !title.isEmpty + else { + return nil + } + + return LastPost(id: id, title: title, date: date) + } +} diff --git a/MacMagazine/WatchWidget/Extensions/DateFormatter.swift b/MacMagazine/WatchWidget/Extensions/DateFormatter.swift new file mode 100644 index 00000000..5c20b83a --- /dev/null +++ b/MacMagazine/WatchWidget/Extensions/DateFormatter.swift @@ -0,0 +1,18 @@ +import Foundation + +extension DateFormatter { + + static let watchWidgetTime: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "pt_BR") + formatter.dateFormat = "HH:mm" + return formatter + }() + + static let watchWidgetDay: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "pt_BR") + formatter.dateFormat = "dd/MM" + return formatter + }() +} diff --git a/MacMagazine/WatchWidget/Extensions/WidgetAccessibility+View.swift b/MacMagazine/WatchWidget/Extensions/WidgetAccessibility+View.swift new file mode 100644 index 00000000..007f4e3c --- /dev/null +++ b/MacMagazine/WatchWidget/Extensions/WidgetAccessibility+View.swift @@ -0,0 +1,61 @@ +import SwiftUI +import WidgetKit + +private enum WidgetAccessibility { + static let label = "MacMagazine" + static let hint = "Toque para abrir as notícias" +} + +// MARK: - Generic (inline / corner) + +extension View { + + func macMagazineWidgetAccessibility( + url: URL?, + lastPostTitle: String, + children: AccessibilityChildBehavior? = nil + ) -> some View { + Group { + if let children { + self + .accessibilityElement(children: children) + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue("Última notícia: \(lastPostTitle)") + .accessibilityHint(WidgetAccessibility.hint) + } else { + self + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue("Última notícia: \(lastPostTitle)") + .accessibilityHint(WidgetAccessibility.hint) + } + } + .widgetURL(url) + } + + // MARK: - Circular + + func macMagazineCircular( + url: URL? + ) -> some View { + self + .accessibilityElement(children: .ignore) + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue("Abrir notícias") + .accessibilityHint("Toque para abrir") + .widgetURL(url) + } + + // MARK: - Rectangular + + func macMagazineRectangular( + url: URL?, + accessibilityValue: String + ) -> some View { + self + .accessibilityElement(children: .combine) + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue(accessibilityValue) + .accessibilityHint(WidgetAccessibility.hint) + .widgetURL(url) + } +} diff --git a/MacMagazine/WatchWidget/Info.plist b/MacMagazine/WatchWidget/Info.plist new file mode 100644 index 00000000..0f118fb7 --- /dev/null +++ b/MacMagazine/WatchWidget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/MacMagazine/WatchWidget/Model/WatchWidgetModel.swift b/MacMagazine/WatchWidget/Model/WatchWidgetModel.swift new file mode 100644 index 00000000..db03b74f --- /dev/null +++ b/MacMagazine/WatchWidget/Model/WatchWidgetModel.swift @@ -0,0 +1,12 @@ +import AppIntents +import SwiftUI +import WidgetKit + +struct WatchWidgetModel: TimelineEntry { + let date: Date + let configuration: AppIntent + + let postId: String? + let postTitle: String + let postDate: Date? +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..49c81cd8 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/WidgetBackground.colorset/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/Contents.json new file mode 100644 index 00000000..085cd9e5 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "logo@3x 3.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/logo@3x 3.png b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/logo@3x 3.png new file mode 100644 index 00000000..63606da9 Binary files /dev/null and b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_color.imageset/logo@3x 3.png differ diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/Contents.json b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/Contents.json new file mode 100644 index 00000000..7c58c478 --- /dev/null +++ b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "logo.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/logo.png b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/logo.png new file mode 100644 index 00000000..24a300db Binary files /dev/null and b/MacMagazine/WatchWidget/Resources/Assets.xcassets/logo_white.imageset/logo.png differ diff --git a/MacMagazine/WatchWidget/Views/WatchWidgetEntryView.swift b/MacMagazine/WatchWidget/Views/WatchWidgetEntryView.swift new file mode 100644 index 00000000..f4acd0bf --- /dev/null +++ b/MacMagazine/WatchWidget/Views/WatchWidgetEntryView.swift @@ -0,0 +1,140 @@ +import AppIntents +import SwiftUI +import WidgetKit + +struct WatchWidgetEntryView: View { + + let entry: WatchWidgetModel + @Environment(\.widgetFamily) private var family + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + content + .containerBackground(for: .widget) { Color.clear } + } + + @ViewBuilder + private var content: some View { + switch family { + case .accessoryCircular: + circular + case .accessoryCorner: + corner + case .accessoryInline: + inline + case .accessoryRectangular: + rectangular + @unknown default: + EmptyView() + } + } +} + +// MARK: - Layouts + +private extension WatchWidgetEntryView { + + // ACCESSORY CIRCULAR + var circular: some View { + ZStack { + AccessoryWidgetBackground() + + Image("logo_color") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + } + .macMagazineCircular( + url: URL(string: "macmagazine://news") + ) + } + + // ACCESSORY CORNER + var corner: some View { + Image(renderingMode == .fullColor ? "logo_color" : "logo_white") + .resizable() + .renderingMode(renderingMode == .fullColor ? .original : .template) + .scaledToFit() + .frame(width: 35, height: 35) + .foregroundStyle(.primary) + .widgetLabel { + Text(entry.postTitle) + .lineLimit(1) + } + .macMagazineWidgetAccessibility( + url: widgetPostURL(), + lastPostTitle: entry.postTitle, + children: .ignore + ) + } + + // ACCESSORY INLINE + var inline: some View { + Text(entry.postTitle) + .macMagazineWidgetAccessibility( + url: widgetPostURL(), + lastPostTitle: entry.postTitle + ) + } + + // ACCESSORY RECTANGULAR + var rectangular: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { + Image(renderingMode == .fullColor ? "logo_color" : "logo_white") + .resizable() + .renderingMode(renderingMode == .fullColor ? .original : .template) + .scaledToFit() + .frame(width: 18, height: 18) + .foregroundStyle(.primary) + + Text(relativePostTimeText(entry.date)) + .font(.footnote) + .foregroundStyle(.secondary) + .lineLimit(1) + + Spacer(minLength: 0) + } + + Text(entry.postTitle) + .font(.callout) + .lineLimit(2) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .containerBackground(for: .widget) { AccessoryWidgetBackground() } + .macMagazineRectangular( + url: widgetPostURL(), + accessibilityValue: accessibilityValueForRectangular() + ) + } + + func widgetPostURL() -> URL? { + if let postId = entry.postId, !postId.isEmpty { + return URL(string: "macmagazine://news/post/\(postId)") + } + + return URL(string: "macmagazine://news") + } + + func relativePostTimeText(_ date: Date?) -> String { + guard let date else { return "Atualizado recentemente" } + + let time = DateFormatter.watchWidgetTime.string(from: date) + let calendar = Calendar.current + + if calendar.isDateInToday(date) { + return "Hoje às \(time)" + } + + if calendar.isDateInYesterday(date) { + return "Ontem às \(time)" + } + + return "\(DateFormatter.watchWidgetDay.string(from: date)) às \(time)" + } + + func accessibilityValueForRectangular() -> String { + let whenText = relativePostTimeText(entry.date) + return "\(whenText), \(entry.postTitle)" + } +} diff --git a/MacMagazine/WatchWidget/WatchWidget.entitlements b/MacMagazine/WatchWidget/WatchWidget.entitlements new file mode 100644 index 00000000..1f2b1f33 --- /dev/null +++ b/MacMagazine/WatchWidget/WatchWidget.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.brit.beta.macmagazine + + + diff --git a/MacMagazine/WatchWidget/Widget/AppIntent.swift b/MacMagazine/WatchWidget/Widget/AppIntent.swift new file mode 100644 index 00000000..2f211c67 --- /dev/null +++ b/MacMagazine/WatchWidget/Widget/AppIntent.swift @@ -0,0 +1,7 @@ +import AppIntents +import WidgetKit + +struct AppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource { "MacMagazine" } + static var description: IntentDescription { "Configuração da complicação." } +} diff --git a/MacMagazine/WatchWidget/Widget/WatchWidget.swift b/MacMagazine/WatchWidget/Widget/WatchWidget.swift new file mode 100644 index 00000000..3c86ef3b --- /dev/null +++ b/MacMagazine/WatchWidget/Widget/WatchWidget.swift @@ -0,0 +1,77 @@ +import AppIntents +import SwiftUI +import WidgetKit + +struct WatchWidget: Widget { + + let kind: String = "WatchWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration( + kind: kind, + intent: AppIntent.self, + provider: WatchWidgetProvider() + ) { entry in + WatchWidgetEntryView(entry: entry) + } + .configurationDisplayName("MacMagazine") + .description("Acesso rápido às notícias do MacMagazine.") + .supportedFamilies([ + .accessoryCircular, + .accessoryCorner, + .accessoryInline, + .accessoryRectangular + ]) + } +} + +#if DEBUG + +#Preview("Circular", as: .accessoryCircular) { + WatchWidget() +} timeline: { + WatchWidgetModel( + date: .now, + configuration: AppIntent(), + postId: UUID().uuidString, + postTitle: "Apple lança atualização do watchOS", + postDate: .now.addingTimeInterval(-60 * 25) + ) +} + +#Preview("Corner", as: .accessoryCorner) { + WatchWidget() +} timeline: { + WatchWidgetModel( + date: .now, + configuration: AppIntent(), + postId: UUID().uuidString, + postTitle: "Apple lança atualização do watchOS", + postDate: .now.addingTimeInterval(-60 * 25) + ) +} + +#Preview("Inline", as: .accessoryInline) { + WatchWidget() +} timeline: { + WatchWidgetModel( + date: .now, + configuration: AppIntent(), + postId: UUID().uuidString, + postTitle: "Apple lança atualização do watchOS", + postDate: .now.addingTimeInterval(-60 * 25) + ) +} + +#Preview("Rectangular", as: .accessoryRectangular) { + WatchWidget() +} timeline: { + WatchWidgetModel( + date: .now, + configuration: AppIntent(), + postId: UUID().uuidString, + postTitle: "O melhor pedaço da maçã da internet, clique para ver mais!", + postDate: .now.addingTimeInterval(-60 * 90) + ) +} +#endif diff --git a/MacMagazine/WatchWidget/Widget/WatchWidgetBundle.swift b/MacMagazine/WatchWidget/Widget/WatchWidgetBundle.swift new file mode 100644 index 00000000..151e1ce0 --- /dev/null +++ b/MacMagazine/WatchWidget/Widget/WatchWidgetBundle.swift @@ -0,0 +1,9 @@ +import SwiftUI +import WidgetKit + +@main +struct WatchWidgetBundle: WidgetBundle { + var body: some Widget { + WatchWidget() + } +} diff --git a/MacMagazine/WatchWidget/Widget/WatchWidgetProvider.swift b/MacMagazine/WatchWidget/Widget/WatchWidgetProvider.swift new file mode 100644 index 00000000..95c27f5a --- /dev/null +++ b/MacMagazine/WatchWidget/Widget/WatchWidgetProvider.swift @@ -0,0 +1,53 @@ +import AppIntents +import SwiftUI +import WidgetKit + +struct WatchWidgetProvider: AppIntentTimelineProvider { + + func recommendations() -> [AppIntentRecommendation] { + [ + AppIntentRecommendation( + intent: AppIntent(), + description: "MacMagazine" + ) + ] + } + + func placeholder(in context: Context) -> WatchWidgetModel { + WatchWidgetModel( + date: .now, + configuration: AppIntent(), + postId: UUID().uuidString, + postTitle: "Apple lança atualização do watchOS", + postDate: .now.addingTimeInterval(-60 * 25) + ) + } + + func snapshot( + for configuration: AppIntent, + in context: Context + ) async -> WatchWidgetModel { + makeEntry(configuration: configuration) + } + + func timeline( + for configuration: AppIntent, + in context: Context + ) async -> Timeline { + let entry = makeEntry(configuration: configuration) + let nextUpdate = Date().addingTimeInterval(60 * 60) + return Timeline(entries: [entry], policy: .after(nextUpdate)) + } + + private func makeEntry(configuration: AppIntent) -> WatchWidgetModel { + let snap = MacMagazineWidgetSharedStore.readPost() + + return WatchWidgetModel( + date: .now, + configuration: configuration, + postId: snap?.id, + postTitle: snap?.title ?? "MacMagazine", + postDate: snap?.date + ) + } +}