From 1c1cbc6f304406a4aad313f7b015f8cc24f205bf Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Tue, 13 Jan 2026 17:42:34 +0200 Subject: [PATCH] Smart-ID control code live activity support --- .../Notification/NotificationUtil.swift | 1 + RIADigiDoc.xcodeproj/project.pbxproj | 181 ++++++++++++++++- .../SmartId/SmartIdLiveActivityTexts.swift | 24 +++ RIADigiDoc/Info.plist | 2 + .../Supporting files/Localizable.xcstrings | 74 ++++++- .../Container/Signing/ControlCodeView.swift | 10 +- .../Signing/MobileId/MobileIdView.swift | 7 +- .../Signing/SmartId/SmartIdView.swift | 14 +- .../Signing/MobileId/MobileIdViewModel.swift | 1 + .../Signing/SmartId/SmartIdViewModel.swift | 106 +++++++++- .../SmartId/SmartIdViewModelProtocol.swift | 3 +- .../SmartId/SmartIdViewModelTests.swift | 189 +++++++++++++++--- .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++++ WidgetExtension/Assets.xcassets/Contents.json | 6 + .../WidgetBackground.colorset/Contents.json | 11 + .../image_id_ee.imageset/Contents.json | 12 ++ .../image_id_ee.imageset/image_id_ee.png | Bin 0 -> 20821 bytes WidgetExtension/Info.plist | 11 + .../WidgetExtensionAttributes.swift | 28 +++ WidgetExtension/WidgetExtensionBundle.swift | 28 +++ .../WidgetExtensionLiveActivity.swift | 57 ++++++ 22 files changed, 775 insertions(+), 36 deletions(-) create mode 100644 RIADigiDoc/Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift create mode 100644 WidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/image_id_ee.imageset/Contents.json create mode 100644 WidgetExtension/Assets.xcassets/image_id_ee.imageset/image_id_ee.png create mode 100644 WidgetExtension/Info.plist create mode 100644 WidgetExtension/WidgetExtensionAttributes.swift create mode 100644 WidgetExtension/WidgetExtensionBundle.swift create mode 100644 WidgetExtension/WidgetExtensionLiveActivity.swift diff --git a/Modules/UtilsLib/Sources/UtilsLib/Notification/NotificationUtil.swift b/Modules/UtilsLib/Sources/UtilsLib/Notification/NotificationUtil.swift index 033b0fa3..15b66bf8 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/Notification/NotificationUtil.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/Notification/NotificationUtil.swift @@ -63,6 +63,7 @@ public final class NotificationUtil: NSObject, NotificationUtilProtocol, Loggabl } public func removeNotification(id: String) { + guard !id.isEmpty else { return } NotificationUtil.logger().info("Removing notification (\(id))") let center = UNUserNotificationCenter.current() center.removePendingNotificationRequests(withIdentifiers: [id]) diff --git a/RIADigiDoc.xcodeproj/project.pbxproj b/RIADigiDoc.xcodeproj/project.pbxproj index 273e6263..9471fa25 100644 --- a/RIADigiDoc.xcodeproj/project.pbxproj +++ b/RIADigiDoc.xcodeproj/project.pbxproj @@ -46,6 +46,9 @@ DFAABD052E82B1AB00906874 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DFAABD042E82B1AB00906874 /* Alamofire */; }; DFB1F12C2CF020C200185A7F /* CommonsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DFB1F12B2CF020C200185A7F /* CommonsLib */; }; DFB1F1312CF0217C00185A7F /* CommonsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DFB1F1302CF0217C00185A7F /* CommonsLib */; }; + DFB663A02F16917E00804545 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB6639F2F16917D00804545 /* WidgetKit.framework */; }; + DFB663A22F16917E00804545 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB663A12F16917E00804545 /* SwiftUI.framework */; }; + DFB663AF2F16918100804545 /* WidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DFBD09F62CE3FBDC006AF9C2 /* LibdigidocLib in Frameworks */ = {isa = PBXBuildFile; productRef = DFBD09F52CE3FBDC006AF9C2 /* LibdigidocLib */; }; /* End PBXBuildFile section */ @@ -57,6 +60,13 @@ remoteGlobalIDString = DF4E43C02D0BA38600967997; remoteInfo = FileImportShareExtension; }; + DFB663AD2F16918100804545 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DFDB14732CC97B0E00153876 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DFB6639D2F16917D00804545; + remoteInfo = WidgetExtensionExtension; + }; DFCE022F2D148B4D00216362 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DFDB14732CC97B0E00153876 /* Project object */; @@ -87,6 +97,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + DFB663AF2F16918100804545 /* WidgetExtensionExtension.appex in Embed Foundation Extensions */, DF4E43CB2D0BA38600967997 /* FileImportShareExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -123,6 +134,9 @@ DFA16B492CD18C7C0099D34F /* libdigidoclib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libdigidoclib.xcodeproj; sourceTree = ""; }; DFA23CBB2EB6CBF7000403E4 /* MobileIdLib */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MobileIdLib; sourceTree = ""; }; DFA3EE432D14962C001D951B /* FileImportShareExtension */ = {isa = PBXFileReference; lastKnownFileType = folder; name = FileImportShareExtension; path = Extensions/FileImportShareExtension; sourceTree = ""; }; + DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtensionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + DFB6639F2F16917D00804545 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + DFB663A12F16917E00804545 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; DFCE022B2D148B4D00216362 /* FileImportShareExtensionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FileImportShareExtensionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DFCF22F82E83512D0015AE6B /* CommonsTestShared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CommonsTestShared; sourceTree = ""; }; DFDB147B2CC97B0E00153876 /* RIADigiDoc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RIADigiDoc.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -160,6 +174,7 @@ Domain/Model/Signing/SigningMethod.swift, Domain/Model/Signing/SmartId/SmartIdCountry.swift, Domain/Model/Signing/SmartId/SmartIdInputData.swift, + Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift, Domain/Model/SupportedLanguage.swift, Domain/Model/SupportedTheme.swift, Domain/Model/ToastMessage.swift, @@ -290,6 +305,7 @@ Domain/Model/Signing/SigningMethod.swift, Domain/Model/Signing/SmartId/SmartIdCountry.swift, Domain/Model/Signing/SmartId/SmartIdInputData.swift, + Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift, Domain/Preferences/DataStore.swift, Domain/Preferences/DataStoreProtocol.swift, Domain/Preferences/KeychainStore.swift, @@ -341,6 +357,7 @@ Domain/Model/Signing/SigningMethod.swift, Domain/Model/Signing/SmartId/SmartIdCountry.swift, Domain/Model/Signing/SmartId/SmartIdInputData.swift, + Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift, Domain/Preferences/DataStore.swift, Domain/Preferences/DataStoreProtocol.swift, Domain/Preferences/KeychainStore.swift, @@ -351,6 +368,27 @@ ); target = DFCE022A2D148B4D00216362 /* FileImportShareExtensionTests */; }; + DFA5D36B2F19077D00FA3836 /* Exceptions for "WidgetExtension" folder in "RIADigiDocTests" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + WidgetExtensionAttributes.swift, + ); + target = DFDB148B2CC97B1000153876 /* RIADigiDocTests */; + }; + DFB663B32F16918100804545 /* Exceptions for "WidgetExtension" folder in "WidgetExtensionExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = DFB6639D2F16917D00804545 /* WidgetExtensionExtension */; + }; + DFB663B72F16939700804545 /* Exceptions for "WidgetExtension" folder in "RIADigiDoc" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + WidgetExtensionAttributes.swift, + ); + target = DFDB147A2CC97B0E00153876 /* RIADigiDoc */; + }; DFDBADEC2DE7BDBC00C8A9A5 /* Exceptions for "RIADigiDocTests" folder in "RIADigiDocTests" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -371,6 +409,16 @@ path = Extensions/FileImportShareExtension; sourceTree = ""; }; + DFB663A32F16917E00804545 /* WidgetExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + DFB663B72F16939700804545 /* Exceptions for "WidgetExtension" folder in "RIADigiDoc" target */, + DFA5D36B2F19077D00FA3836 /* Exceptions for "WidgetExtension" folder in "RIADigiDocTests" target */, + DFB663B32F16918100804545 /* Exceptions for "WidgetExtension" folder in "WidgetExtensionExtension" target */, + ); + path = WidgetExtension; + sourceTree = ""; + }; DFCE022C2D148B4D00216362 /* FileImportShareExtensionTests */ = { isa = PBXFileSystemSynchronizedRootGroup; name = FileImportShareExtensionTests; @@ -416,6 +464,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DFB6639B2F16917D00804545 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DFB663A22F16917E00804545 /* SwiftUI.framework in Frameworks */, + DFB663A02F16917E00804545 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFCE02282D148B4D00216362 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -530,6 +587,8 @@ isa = PBXGroup; children = ( DFA3EE432D14962C001D951B /* FileImportShareExtension */, + DFB6639F2F16917D00804545 /* WidgetKit.framework */, + DFB663A12F16917E00804545 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -544,6 +603,7 @@ DFA16AFE2CD18BD00099D34F /* Modules */, DF4E43C22D0BA38600967997 /* FileImportShareExtension */, DFCE022C2D148B4D00216362 /* FileImportShareExtensionTests */, + DFB663A32F16917E00804545 /* WidgetExtension */, DFD24C382CD1B718004C1D2E /* Frameworks */, DFDB147C2CC97B0E00153876 /* Products */, ); @@ -557,6 +617,7 @@ DFDB14962CC97B1000153876 /* RIADigiDocUITests.xctest */, DF4E43C12D0BA38600967997 /* FileImportShareExtension.appex */, DFCE022B2D148B4D00216362 /* FileImportShareExtensionTests.xctest */, + DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */, ); name = Products; sourceTree = ""; @@ -591,6 +652,28 @@ productReference = DF4E43C12D0BA38600967997 /* FileImportShareExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + DFB6639D2F16917D00804545 /* WidgetExtensionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = DFB663B22F16918100804545 /* Build configuration list for PBXNativeTarget "WidgetExtensionExtension" */; + buildPhases = ( + DFB6639A2F16917D00804545 /* Sources */, + DFB6639B2F16917D00804545 /* Frameworks */, + DFB6639C2F16917D00804545 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + DFB663A32F16917E00804545 /* WidgetExtension */, + ); + name = WidgetExtensionExtension; + packageProductDependencies = ( + ); + productName = WidgetExtensionExtension; + productReference = DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; DFCE022A2D148B4D00216362 /* FileImportShareExtensionTests */ = { isa = PBXNativeTarget; buildConfigurationList = DFCE02332D148B4D00216362 /* Build configuration list for PBXNativeTarget "FileImportShareExtensionTests" */; @@ -637,6 +720,7 @@ ); dependencies = ( DF4E43CA2D0BA38600967997 /* PBXTargetDependency */, + DFB663AE2F16918100804545 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( DFDB147D2CC97B0E00153876 /* RIADigiDoc */, @@ -727,13 +811,16 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 2620; LastUpgradeCheck = 2600; ORGANIZATIONNAME = "Riigi Infosüsteemi Amet"; TargetAttributes = { DF4E43C02D0BA38600967997 = { CreatedOnToolsVersion = 16.2; }; + DFB6639D2F16917D00804545 = { + CreatedOnToolsVersion = 26.2; + }; DFCE022A2D148B4D00216362 = { CreatedOnToolsVersion = 16.2; TestTargetID = DFDB147A2CC97B0E00153876; @@ -786,6 +873,7 @@ DFDB14952CC97B1000153876 /* RIADigiDocUITests */, DF4E43C02D0BA38600967997 /* FileImportShareExtension */, DFCE022A2D148B4D00216362 /* FileImportShareExtensionTests */, + DFB6639D2F16917D00804545 /* WidgetExtensionExtension */, ); }; /* End PBXProject section */ @@ -798,6 +886,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DFB6639C2F16917D00804545 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFCE02292D148B4D00216362 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -859,6 +954,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DFB6639A2F16917D00804545 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFCE02272D148B4D00216362 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -895,6 +997,11 @@ target = DF4E43C02D0BA38600967997 /* FileImportShareExtension */; targetProxy = DF4E43C92D0BA38600967997 /* PBXContainerItemProxy */; }; + DFB663AE2F16918100804545 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DFB6639D2F16917D00804545 /* WidgetExtensionExtension */; + targetProxy = DFB663AD2F16918100804545 /* PBXContainerItemProxy */; + }; DFCE02302D148B4D00216362 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DFDB147A2CC97B0E00153876 /* RIADigiDoc */; @@ -976,6 +1083,69 @@ }; name = Release; }; + DFB663B02F16918100804545 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ET847QJV9F; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Riigi Infosüsteemi Amet. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ee.ria.digidoc.WidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + 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 = "1,2"; + }; + name = Debug; + }; + DFB663B12F16918100804545 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ET847QJV9F; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Riigi Infosüsteemi Amet. All rights reserved."; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ee.ria.digidoc.WidgetExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + 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 = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; DFCE02312D148B4D00216362 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1389,6 +1559,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DFB663B22F16918100804545 /* Build configuration list for PBXNativeTarget "WidgetExtensionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DFB663B02F16918100804545 /* Debug */, + DFB663B12F16918100804545 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DFCE02332D148B4D00216362 /* Build configuration list for PBXNativeTarget "FileImportShareExtensionTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/RIADigiDoc/Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift b/RIADigiDoc/Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift new file mode 100644 index 00000000..05e092b0 --- /dev/null +++ b/RIADigiDoc/Domain/Model/Signing/SmartId/SmartIdLiveActivityTexts.swift @@ -0,0 +1,24 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +public struct SmartIdLiveActivityTexts: Sendable { + let initialMessage: String + let controlCodeTitle: String + let compactTitle: String +} diff --git a/RIADigiDoc/Info.plist b/RIADigiDoc/Info.plist index b1a8a206..81ca6d76 100644 --- a/RIADigiDoc/Info.plist +++ b/RIADigiDoc/Info.plist @@ -7,6 +7,8 @@ com.ftsafe.bR301 com.ftsafe.iR301 + NSSupportsLiveActivities + CFBundleDocumentTypes diff --git a/RIADigiDoc/Supporting files/Localizable.xcstrings b/RIADigiDoc/Supporting files/Localizable.xcstrings index d85ca895..eee14da7 100644 --- a/RIADigiDoc/Supporting files/Localizable.xcstrings +++ b/RIADigiDoc/Supporting files/Localizable.xcstrings @@ -626,6 +626,24 @@ } } }, + "Code" : { + "comment" : "Smart-ID live activity Dynamic Island compact text", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kood" + } + } + } + }, "ConcatKDF reference method" : { "comment" : "Recipient detail", "extractionState" : "manual", @@ -4550,6 +4568,24 @@ } } }, + "Mobile-ID signing info message" : { + "comment" : "Displayed under control code when signing with Mobile-ID", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Make sure the control code matches the one on phone screen and enter mobile-ID PIN2 code." + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veendu kontrollkoodi õigsuses ja sisesta telefonil mobiil-ID PIN2-kood." + } + } + } + }, "More options" : { "comment" : "More options button name", "localizations" : { @@ -7189,6 +7225,24 @@ } } }, + "Smart-ID control code signing info message" : { + "comment" : "Displayed under control code when signing with Smart-ID", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Make sure control code matches with one in phone screen and enter Smart-ID PIN2 code." + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veendu kontrollkoodi õigsuses ja sisesta nutiseadmes Smart-ID PIN2-kood." + } + } + } + }, "Smart-ID notification title" : { "comment" : "Smart-ID signing notification title in Notification Center", "extractionState" : "manual", @@ -7243,6 +7297,24 @@ } } }, + "Smart-ID signing info message" : { + "comment" : "Displayed under control code when signing with Smart-ID", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm device for signing in Smart-ID application." + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allkirjastamiseks kinnita seade Smart-ID rakenduses." + } + } + } + }, "Smart-ID under maintenance" : { "comment" : "Smart-ID error", "extractionState" : "manual", @@ -7743,4 +7815,4 @@ } }, "version" : "1.1" -} \ No newline at end of file +} diff --git a/RIADigiDoc/UI/Component/Container/Signing/ControlCodeView.swift b/RIADigiDoc/UI/Component/Container/Signing/ControlCodeView.swift index 5446f2cc..5bb03422 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/ControlCodeView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/ControlCodeView.swift @@ -29,6 +29,7 @@ struct ControlCodeView: View { var icon: String @Binding var controlCode: String + @Binding var infoMessage: String var body: some View { VStack(alignment: .center) { @@ -51,10 +52,16 @@ struct ControlCodeView: View { .foregroundStyle(theme.onSurface) .scaleEffect(x: Dimensions.Scaling.WideScaling, y: Dimensions.Scaling.DefaultScaling) .accessibilityIdentifier("controlCode") + + Text(verbatim: languageSettings.localized(infoMessage)) + .font(typography.bodyLarge) + .foregroundStyle(theme.onSurface) + .accessibilityIdentifier("infoMessage") } } .onDisappear { controlCode = "- - - -" + infoMessage = "" } } } @@ -62,7 +69,8 @@ struct ControlCodeView: View { #Preview { ControlCodeView( icon: "mobile_id_logo", - controlCode: .constant("1234") + controlCode: .constant("1234"), + infoMessage: .constant("Mobile-ID info message") ) .environment(Container.shared.languageSettings()) .environment(Container.shared.themeSettings()) diff --git a/RIADigiDoc/UI/Component/Container/Signing/MobileId/MobileIdView.swift b/RIADigiDoc/UI/Component/Container/Signing/MobileId/MobileIdView.swift index 13cf9378..cc403abe 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/MobileId/MobileIdView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/MobileId/MobileIdView.swift @@ -38,6 +38,10 @@ struct MobileIdView: View { @State private var task: Task? + private var infoMessage: String { + languageSettings.localized(viewModel.infoMessage) + } + let signedContainer: SignedContainerProtocol let onSuccess: (SignedContainerProtocol) -> Void @@ -77,7 +81,8 @@ struct MobileIdView: View { if isSigning { ControlCodeView( icon: "mobile_id_logo", - controlCode: $viewModel.controlCode + controlCode: $viewModel.controlCode, + infoMessage: $viewModel.infoMessage ) } else { MobileIdInputView( diff --git a/RIADigiDoc/UI/Component/Container/Signing/SmartId/SmartIdView.swift b/RIADigiDoc/UI/Component/Container/Signing/SmartId/SmartIdView.swift index 0b2b336d..cbeea76d 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/SmartId/SmartIdView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/SmartId/SmartIdView.swift @@ -39,6 +39,14 @@ struct SmartIdView: View { @State private var task: Task? + private var liveActivityTexts: SmartIdLiveActivityTexts { + SmartIdLiveActivityTexts( + initialMessage: languageSettings.localized("Smart-ID signing info message"), + controlCodeTitle: languageSettings.localized("Smart-ID notification title"), + compactTitle: languageSettings.localized("Code") + ) + } + private let signedContainer: SignedContainerProtocol private let onSuccess: (SignedContainerProtocol) -> Void @@ -78,7 +86,8 @@ struct SmartIdView: View { if isSigning { ControlCodeView( icon: "smart_id_logo", - controlCode: $viewModel.controlCode + controlCode: $viewModel.controlCode, + infoMessage: $viewModel.infoMessage ) .onChange(of: scenePhase) { _, newPhase in switch newPhase { @@ -192,7 +201,8 @@ struct SmartIdView: View { country: "", zipCode: "" ), - signedContainer: signedContainer + signedContainer: signedContainer, + liveActivityTexts: liveActivityTexts ) guard let container = updatedContainer else { cancelSigning() diff --git a/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift b/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift index 7d6e847f..bc6eec97 100644 --- a/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift +++ b/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift @@ -34,6 +34,7 @@ class MobileIdViewModel: MobileIdViewModelProtocol, Loggable { private static let signatureSessionEndpoint = "\(signatureEndpoint)/session" var controlCode: String = "- - - -" + var infoMessage: String = "Mobile-ID signing info message" var countryCodeAndPhoneErrorKey: String? var personalCodeErrorKey: String? diff --git a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift index f3589a16..84299444 100644 --- a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift +++ b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift @@ -25,10 +25,14 @@ import UtilsLib import ConfigLib import CommonsLib import CryptoKit +import ActivityKit @Observable @MainActor class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { + + private var activity: Activity? + private var backgroundTask: UIBackgroundTaskIdentifier = .invalid private static let certificateEndpoint = "/certificatechoice/etsi" @@ -36,6 +40,7 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { private static let sessionEndpoint = "/session" var controlCode: String = "- - - -" + var infoMessage: String = "Smart-ID signing info message" var personalCodeErrorKey: String? @@ -117,7 +122,8 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { country: SmartIdCountry, personalCode: String, roleData: RoleData, - signedContainer: SignedContainerProtocol + signedContainer: SignedContainerProtocol, + liveActivityTexts: SmartIdLiveActivityTexts ) async -> SignedContainerProtocol? { SmartIdViewModel.logger().info("Smart-ID: Signing with Smart-ID") @@ -157,7 +163,16 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { return nil } - let isNotificationPermissionGranted = await notificationUtil.requestAuthorization() + var isNotificationPermissionGranted = false + do { + try startLiveActivity(withTexts: liveActivityTexts) + } catch { + SmartIdViewModel.logger().error( + "Smart-ID: Unable to start live activity for verification code (control code). \(error)" + ) + + isNotificationPermissionGranted = await notificationUtil.requestAuthorization() + } let response: SmartIdSessionResponse do { @@ -200,6 +215,9 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { } controlCode = await getVerificationCode(hash: hash) + infoMessage = "Smart-ID control code signing info message" + + await updateLiveActivity(withTexts: liveActivityTexts) var notificationIdentifier: String = "" do { @@ -238,11 +256,14 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { SmartIdViewModel.logger().info("Smart-ID: Unable to request signature or get cert from response") handleSigningError(error) notificationUtil.removeNotification(id: notificationIdentifier) + await endLiveActivity() return nil } endBackgroundTask() + await endLiveActivity() + do { try Task.checkCancellation() @@ -254,11 +275,13 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { SmartIdViewModel.logger().info("Signature added successfully (Smart-ID)") smartIdMessageKey = "Signature added" notificationUtil.removeNotification(id: notificationIdentifier) + await endLiveActivity() return updatedContainer } catch { SmartIdViewModel.logger().error("Unable to sign container with Smart-ID: \(error)") handleSignatureAddingError(error) notificationUtil.removeNotification(id: notificationIdentifier) + await endLiveActivity() return nil } } @@ -607,4 +630,83 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { smartIdMessageKey = "General error" } } + + private func startLiveActivity(withTexts texts: SmartIdLiveActivityTexts) throws { + if UIAccessibility.isVoiceOverRunning { + SmartIdViewModel.logger().info("Smart-ID: VoiceOver active - using local notification instead") + throw ActivityAuthorizationError.unsupported + } + + guard ActivityAuthorizationInfo().areActivitiesEnabled else { + SmartIdViewModel.logger().info("Smart-ID: Live Activities not enabled - using local notification instead") + throw ActivityAuthorizationError.denied + } + + if let existingActivity = Activity.activities.first { + self.activity = existingActivity + return + } + + let attributes = WidgetExtensionAttributes() + let initialState = WidgetExtensionAttributes.ContentState( + title: texts.initialMessage, + compactTitle: texts.compactTitle, + controlCode: "" + ) + + activity = try Activity.request( + attributes: attributes, + content: ActivityContent( + state: initialState, + staleDate: Date.now.addingTimeInterval(100000), + relevanceScore: 100.0 + ), + pushType: nil + ) + + SmartIdViewModel.logger().info("Smart-ID: Live activity started for control code") + } + + private func updateLiveActivity(withTexts texts: SmartIdLiveActivityTexts) async { + guard let activity = self.activity else { + SmartIdViewModel.logger().error("Smart-ID: No active Live Activity to update") + return + } + + let newState = WidgetExtensionAttributes.ContentState( + title: texts.controlCodeTitle, + compactTitle: texts.compactTitle, + controlCode: controlCode + ) + let newContent = ActivityContent( + state: newState, + staleDate: nil + ) + + await activity.update(newContent) + + SmartIdViewModel.logger().info("Smart-ID: Live Activity updated") + } + + func endLiveActivity() async { + guard let activity else { return } + + let state = WidgetExtensionAttributes.ContentState( + title: "", + compactTitle: "", + controlCode: "" + ) + + let content = ActivityContent( + state: state, + staleDate: nil + ) + + await activity.end( + content, + dismissalPolicy: .immediate + ) + + self.activity = nil + } } diff --git a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModelProtocol.swift b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModelProtocol.swift index 913dcd71..05f048c7 100644 --- a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModelProtocol.swift +++ b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModelProtocol.swift @@ -44,7 +44,8 @@ public protocol SmartIdViewModelProtocol: Sendable { country: SmartIdCountry, personalCode: String, roleData: RoleData, - signedContainer: SignedContainerProtocol + signedContainer: SignedContainerProtocol, + liveActivityTexts: SmartIdLiveActivityTexts ) async -> SignedContainerProtocol? func isRoleDataEnabled() async -> Bool diff --git a/RIADigiDocTests/ViewModel/Signing/SmartId/SmartIdViewModelTests.swift b/RIADigiDocTests/ViewModel/Signing/SmartId/SmartIdViewModelTests.swift index ec317853..748ad2fc 100644 --- a/RIADigiDocTests/ViewModel/Signing/SmartId/SmartIdViewModelTests.swift +++ b/RIADigiDocTests/ViewModel/Signing/SmartId/SmartIdViewModelTests.swift @@ -162,7 +162,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -184,7 +189,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -215,7 +225,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -243,7 +258,12 @@ struct SmartIdViewModelTests { country: .lithuania, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -279,7 +299,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "38501010001", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -329,7 +354,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "38501010001", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result != nil) @@ -382,7 +412,12 @@ struct SmartIdViewModelTests { country: .latvia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result != nil) @@ -418,7 +453,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -460,7 +500,12 @@ struct SmartIdViewModelTests { country: .latvia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -511,7 +556,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -553,7 +603,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -595,7 +650,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -637,7 +697,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: container + signedContainer: container, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -661,7 +726,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -684,7 +754,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -715,7 +790,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -738,7 +818,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -769,7 +854,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -792,7 +882,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -820,7 +915,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -860,7 +960,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -885,7 +990,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -910,7 +1020,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -934,7 +1049,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer() + signedContainer: mockContainer(), + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -981,7 +1101,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer + signedContainer: mockContainer, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -1027,7 +1152,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer + signedContainer: mockContainer, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) @@ -1093,7 +1223,12 @@ struct SmartIdViewModelTests { country: .estonia, personalCode: "60001019906", roleData: roleData, - signedContainer: mockContainer + signedContainer: mockContainer, + liveActivityTexts: SmartIdLiveActivityTexts( + initialMessage: "Initial message", + controlCodeTitle: "Control code", + compactTitle: "Code" + ) ) #expect(result == nil) diff --git a/WidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json b/WidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json b/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..23058801 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/Contents.json b/WidgetExtension/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json b/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/image_id_ee.imageset/Contents.json b/WidgetExtension/Assets.xcassets/image_id_ee.imageset/Contents.json new file mode 100644 index 00000000..38fe0946 --- /dev/null +++ b/WidgetExtension/Assets.xcassets/image_id_ee.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "image_id_ee.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WidgetExtension/Assets.xcassets/image_id_ee.imageset/image_id_ee.png b/WidgetExtension/Assets.xcassets/image_id_ee.imageset/image_id_ee.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a86acf7a43d41ea22ae9268c90206681d6a555 GIT binary patch literal 20821 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%0A<>-D5}s4t7Z=Ozw&=v>w(`}CNuMa)XN=;G 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 y0^8Vlt*pXNXzlWosI%r&u5uU=tRgZAFkwP>+Z` + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/WidgetExtension/WidgetExtensionAttributes.swift b/WidgetExtension/WidgetExtensionAttributes.swift new file mode 100644 index 00000000..a7805f07 --- /dev/null +++ b/WidgetExtension/WidgetExtensionAttributes.swift @@ -0,0 +1,28 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import ActivityKit + +public struct WidgetExtensionAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + var title: String + var compactTitle: String + var controlCode: String + } +} diff --git a/WidgetExtension/WidgetExtensionBundle.swift b/WidgetExtension/WidgetExtensionBundle.swift new file mode 100644 index 00000000..b6477c52 --- /dev/null +++ b/WidgetExtension/WidgetExtensionBundle.swift @@ -0,0 +1,28 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import WidgetKit +import SwiftUI + +@main +struct WidgetExtensionBundle: WidgetBundle { + var body: some Widget { + WidgetExtensionLiveActivity() + } +} diff --git a/WidgetExtension/WidgetExtensionLiveActivity.swift b/WidgetExtension/WidgetExtensionLiveActivity.swift new file mode 100644 index 00000000..33628a27 --- /dev/null +++ b/WidgetExtension/WidgetExtensionLiveActivity.swift @@ -0,0 +1,57 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import ActivityKit +import WidgetKit +import SwiftUI + +struct WidgetExtensionLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: WidgetExtensionAttributes.self) { context in + let controlCode = context.state.controlCode + HStack { + Image("image_id_ee") + .resizable() + .scaledToFit() + .frame(width: 48) + .accessibilityLabel("DigiDoc") + + let text = controlCode.isEmpty ? + context.state.title : + "\(context.state.title): \(context.state.controlCode)" + + Text(verbatim: text) + .padding() + } + .padding() + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.center) { + HStack { + Text(verbatim: context.state.title) + Text(verbatim: context.state.controlCode) + .bold() + } + } + } compactLeading: { Text(verbatim: context.state.compactTitle) } + compactTrailing: { Text(verbatim: context.state.controlCode) } + minimal: { Text(verbatim: context.state.controlCode) } + } + } +}