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 00000000..c4a86acf Binary files /dev/null and b/WidgetExtension/Assets.xcassets/image_id_ee.imageset/image_id_ee.png differ diff --git a/WidgetExtension/Info.plist b/WidgetExtension/Info.plist new file mode 100644 index 00000000..0f118fb7 --- /dev/null +++ b/WidgetExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + 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) } + } + } +}