diff --git a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj index 5c044bcc696830a87660c69451c1d99bed40e381..ce4ac23f96663f55012299cb0bd62ceec00d1465 100644 --- a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj +++ b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 3127EE0F241A2A9500535CC7 /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127EE0E241A2A9500535CC7 /* TodayViewController.swift */; }; 3127EE12241A2A9500535CC7 /* TodayViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3127EE10241A2A9500535CC7 /* TodayViewController.storyboard */; }; 3127EE16241A2A9500535CC7 /* TodayExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 3127EE0A241A2A9500535CC7 /* TodayExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3127EE1B241A2B4D00535CC7 /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127EE0E241A2A9500535CC7 /* TodayViewController.swift */; }; 31AA002F24347BBD000177B4 /* ApprovalResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA002E24347BBD000177B4 /* ApprovalResponse.swift */; }; 31AA003024347BD1000177B4 /* ApprovalResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA002E24347BBD000177B4 /* ApprovalResponse.swift */; }; 3AF4A84524A06A73006C0C0A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AF4A84724A06A73006C0C0A /* Localizable.strings */; }; @@ -47,6 +46,23 @@ 75BC052F29ECE8AC00E21941 /* ServerSwitchingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC052E29ECE8AC00E21941 /* ServerSwitchingViewController.swift */; }; 75BC053229ED087000E21941 /* ServerSwitchingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 75BC053129ED087000E21941 /* ServerSwitchingViewController.storyboard */; }; 75BC053429EDA23700E21941 /* MordalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC053329EDA23700E21941 /* MordalViewController.swift */; }; + 75BC053629F8DFCC00E21941 /* ServerSwitchingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC053529F8DFCC00E21941 /* ServerSwitchingTableViewController.swift */; }; + 75D4EC652A3C00B00096F9D2 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D4EC642A3C00B00096F9D2 /* Intents.framework */; }; + 75D4EC682A3C00B00096F9D2 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC672A3C00B00096F9D2 /* IntentHandler.swift */; }; + 75D4EC6C2A3C00B00096F9D2 /* IntentsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 75D4EC632A3C00B00096F9D2 /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 75D4EC712A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */; }; + 75D4EC722A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */; }; + 75D4EC732A3C12730096F9D2 /* WidgetConfiguration.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */; }; + 75D4EC742A3C168C0096F9D2 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC672A3C00B00096F9D2 /* IntentHandler.swift */; }; + 75D4EC782A3C29830096F9D2 /* UserDefaultsDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD928CCD2407500400ED04C2 /* UserDefaultsDataStore.swift */; }; + 75D4EC802A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D4EC7F2A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework */; }; + 75D4EC812A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 75D4EC7F2A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 75D4EC872A3C32F10096F9D2 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD5061B3242866780014F3FA /* Configuration.swift */; }; + 75D4EC882A3C33310096F9D2 /* KeychainDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDEA2CD4246CDAEF00D3E15F /* KeychainDataStore.swift */; }; + 75D4EC892A3C33490096F9D2 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAB1F6C240D0D0000EA15FD /* UserDefaultsExtensions.swift */; }; + 75D4EC8A2A3C33720096F9D2 /* XCGLoggerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8D55D82420609000A667B0 /* XCGLoggerExtensions.swift */; }; + 75D4EC8B2A3C33E70096F9D2 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA74C5524501917000D4351 /* Log.swift */; }; + 75D4EC8C2A413E300096F9D2 /* WidgetConfiguration.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */; }; 75EDD2272806618A0068B4BC /* WidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EDD2262806618A0068B4BC /* WidgetView.swift */; }; 75EDD2282806618A0068B4BC /* WidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EDD2262806618A0068B4BC /* WidgetView.swift */; }; 75EDD2292806618A0068B4BC /* WidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EDD2262806618A0068B4BC /* WidgetView.swift */; }; @@ -198,6 +214,13 @@ remoteGlobalIDString = 3127EE09241A2A9500535CC7; remoteInfo = TodayExtension; }; + 75D4EC6A2A3C00B00096F9D2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BDA1830723F3FD7F00C9A6DD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 75D4EC622A3C00B00096F9D2; + remoteInfo = IntentsExtension; + }; 75EF9CB727E9E984003178A3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BDA1830723F3FD7F00C9A6DD /* Project object */; @@ -221,6 +244,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 75D4EC6C2A3C00B00096F9D2 /* IntentsExtension.appex in Embed App Extensions */, 7672AD73257EFF500063884A /* ShareExtension.appex in Embed App Extensions */, 3127EE16241A2A9500535CC7 /* TodayExtension.appex in Embed App Extensions */, 75EF9CB927E9E985003178A3 /* WidgetExtension.appex in Embed App Extensions */, @@ -228,6 +252,17 @@ name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC822A3C2CAD0096F9D2 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 75D4EC812A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -241,6 +276,9 @@ 3AF4A84824A06AD0006C0C0A /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 7503F77D27EAF7E00074A76E /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = ""; }; 756C7E7A282B6A5700C24F4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 7576916A2A2EFEEC00EAFCBC /* AgileWorksRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AgileWorksRelease-Production.entitlements"; sourceTree = ""; }; + 7576916B2A2F003400EAFCBC /* TodayExtensionRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "TodayExtensionRelease-Production.entitlements"; sourceTree = ""; }; + 757691732A32E5F100EAFCBC /* WidgetExtensionRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WidgetExtensionRelease-Production.entitlements"; sourceTree = ""; }; 75917F9227BD315A0051E201 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 75917F9627BD317E0051E201 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 75917F9B27BDF0F10051E201 /* Japanese.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Japanese.strings; sourceTree = ""; }; @@ -252,6 +290,19 @@ 75BC052E29ECE8AC00E21941 /* ServerSwitchingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSwitchingViewController.swift; sourceTree = ""; }; 75BC053129ED087000E21941 /* ServerSwitchingViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ServerSwitchingViewController.storyboard; sourceTree = ""; }; 75BC053329EDA23700E21941 /* MordalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MordalViewController.swift; sourceTree = ""; }; + 75BC053529F8DFCC00E21941 /* ServerSwitchingTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSwitchingTableViewController.swift; sourceTree = ""; }; + 75D4EC632A3C00B00096F9D2 /* IntentsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IntentsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 75D4EC642A3C00B00096F9D2 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; + 75D4EC672A3C00B00096F9D2 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; + 75D4EC692A3C00B00096F9D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = WidgetConfiguration.intentdefinition; sourceTree = ""; }; + 75D4EC772A3C295F0096F9D2 /* IntentsExtensionDebug-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "IntentsExtensionDebug-Production.entitlements"; sourceTree = ""; }; + 75D4EC792A3C29CB0096F9D2 /* IntentsExtensionRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "IntentsExtensionRelease-Production.entitlements"; sourceTree = ""; }; + 75D4EC7B2A3C2C760096F9D2 /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 75D4EC7F2A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_All_AgileWorks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 75D4EC832A3C2EE40096F9D2 /* Production-Intents.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Production-Intents.xcconfig"; sourceTree = ""; }; + 75D4EC852A3C308D0096F9D2 /* Debug-Production-Intents.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug-Production-Intents.xcconfig"; sourceTree = ""; }; + 75D4EC862A3C30B90096F9D2 /* Release-Production-Intents.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Release-Production-Intents.xcconfig"; sourceTree = ""; }; 75EDD2262806618A0068B4BC /* WidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetView.swift; sourceTree = ""; }; 75EF9CAC27E9E983003178A3 /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 75EF9CAD27E9E983003178A3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -388,6 +439,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC602A3C00B00096F9D2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 75D4EC802A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework in Frameworks */, + 75D4EC652A3C00B00096F9D2 /* Intents.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 75EF9CA927E9E983003178A3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -419,12 +479,15 @@ 07A802C56A3436654CD69C43 /* Frameworks */ = { isa = PBXGroup; children = ( + 75D4EC7F2A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework */, + 75D4EC7B2A3C2C760096F9D2 /* AppAuth.framework */, BDA74C5824502255000D4351 /* AppAuth.framework */, BDEB7D8F23F530A400EFAF31 /* KeychainAccess.framework */, 3127EE0B241A2A9500535CC7 /* NotificationCenter.framework */, CF516C40B9AA44C977664C98 /* Pods_All_AgileWorks.framework */, 75EF9CAD27E9E983003178A3 /* WidgetKit.framework */, 75EF9CAF27E9E983003178A3 /* SwiftUI.framework */, + 75D4EC642A3C00B00096F9D2 /* Intents.framework */, ); name = Frameworks; sourceTree = ""; @@ -451,6 +514,7 @@ 3127EE0D241A2A9500535CC7 /* TodayExtension */ = { isa = PBXGroup; children = ( + 7576916B2A2F003400EAFCBC /* TodayExtensionRelease-Production.entitlements */, BD285CAF24D2ACA300DC712E /* View */, 3127EE13241A2A9500535CC7 /* Info.plist */, BD10D30624E38C2F00A0DFDC /* TodayExtension-Production.entitlements */, @@ -512,17 +576,31 @@ 75BC052E29ECE8AC00E21941 /* ServerSwitchingViewController.swift */, 75BC053129ED087000E21941 /* ServerSwitchingViewController.storyboard */, 75BC053329EDA23700E21941 /* MordalViewController.swift */, + 75BC053529F8DFCC00E21941 /* ServerSwitchingTableViewController.swift */, ); path = View; sourceTree = ""; }; + 75D4EC662A3C00B00096F9D2 /* IntentsExtension */ = { + isa = PBXGroup; + children = ( + 75D4EC792A3C29CB0096F9D2 /* IntentsExtensionRelease-Production.entitlements */, + 75D4EC772A3C295F0096F9D2 /* IntentsExtensionDebug-Production.entitlements */, + 75D4EC672A3C00B00096F9D2 /* IntentHandler.swift */, + 75D4EC692A3C00B00096F9D2 /* Info.plist */, + ); + path = IntentsExtension; + sourceTree = ""; + }; 75EF9CB127E9E983003178A3 /* WidgetExtension */ = { isa = PBXGroup; children = ( + 757691732A32E5F100EAFCBC /* WidgetExtensionRelease-Production.entitlements */, 7503F77D27EAF7E00074A76E /* WidgetExtension.entitlements */, 75EF9CB227E9E983003178A3 /* WidgetExtension.swift */, 75EF9CB427E9E984003178A3 /* Assets.xcassets */, 75EF9CB627E9E984003178A3 /* Info.plist */, + 75D4EC702A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition */, ); path = WidgetExtension; sourceTree = ""; @@ -695,6 +773,8 @@ 764D0E00257F603400AB6617 /* Release-Production-Share.xcconfig */, 75EF9CBE27E9EB93003178A3 /* Debug-Production-Widget.xcconfig */, 75EF9CBF27E9EBB3003178A3 /* Release-Production-Widget.xcconfig */, + 75D4EC852A3C308D0096F9D2 /* Debug-Production-Intents.xcconfig */, + 75D4EC862A3C30B90096F9D2 /* Release-Production-Intents.xcconfig */, ); path = Configurations; sourceTree = ""; @@ -707,6 +787,7 @@ BD5061A1242845750014F3FA /* Production-Today.xcconfig */, 764D0DF7257F589D00AB6617 /* Production-Share.xcconfig */, 75EF9CBD27E9EB6E003178A3 /* Production-Widget.xcconfig */, + 75D4EC832A3C2EE40096F9D2 /* Production-Intents.xcconfig */, ); path = Flavor; sourceTree = ""; @@ -789,6 +870,7 @@ 3127EE0D241A2A9500535CC7 /* TodayExtension */, 7672AD6A257EFF500063884A /* ShareExtension */, 75EF9CB127E9E983003178A3 /* WidgetExtension */, + 75D4EC662A3C00B00096F9D2 /* IntentsExtension */, BDA1831023F3FD7F00C9A6DD /* Products */, 833C64FFE6F6CB735DE07EB7 /* Pods */, 07A802C56A3436654CD69C43 /* Frameworks */, @@ -802,6 +884,7 @@ 3127EE0A241A2A9500535CC7 /* TodayExtension.appex */, 7672AD69257EFF500063884A /* ShareExtension.appex */, 75EF9CAC27E9E983003178A3 /* WidgetExtension.appex */, + 75D4EC632A3C00B00096F9D2 /* IntentsExtension.appex */, ); name = Products; sourceTree = ""; @@ -809,6 +892,7 @@ BDA1831123F3FD7F00C9A6DD /* AgileWorks */ = { isa = PBXGroup; children = ( + 7576916A2A2EFEEC00EAFCBC /* AgileWorksRelease-Production.entitlements */, BD5061B2242865BC0014F3FA /* Configurations */, BDA6D6A42411FFAA00FA9C33 /* Settings.bundle */, BDA1831B23F3FD8000C9A6DD /* Assets.xcassets */, @@ -1049,6 +1133,24 @@ productReference = 3127EE0A241A2A9500535CC7 /* TodayExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + 75D4EC622A3C00B00096F9D2 /* IntentsExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 75D4EC6F2A3C00B00096F9D2 /* Build configuration list for PBXNativeTarget "IntentsExtension" */; + buildPhases = ( + 75D4EC5F2A3C00B00096F9D2 /* Sources */, + 75D4EC602A3C00B00096F9D2 /* Frameworks */, + 75D4EC612A3C00B00096F9D2 /* Resources */, + 75D4EC822A3C2CAD0096F9D2 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IntentsExtension; + productName = IntentsExtension; + productReference = 75D4EC632A3C00B00096F9D2 /* IntentsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 75EF9CAB27E9E983003178A3 /* WidgetExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 75EF9CBC27E9E985003178A3 /* Build configuration list for PBXNativeTarget "WidgetExtension" */; @@ -1102,6 +1204,7 @@ 3127EE15241A2A9500535CC7 /* PBXTargetDependency */, 7672AD72257EFF500063884A /* PBXTargetDependency */, 75EF9CB827E9E984003178A3 /* PBXTargetDependency */, + 75D4EC6B2A3C00B00096F9D2 /* PBXTargetDependency */, ); name = AgileWorks; packageProductDependencies = ( @@ -1117,13 +1220,16 @@ BDA1830723F3FD7F00C9A6DD /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1320; + LastSwiftUpdateCheck = 1410; LastUpgradeCheck = 1140; ORGANIZATIONNAME = "ATLED CORP"; TargetAttributes = { 3127EE09241A2A9500535CC7 = { CreatedOnToolsVersion = 11.3.1; }; + 75D4EC622A3C00B00096F9D2 = { + CreatedOnToolsVersion = 14.1; + }; 75EF9CAB27E9E983003178A3 = { CreatedOnToolsVersion = 13.2.1; }; @@ -1159,6 +1265,7 @@ 3127EE09241A2A9500535CC7 /* TodayExtension */, 7672AD68257EFF500063884A /* ShareExtension */, 75EF9CAB27E9E983003178A3 /* WidgetExtension */, + 75D4EC622A3C00B00096F9D2 /* IntentsExtension */, ); }; /* End PBXProject section */ @@ -1178,6 +1285,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC612A3C00B00096F9D2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 75EF9CAA27E9E983003178A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1337,6 +1451,7 @@ BDBBF83D243CAB3300EEB25D /* GetApprovalsEndpoint.swift in Sources */, BDBBF840243CAB6200EEB25D /* UserDefaultsExtensions.swift in Sources */, BDA74C5724501917000D4351 /* Log.swift in Sources */, + 75D4EC8C2A413E300096F9D2 /* WidgetConfiguration.intentdefinition in Sources */, BD7DC0F324C061EA00C3FBED /* ErrorResponse.swift in Sources */, 3127EE0F241A2A9500535CC7 /* TodayViewController.swift in Sources */, 75917FB427C371390051E201 /* StringsUtility.swift in Sources */, @@ -1346,6 +1461,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC5F2A3C00B00096F9D2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 75D4EC682A3C00B00096F9D2 /* IntentHandler.swift in Sources */, + 75D4EC882A3C33310096F9D2 /* KeychainDataStore.swift in Sources */, + 75D4EC892A3C33490096F9D2 /* UserDefaultsExtensions.swift in Sources */, + 75D4EC732A3C12730096F9D2 /* WidgetConfiguration.intentdefinition in Sources */, + 75D4EC8A2A3C33720096F9D2 /* XCGLoggerExtensions.swift in Sources */, + 75D4EC872A3C32F10096F9D2 /* Configuration.swift in Sources */, + 75D4EC8B2A3C33E70096F9D2 /* Log.swift in Sources */, + 75D4EC782A3C29830096F9D2 /* UserDefaultsDataStore.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 75EF9CA827E9E983003178A3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1354,6 +1484,7 @@ 75EF9CD127E9ED82003178A3 /* XCGLoggerExtensions.swift in Sources */, 75EF9CD027E9ED7B003178A3 /* StringsUtility.swift in Sources */, 75EF9CCF27E9ED6D003178A3 /* ErrorResponse.swift in Sources */, + 75D4EC742A3C168C0096F9D2 /* IntentHandler.swift in Sources */, 75EF9CCE27E9ED5F003178A3 /* Log.swift in Sources */, 75EF9CCD27E9ED53003178A3 /* GetApprovalsEndpoint.swift in Sources */, 75EF9CCC27E9ED3B003178A3 /* ApprovalResponse.swift in Sources */, @@ -1365,6 +1496,7 @@ 75EF9CC727E9ECF9003178A3 /* APIEndpoint.swift in Sources */, 75EF9CC527E9ECEF003178A3 /* SessionInfo.swift in Sources */, 75EF9CC627E9ECEF003178A3 /* Session.swift in Sources */, + 75D4EC722A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition in Sources */, 75EF9CC427E9ECE5003178A3 /* URLAuthenticationUtility.swift in Sources */, 75EF9CC327E9ECDB003178A3 /* KeychainDataStore.swift in Sources */, 75EF9CC227E9ECD2003178A3 /* GetSessionEndpoint.swift in Sources */, @@ -1439,8 +1571,8 @@ C599740B27D992E9006F5AAC /* OpenLicenseTableViewCell.swift in Sources */, 76AE533325D37B0200AFA45A /* LicenseViewPresenter.swift in Sources */, 76AE530725D3652400AFA45A /* LicenseViewBuilder.swift in Sources */, - 3127EE1B241A2B4D00535CC7 /* TodayViewController.swift in Sources */, BDAABA1B24A361E90077EC69 /* PostDeviceEndpoint.swift in Sources */, + 75BC053629F8DFCC00E21941 /* ServerSwitchingTableViewController.swift in Sources */, 31AA002F24347BBD000177B4 /* ApprovalResponse.swift in Sources */, BD7A8008241A16B90040B418 /* LinkManager.swift in Sources */, C535772E2754D5C500EAA660 /* WebViewController.swift in Sources */, @@ -1456,6 +1588,7 @@ BD969631240C9CD400521925 /* SessionInfo.swift in Sources */, BD928CCE2407500400ED04C2 /* UserDefaultsDataStore.swift in Sources */, BD5061B4242866780014F3FA /* Configuration.swift in Sources */, + 75D4EC712A3C01D90096F9D2 /* WidgetConfiguration.intentdefinition in Sources */, 76AE52DD25D35CDC00AFA45A /* LicenseUtility.swift in Sources */, BDAB1F6F240D0D1400EA15FD /* APIEndpoint.swift in Sources */, C50CF79227D8708B0042C210 /* OpenLicenseViewController.swift in Sources */, @@ -1481,6 +1614,11 @@ target = 3127EE09241A2A9500535CC7 /* TodayExtension */; targetProxy = 3127EE14241A2A9500535CC7 /* PBXContainerItemProxy */; }; + 75D4EC6B2A3C00B00096F9D2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 75D4EC622A3C00B00096F9D2 /* IntentsExtension */; + targetProxy = 75D4EC6A2A3C00B00096F9D2 /* PBXContainerItemProxy */; + }; 75EF9CB827E9E984003178A3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 75EF9CAB27E9E983003178A3 /* WidgetExtension */; @@ -1536,6 +1674,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = BD5061AF242848560014F3FA /* Release-Production-Today.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = "TodayExtension/TodayExtensionRelease-Production.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -1560,6 +1699,172 @@ }; name = "Release-Production"; }; + 75D4EC6D2A3C00B00096F9D2 /* Release-Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 75D4EC862A3C30B90096F9D2 /* Release-Production-Intents.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "IntentsExtension/IntentsExtensionRelease-Production.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 4TWZNUHVN6; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TWZNUHVN6; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IntentsExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IntentsExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 ATLED CORP. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.IntentsExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-Production"; + }; + 75D4EC6E2A3C00B00096F9D2 /* Debug-Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 75D4EC852A3C308D0096F9D2 /* Debug-Production-Intents.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "IntentsExtension/IntentsExtensionDebug-Production.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 4TWZNUHVN6; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4TWZNUHVN6; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IntentsExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IntentsExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 ATLED CORP. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.IntentsExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-Production"; + }; 75EF9CBA27E9E985003178A3 /* Release-Production */ = { isa = XCBuildConfiguration; baseConfigurationReference = 75EF9CBF27E9EBB3003178A3 /* Release-Production-Widget.xcconfig */; @@ -1596,7 +1901,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = "WidgetExtension/WidgetExtensionRelease-Production.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -1862,7 +2167,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -2024,6 +2329,7 @@ baseConfigurationReference = BD5061A9242846A50014F3FA /* Release-Production.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_ENTITLEMENTS = "AgileWorks/AgileWorksRelease-Production.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -2036,7 +2342,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -2119,6 +2425,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release-Production"; }; + 75D4EC6F2A3C00B00096F9D2 /* Build configuration list for PBXNativeTarget "IntentsExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 75D4EC6D2A3C00B00096F9D2 /* Release-Production */, + 75D4EC6E2A3C00B00096F9D2 /* Debug-Production */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-Production"; + }; 75EF9CBC27E9E985003178A3 /* Build configuration list for PBXNativeTarget "WidgetExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/AgileWorks/AgileWorks.xcodeproj/xcshareddata/xcschemes/IntentsExtension.xcscheme b/AgileWorks/AgileWorks.xcodeproj/xcshareddata/xcschemes/IntentsExtension.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..d3f03e11e9275d9efe6c5460669361dfc50afd49 --- /dev/null +++ b/AgileWorks/AgileWorks.xcodeproj/xcshareddata/xcschemes/IntentsExtension.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AgileWorks/AgileWorks/AgileWorks-Production.entitlements b/AgileWorks/AgileWorks/AgileWorks-Production.entitlements index 6f5ee786c51a485eb983f97ead459084c6e61143..002355ec729a449df0ca46517ada98541abb10d8 100644 --- a/AgileWorks/AgileWorks/AgileWorks-Production.entitlements +++ b/AgileWorks/AgileWorks/AgileWorks-Production.entitlements @@ -4,9 +4,17 @@ aps-environment development + com.apple.security.application-groups + + group.jp.atled.agileworks + keychain-access-groups - $(AppIdentifierPrefix)jp.atled.AgileWorks + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 diff --git a/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements b/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..002355ec729a449df0ca46517ada98541abb10d8 --- /dev/null +++ b/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements @@ -0,0 +1,20 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.jp.atled.agileworks + + keychain-access-groups + + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 + + + diff --git a/AgileWorks/AgileWorks/App/AppDelegate.swift b/AgileWorks/AgileWorks/App/AppDelegate.swift index 84ff5cbe806798d0cf7d35164a944c2dcd89b32e..468a5ca56b3734eb1abb83fff5815b081972e376 100644 --- a/AgileWorks/AgileWorks/App/AppDelegate.swift +++ b/AgileWorks/AgileWorks/App/AppDelegate.swift @@ -63,6 +63,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OIDURLSessionProvider.setSession(Session.shared) } + private func getTapLink(url: URL) -> String? { + var strUrl = url.absoluteString + if let index = strUrl.firstIndex(of: "?") { + strUrl = String(strUrl.prefix(upTo: index)) + return strUrl + } + return nil + } + + private func getTapServer(url: URL) -> Int? { + var strUrl = url.absoluteString + var serverNumber: Int? + if strUrl.contains("serverNumber") { + serverNumber = (Int(String(strUrl.last!))) ?? nil + } + return serverNumber + } + // MARK: DeepLinks @available(iOS 9.0, *) func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) @@ -72,12 +90,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OAuthService.currentAuthorizationFlow = nil } + //タップされたサーバーに切り替える + let serverList = UserDefaultsDataStore().readServerList() + if !serverList.isEmpty { + var tapServer: Int? + if #available(iOS 14.0, *) { + tapServer = getTapServer(url: url) + } else { + tapServer = UserDefaultsDataStore().readTapServer() + } + + if let serverNumber = tapServer { + UserDefaultsDataStore().changeServerList(firstServer: serverNumber) + UserDefaultsDataStore().setGroupId(serverNumber: serverNumber) + Thread.sleep(forTimeInterval: 1) + } + } + //ios13の場合 + var resultURL = url if url.scheme == urlSchemeName { var strUrl = url.absoluteString //指定した書類状態の取得、アプリ起動のみの場合は飛ばす if strUrl.contains("/") { - let result: URL var scheme = urlScheme // 不要な"/"を取り除く let delIdx = scheme.index(scheme.endIndex, offsetBy: -1) @@ -85,11 +120,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { //scheme以降の取得 strUrl = strUrl.replacingOccurrences(of: scheme, with: "") //遷移URLの取得 - result = getStatusURL(status: strUrl) - return Linker.handleDeeplink(url: result) + resultURL = getStatusURL(status: strUrl) + } + }else { //iOS14~ + if let url = getTapLink(url: url) { + resultURL = URL(string: url)! } } - return Linker.handleDeeplink(url: url) + return Linker.handleDeeplink(url: resultURL) } func getStatusURL(status: String) -> URL { diff --git a/AgileWorks/AgileWorks/App/RootViewController.swift b/AgileWorks/AgileWorks/App/RootViewController.swift index 6e82fe618a8e9b7d9c9c8d1b772dbf433674c72c..bf268dce41794b201982b938c5cf958ed97aba2b 100644 --- a/AgileWorks/AgileWorks/App/RootViewController.swift +++ b/AgileWorks/AgileWorks/App/RootViewController.swift @@ -55,7 +55,7 @@ extension RootViewController { } // ログアウトアラート デフォルトボタンアクション生成 - func logoutDefaultAction(title: String) -> UIAlertAction { + func logoutDefaultAction(serverRemove: Bool, title: String) -> UIAlertAction { let defaultAction = UIAlertAction(title: title, style: .default) { _ in self.logout { result in switch result { @@ -64,17 +64,31 @@ extension RootViewController { case .failure(let error): log.e(error) } - KeychainDataStore().removeOAuthState() - KeychainDataStore().removeAccessToken() - KeychainDataStore().removeSessionID() - KeychainDataStore().removeLanguage() - KeychainDataStore().removeDeviceID() - - DispatchQueue.main.async { - self.switchToLogout() - //ウィジェットの更新 - if #available(iOS 14.0, *) { - WidgetCenter.shared.reloadAllTimelines() + var serverList = UserDefaultsDataStore().readServerList() + let removeServer = serverList.first + self.deleteServerInfo(serverNumber: removeServer) + + if serverRemove { + UserDefaultsDataStore().removeServer() + } + + serverList = UserDefaultsDataStore().readServerList() + if serverList.isEmpty { + DispatchQueue.main.async { + self.switchToLogout() + //ウィジェットの更新 + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + } + } else { + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + DispatchQueue.main.async { + self.switchToMainScreen() + //ウィジェットの更新 + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } } } } @@ -83,6 +97,24 @@ extension RootViewController { return defaultAction } + func deleteServerInfo(serverNumber: Int?) { + DeviceService().deleteDevice(serverNumber: serverNumber) { result in + switch result { + case .success: + break + case .failure(let error): + log.e(error) + } + KeychainDataStore().removeOAuthState(serverNumber: serverNumber) + KeychainDataStore().removeAccessToken(serverNumber: serverNumber) + KeychainDataStore().removeDeviceID(serverNumber: serverNumber) + KeychainDataStore().removeSessionID(serverNumber: serverNumber) + } + //認証情報削除 + KeychainDataStore().removeServerName() + KeychainDataStore().removeLanguage() + } + // アラート表示 func showAlertScreen(view: UIViewController, title: String, message: String, defaultAction: UIAlertAction?, cancelAction: UIAlertAction?) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) diff --git a/AgileWorks/AgileWorks/Common/Service/DeviceService.swift b/AgileWorks/AgileWorks/Common/Service/DeviceService.swift index 98efebb1db1e86f62b7f700ee07cfc3e4f83ba7c..d2ee712a11215013ea82b4bb491274275a5c9fd0 100644 --- a/AgileWorks/AgileWorks/Common/Service/DeviceService.swift +++ b/AgileWorks/AgileWorks/Common/Service/DeviceService.swift @@ -65,15 +65,15 @@ public class DeviceService { } } - func deleteDevice(completion: @escaping (APIResult) -> Void) { + func deleteDevice(serverNumber: Int? = nil, completion: @escaping (APIResult) -> Void) { let request = DeviceRequest(deleteFlag: true) - let deleteDevice = DeleteDeviceEndpoint(request: request) + let deleteDevice = DeleteDeviceEndpoint(request: request, serverNumber: serverNumber) Session.send(deleteDevice) { result in let completionArg: APIResult switch result { case .success: - KeychainDataStore().removeDeviceID() + KeychainDataStore().removeDeviceID(serverNumber: serverNumber) completionArg = .success(true) case .failure(let error): log.e(error) diff --git a/AgileWorks/AgileWorks/Info.plist b/AgileWorks/AgileWorks/Info.plist index a511440b8ebf4481f93f0564aee4beec5abef279..395e7180236cc1f2dc4411ebefc4c44cc4d6a0ff 100644 --- a/AgileWorks/AgileWorks/Info.plist +++ b/AgileWorks/AgileWorks/Info.plist @@ -62,6 +62,10 @@ ${PRODUCT_NAME} NSPhotoLibraryUsageDescription ${PRODUCT_NAME} + NSUserActivityTypes + + WidgetConfigurationIntent + UIBackgroundModes remote-notification diff --git a/AgileWorks/AgileWorks/Login/Builder/LoginBuilder.swift b/AgileWorks/AgileWorks/Login/Builder/LoginBuilder.swift index 13926adb9cbc0a9d90f3383794db3452928bcbb0..54a3c59425c37bac04098f5bada0a340bc1f0fc9 100644 --- a/AgileWorks/AgileWorks/Login/Builder/LoginBuilder.swift +++ b/AgileWorks/AgileWorks/Login/Builder/LoginBuilder.swift @@ -14,6 +14,7 @@ protocol LoginBuilder { struct LoginBuilderImpl: LoginBuilder { let identifier = "LoginViewController" + var navVC: UINavigationController? = nil func build() -> UIViewController { let viewController = storyboard() @@ -26,6 +27,12 @@ struct LoginBuilderImpl: LoginBuilder { ) return viewController } + + func naviBuild() -> UINavigationController { + let navVC = UINavigationController(rootViewController: build()) + navVC.modalPresentationStyle = .fullScreen + return navVC + } } extension LoginBuilderImpl { diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift index d12e5f151fa59651dba0e01b258ea0dfdbe20874..7ea78a97cae39c257487280f46cb1f6c7ccf9dac 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift @@ -35,6 +35,8 @@ class LoginViewController: UIViewController { @IBOutlet private var qrCodeReadView: UIView! @IBOutlet private var qrCodeStartupButton: UIButton! @IBOutlet private var scrollViewBottomConstraint: NSLayoutConstraint! + //サーバー追加画面フラグ + private var addServerloginFlg = false private let activityIndicator = UIActivityIndicatorView(style: .whiteLarge) @@ -58,28 +60,29 @@ class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - - //ログイン画面が表示されると通知を取得しない - - self.devcie { result in - switch result { - case .success: - break - case .failure(let error): - log.e(error) - } + if let navVC = self.parent as? UINavigationController { + addServerloginFlg = true + let leftButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped(_:))) + navVC.navigationBar.topItem!.leftBarButtonItem = leftButton } + //サーバー追加画面の場合は認証情報を削除しない + if !addServerloginFlg { + let serverList = UserDefaultsDataStore().readServerList() + + //キーチェーン情報削除 + for serverNumber in serverList { + UserDefaultsDataStore().setGroupId(serverNumber: serverNumber) + //ログイン画面が表示されると通知を取得しない + AppDelegate.shared.rootViewController.deleteServerInfo(serverNumber: serverNumber) + } - //認証情報削除 - KeychainDataStore().removeOAuthState() - KeychainDataStore().removeAccessToken() - KeychainDataStore().removeSessionID() - KeychainDataStore().removeLanguage() - KeychainDataStore().removeDeviceID() + //サーバーリストの削除 + UserDefaultsDataStore().removeAllServer() - //ウィジェットの更新 - if #available(iOS 14.0, *) { - WidgetCenter.shared.reloadAllTimelines() + //ウィジェットの更新 + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } } clientCertificateSettingView.isHidden = (CertificateDataStore().readClientCertificate() == nil) @@ -87,10 +90,12 @@ class LoginViewController: UIViewController { setupFixedWording() //保存されたサーバー名・コンテキストパスの取得 - let server = Configuration.shared.awServer - let context = Configuration.shared.awContextPath - serverTextField.text = server - contextTextField.text = context + if !addServerloginFlg { + let server = Configuration.shared.awServer + let context = Configuration.shared.awContextPath + serverTextField.text = server + contextTextField.text = context + } loginButton.isEnabled = isTextFieldValue() view.addSubview(activityIndicator) @@ -114,6 +119,12 @@ class LoginViewController: UIViewController { func dismissKeyboard() { self.view.endEditing(true) } + + @objc private func cancelTapped(_ sender: UIBarButtonItem) { + var serverList = UserDefaultsDataStore().readServerList() + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil) + } //ログイン画面の固定文言表示 private func setupFixedWording() { @@ -167,7 +178,35 @@ class LoginViewController: UIViewController { guard let serverHost = serverHost else { return } guard let context = context else { return } + //サーバーの重複チェック + let serverList = UserDefaultsDataStore().readServerList() + var serverCheckFlg = false + for serverNumber in serverList { + let serverName = KeychainDataStore().readServerName(serverNumber: serverNumber)! + if serverName == serverHost { + serverCheckFlg = true + let alert = UIAlertController(title: "", message: getLocalizableStrings(key: "LoginServerAlert", comment: ""), preferredStyle: .alert) + let ok = UIAlertAction(title: "OK", style: .default) { (action) in + return + } + alert.addAction(ok) + present(alert, animated: true, completion: nil) + } + } + + if serverCheckFlg { + return + } + disableLogin() + + //サーバー切り替え + if let serverId = UserDefaultsDataStore().serchEmptyServerData() { + UserDefaultsDataStore().setGroupId(serverNumber: serverId) + } else { + //TODO: サーバー追加できない場合 + } + //サーバー名コンテキストパスの保存 KeychainDataStore().writeServerURL(serverURL: serverHost) KeychainDataStore().writeContextPath(contextPath: context) @@ -209,9 +248,20 @@ class LoginViewController: UIViewController { case .success(let response): setStringsName(language: response.user.displayLanguage) KeychainDataStore().writeSessionID(sessionID: response.sessionId) + KeychainDataStore().writeServerName(serverName: "サーバー識別名") //TODO response.server.name DispatchQueue.main.async { + UserDefaultsDataStore().addServerList() + let serverList = UserDefaultsDataStore().readServerList() + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + //ウィジェットの更新 + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } //メイン画面表示 AppDelegate.shared.rootViewController.switchToMainScreen() + if let nc = self.parent as? UINavigationController { + self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil) + } } // セッション情報取得失敗 case .failure(let error): @@ -300,7 +350,7 @@ class LoginViewController: UIViewController { let message = getLocalizableStrings(key: "LoginErrorMessage", comment: "") let defaultAction: UIAlertAction if isLogout { - defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(title: getLocalizableStrings(key: "OK", comment: "")) + defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(serverRemove: false, title: getLocalizableStrings(key: "OK", comment: "")) } else { defaultAction = UIAlertAction(title: getLocalizableStrings(key: "OK", comment: ""), style: .default, handler: nil) } @@ -321,8 +371,8 @@ class LoginViewController: UIViewController { present(alertController, animated: true, completion: nil) } - private func devcie(completion: @escaping (APIResult) -> Void) { - DeviceService().deleteDevice { result in + private func devcie(serverNumber: Int, completion: @escaping (APIResult) -> Void) { + DeviceService().deleteDevice(serverNumber: serverNumber) { result in switch result { case .success: completion(.success(true)) diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift b/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift index 8474044a3d6f7235d1ef89f7a6792cabbb3b7281..a67e42a69e9db1d9b7a1f1cbc6e75f7033342483 100644 --- a/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift +++ b/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift @@ -10,14 +10,21 @@ import UIKit import FloatingPanel class MordalViewController: UIViewController { + @IBOutlet private var accessPoint: UILabel! + @IBOutlet private var addServer: UIButton! + override func viewDidLoad() { super.viewDidLoad() - - // Do any additional setup after loading the view. + accessPoint.text = getDisplayString(key: "ServerTitle", comment: "") } static func fromStoryboard(_ storyboard: UIStoryboard = UIStoryboard(name: "ServerSwitchingViewController", bundle: nil)) -> MordalViewController { let controller = storyboard.instantiateViewController(withIdentifier: "MordalViewController") as! MordalViewController return controller } + @IBAction private func addServerTapped(_ sender: Any) { + //モーダルの表示 + let navVC = LoginBuilderImpl().naviBuild() + self.present(navVC, animated: true) + } } diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingTableViewController.swift b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingTableViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..f540c2baa712a0d7cb8218213fd56f6d4b246a2b --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingTableViewController.swift @@ -0,0 +1,115 @@ +// +// ServerSwitchingTableTableViewController.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/04/26. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit + +class ServerSwitchingTableTableViewController: UITableViewController { + @IBOutlet var table: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + table.tableFooterView = UIView() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + //承認待ち件数の表示 + Task{ + let serverList = UserDefaultsDataStore().readServerList() + let rowCount = table.numberOfRows(inSection: 0) + for row in 1.. Int { + return 1 + } + //セルの数 + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let serverCount = UserDefaultsDataStore().readServerList().count + return serverCount + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "ServerSwitchingTableViewCell", for: indexPath) + + let serverList = UserDefaultsDataStore().readServerList() + let serverName = cell.viewWithTag(1) as! UILabel + let serverNumber = serverList[indexPath.row] + serverName.text = KeychainDataStore().readServerName(serverNumber: serverNumber) + + let badgeIcon = cell.viewWithTag(2) as! UIImageView + badgeIcon.image = UIImage(systemName: "circle.fill", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 25))) + let approvalCount = cell.viewWithTag(3) as! UILabel + + //ログイン中のサーバー + if indexPath.row == 0 { + //チェックマーク + badgeIcon.image = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 25))) + badgeIcon.tintColor = UIColor(named: getDisplayString(key: "MainColor", comment: "")) + //承認待ち件数は取得しないため非表示 + approvalCount.isHidden = true + } + + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 50 + } + + //セル選択時の処理 + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 0 { + self.dismiss(animated: true, completion: nil) + return + } + let serverList = UserDefaultsDataStore().readServerList() + let server = serverList[indexPath.row] + UserDefaultsDataStore().setGroupId(serverNumber: server) + UserDefaultsDataStore().changeServerList(firstServer: server) + AppDelegate.shared.rootViewController.switchToMainScreen() + self.parent!.dismiss(animated: true) + } + + private func callApprovalCount(label: UILabel) async{ + await setApprovalsCount(label: label) + } + + //承認待ち件数取得 + private func setApprovalsCount(label: UILabel) async { + let approvalsEndpoint = GetApprovalsEndpoint() + Session.send(approvalsEndpoint) { result in + var reqApprovalCount: Int = 0 + switch result { + case .success(let response): + let requestApproval = "REQUEST_APPROVAL" + for item in response.items where item.code == requestApproval { + reqApprovalCount = item.count + DispatchQueue.main.async { + label.text = String(reqApprovalCount) + } + } + case .failure(let error): + log.e(error) + DispatchQueue.main.async { + label.text = "0" + } + } + } + } +} diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingViewController.swift b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingViewController.swift index 3ebe6b991b239771edd58acfb8032deff4c2b077..652e871aad5798aa9cf31fd09f3e65d6fbafd9fe 100644 --- a/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingViewController.swift +++ b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingViewController.swift @@ -29,9 +29,9 @@ class ServerSwitchingViewController: UIViewController, UIGestureRecognizerDelega fpc.surfaceView.cornerRadius = 24.0 //下スワイプで閉じる fpc.isRemovalInteractionEnabled = true - let mordalVC = MordalViewController.fromStoryboard() + let modalVC = MordalViewController.fromStoryboard() self.view.addSubview(fpc.view) - fpc.set(contentViewController: mordalVC) + fpc.set(contentViewController: modalVC) fpc.addPanel(toParent: self) } diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard b/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard index 7f04f14b0d25ac04bf00a72ab17cf67aae33a2c3..6cdbd825352059f406623a8f1618b81f3ae03149 100644 --- a/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard +++ b/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard @@ -4,6 +4,8 @@ + + @@ -22,7 +24,7 @@ - + @@ -32,25 +34,135 @@ - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings index 7d800c1fef1f3e5275f9938158556beb83e9d36f..f522d3b855b1f13d10b6915ef5a66642b9529f91 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登录失败。 请重新登录。"; "CameraStartupErrorMessage" = "未能激活摄像机。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "由通知点选的文件已经显示了。\n你想放弃当前的编辑并显示一个新的文件吗?"; diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings index 4168400356c644fd98ab2db646f41efbd9f155d0..32cafea375d81a1f46226157da2b471034a89504 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登錄失敗。請重新登錄。"; "CameraStartupErrorMessage" = "未能激活攝像機。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "由通知點選的文件已經顯示了。 \n你想放棄當前的編輯並顯示一個新的文件嗎?"; diff --git a/AgileWorks/AgileWorks/Strings/English.strings b/AgileWorks/AgileWorks/Strings/English.strings index 527ffdb2642ecd62b071c7ef9615e68a7e534d1a..91107a41618cfb8b63f7f80bd005a3ed131043e5 100644 --- a/AgileWorks/AgileWorks/Strings/English.strings +++ b/AgileWorks/AgileWorks/Strings/English.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "cancel"; "LoginErrorMessage" = "Login failed. Please login again."; "CameraStartupErrorMessage" = "Camera failed to start."; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "A document by a notification tap is already displayed.\nDo you want to discard the current edits and display a new document?"; diff --git a/AgileWorks/AgileWorks/Strings/Japanese.strings b/AgileWorks/AgileWorks/Strings/Japanese.strings index 471ddc01a6d000f71367eee3a73c14ddbb5b5e57..5219e3cbb1ea6b136263d680437d6a5340abb686 100644 --- a/AgileWorks/AgileWorks/Strings/Japanese.strings +++ b/AgileWorks/AgileWorks/Strings/Japanese.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "キャンセル"; "LoginErrorMessage" = "ログインに失敗しました。再度ログインしてください。"; "CameraStartupErrorMessage" = "カメラの起動に失敗しました。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "既に通知タップによる書類が表示されています。\n現在の編集内容を破棄し、新たな書類を表示しますか?"; diff --git a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings index 7f2918a2e7d69ce9492609d16171ed4af02be0b4..c2874e34b6b436b87012ed152fc9568efb66d660 100644 --- a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "cancel"; "LoginErrorMessage" = "Login failed. Please login again."; "CameraStartupErrorMessage" = "Camera failed to start."; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "A document by a notification tap is already displayed.\nDo you want to discard the current edits and display a new document?"; diff --git a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings index 529cfff8a8d8b07de58ac75d4d247b181a69e7bd..2d54811b12314180f78ffcad8fe1aee295de87dd 100644 --- a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "キャンセル"; "LoginErrorMessage" = "ログインに失敗しました。再度ログインしてください。"; "CameraStartupErrorMessage" = "カメラの起動に失敗しました。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "既に通知タップによる書類が表示されています。\n現在の編集内容を破棄し、新たな書類を表示しますか?"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings index 19b2444df76dbd54b4aefb82dda79dc03ec5a87b..55c49a32e2035efed42b9127d7305890e5bf57e3 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登录失败。 请重新登录。"; "CameraStartupErrorMessage" = "未能激活摄像机。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "由通知点选的文件已经显示了。\n你想放弃当前的编辑并显示一个新的文件吗?"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings index a4898c37fbfc1b4d4866d677122dd07d3852d53b..a818da875174b5ffe9bc8460feeccf1bd670c21e 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings @@ -24,6 +24,7 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登錄失敗。請重新登錄。"; "CameraStartupErrorMessage" = "未能激活攝像機。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -58,6 +59,7 @@ //server "ServerTitle" = "接続先サーバー"; +"accessPoint" = "接続先"; // Notification "DocOverrideConfirm" = "由通知點選的文件已經顯示了。 \n你想放棄當前的編輯並顯示一個新的文件嗎?"; diff --git a/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift b/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift index 368a19e414ca4a79cc052aebf292e1404cde32f9..c487fbf96129b0eaefb494db23eb34299f5af7a4 100644 --- a/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift @@ -79,7 +79,7 @@ class MenuTableViewController: UITableViewController { private func logout() { // ログアウトタップ時処理 let message = getDisplayString(key: "LogoutConfirm", comment: "") - let defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(title: getDisplayString(key: "YES", comment: "")) + let defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(serverRemove: true, title: getDisplayString(key: "YES", comment: "")) let cancelAction = UIAlertAction(title: getDisplayString(key: "NO", comment: ""), style: .cancel) { _ in if let indexPath = self.tableView.indexPath(for: self.logoutCell) { self.tableView.deselectRow(at: indexPath, animated: true) diff --git a/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift b/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift index 07e6ba3ba31d449f8190612dbac7fdae0f4405c5..0129ea3ff158c2b45851f55b092c67bb6f47ad20 100644 --- a/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift @@ -67,12 +67,13 @@ class MenuViewController: UIViewController { switch result { case .success(let response): let userName = response.user.name - let serverName = "サーバー識別名" //TODO response.server.name + let serverName = KeychainDataStore().readServerURL()! //TODO response.server.name let sessionId = response.sessionId DispatchQueue.main.async { self.nameLabel.text = userName self.serverLabel.text = serverName KeychainDataStore().writeSessionID(sessionID: sessionId) + KeychainDataStore().writeServerName(serverName: serverName) } case .failure: DispatchQueue.main.async { diff --git a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift index 4cdd571ec243eea05a935aef7b9dd9e620a045e5..008af00dc4e616ea9c7863dea21bdadbb6904e20 100644 --- a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift @@ -18,9 +18,10 @@ class WebViewController: UIViewController { @IBOutlet private var closeButton: UIBarButtonItem! @IBOutlet private var menuButton: UIBarButtonItem! var mainWebView: WKWebView! - var subWebView: WKWebView! // 別ウインドウで開いたときの子ウインドウスタック var subViewStack = SubViewStack() + //subWebView読み込みフラグ + var subWebViewLoadingFlg = false var loadURL: String! var reloadTab = false var showNotification = false @@ -105,6 +106,7 @@ class WebViewController: UIViewController { // WebView セットアップ private func setWebView() { + print(KeychainDataStore().readServerURL()) let userContentController = WKUserContentController() let script = "document.cookie='JSESSIONID=\(KeychainDataStore().readSessionID() ?? "")'" let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false) @@ -218,6 +220,7 @@ class WebViewController: UIViewController { // セッション情報取得成功 case .success(let response): KeychainDataStore().writeSessionID(sessionID: response.sessionId) + KeychainDataStore().writeServerName(serverName: "サーバー識別名") //TODO response.server.name setStringsName(language: response.user.displayLanguage) if isInit { // 画面ロード @@ -261,7 +264,7 @@ class WebViewController: UIViewController { DispatchQueue.main.async { let title = getDisplayString(key: "RefreshTokenUpdateErrorTitle", comment: "") let message = getDisplayString(key: "RefreshTokenUpdateError", comment: "") - let defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(title: getDisplayString(key: "OK", comment: "")) + let defaultAction = AppDelegate.shared.rootViewController.logoutDefaultAction(serverRemove: true, title: getDisplayString(key: "OK", comment: "")) AppDelegate.shared.rootViewController.showAlertScreen(view: self, title: title, message: message, defaultAction: defaultAction, cancelAction: nil) } // その他エラーの場合 @@ -464,17 +467,18 @@ extension WebViewController: WKUIDelegate { func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if navigationAction.targetFrame?.isMainFrame != true { - subWebView = WKWebView(frame: self.formView.bounds, configuration: configuration) - subWebView.navigationDelegate = self - subWebView.uiDelegate = self - subViewStack.push(subWebView, superView: mainWebView) - subWebView.load(navigationAction.request) - - //キャンセルボタンの表示 - self.closeButton.title = getDisplayString(key: "Close", comment: "") - self.closeButton.isEnabled = true - - return subWebView + if !subWebViewLoadingFlg { + subWebViewLoadingFlg = true + let subWebView = WKWebView(frame: self.formView.bounds, configuration: configuration) + //読み込むが非表示 + subWebView.isHidden = true + subWebView.navigationDelegate = self + subWebView.uiDelegate = self + subViewStack.push(subWebView, superView: mainWebView) + subWebView.load(navigationAction.request) + + return subWebView + } } return nil } @@ -497,6 +501,15 @@ extension WebViewController: WKNavigationDelegate { download(url: url, saveAs: attachmentFileName) decisionHandler(.cancel) } else { + //subWebViewの場合 + if !subViewStack.isEmpty { + //subWebViewの表示 + webView.isHidden = false + //キャンセルボタンの表示 + self.closeButton.title = getDisplayString(key: "Close", comment: "") + self.closeButton.isEnabled = true + subWebViewLoadingFlg = false + } decisionHandler(.allow) } } @@ -614,9 +627,20 @@ extension WebViewController: WKNavigationDelegate { } DispatchQueue.main.async {[self] in let activityViewController = UIActivityViewController(activityItems: [tempPath], applicationActivities: nil) + //iPadのみ + if UIDevice.current.userInterfaceIdiom == .pad { + let deviceSize = UIScreen.main.bounds + if let popPC = activityViewController.popoverPresentationController { + popPC.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0) + popPC.sourceView = self.view + popPC.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0) + } + } activityViewController.completionWithItemsHandler = { (activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, activityError: Error?) in + //subWebViewを消す self.closeTapped() - + subWebViewLoadingFlg = false + guard completed else { return } diff --git a/AgileWorks/Common/DataStore/KeychainDataStore.swift b/AgileWorks/Common/DataStore/KeychainDataStore.swift index 681a27c146c33e8e13104f779fb020c1ba38129b..cd7b89efbe48befa4d0838f4cde456d7c7336c11 100644 --- a/AgileWorks/Common/DataStore/KeychainDataStore.swift +++ b/AgileWorks/Common/DataStore/KeychainDataStore.swift @@ -12,7 +12,6 @@ import UIKit final class KeychainDataStore: DataStoreProtocol { private let service = "AgileWorks" - private let groupId = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + Configuration.shared.awShareBundleIdentifier private let kAccessToken: String = "AccessToken" private let kOAuthState: String = "OAuthState" private let kLanguage: String = "Localizable" @@ -20,6 +19,7 @@ final class KeychainDataStore: DataStoreProtocol { private let kContextPath: String = "ContextPath" private let kDeviceID: String = "DeviceID" private let kSessionID: String = "SessionID" + private let kServerName: String = "ServerName" // アクセストークンの書き込み func writeAccessToken(accessToken: String) { @@ -27,18 +27,24 @@ final class KeychainDataStore: DataStoreProtocol { } // アクセストークンの読み込み - func readAccessToken() -> String? { + func readAccessToken(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kAccessToken, serverNumber: serverNumber) + } return getKeychainValue(key: kAccessToken) } // アクセストークンの削除 - func removeAccessToken() { + func removeAccessToken(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kAccessToken, serverNumber: serverNumber) + } removeKeychainValue(key: kAccessToken) } // 認可ステータスの書き込み func writeOAuthState(authState: OIDAuthState) { - let keychain = Keychain(service: service, accessGroup: groupId) + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) guard let object = try? NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: true) else { return } @@ -47,7 +53,7 @@ final class KeychainDataStore: DataStoreProtocol { // 認可ステータスの読み込み func readOAuthState() -> OIDAuthState? { - let keychain = Keychain(service: service, accessGroup: groupId) + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) if let object = keychain[data: kOAuthState] { guard let authState = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(object) as? OIDAuthState else { return nil @@ -58,7 +64,10 @@ final class KeychainDataStore: DataStoreProtocol { } // 認可ステータスの削除 - func removeOAuthState() { + func removeOAuthState(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kOAuthState, serverNumber: serverNumber) + } removeKeychainValue(key: kOAuthState) } @@ -98,7 +107,10 @@ final class KeychainDataStore: DataStoreProtocol { } // コンテキストパスの読み込み - func readContextPath() -> String? { + func readContextPath(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kContextPath, serverNumber: serverNumber) + } return getKeychainValue(key: kContextPath) } @@ -113,8 +125,15 @@ final class KeychainDataStore: DataStoreProtocol { } // デバイスIDの読み込み - func readDeviceID() -> Int? { - guard let deviceIDString = getKeychainValue(key: kDeviceID) else { + func readDeviceID(serverNumber: Int? = nil) -> Int? { + var deviceIDString: String? + if let serverNumber = serverNumber { + deviceIDString = getKeychainValue(key: kDeviceID, serverNumber: serverNumber) + } else { + deviceIDString = getKeychainValue(key: kDeviceID) + } + + guard let deviceIDString = deviceIDString else { return nil } guard let deviceID = Int(deviceIDString) else { @@ -124,7 +143,10 @@ final class KeychainDataStore: DataStoreProtocol { } // デバイスIDの削除 - func removeDeviceID() { + func removeDeviceID(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kDeviceID, serverNumber: serverNumber) + } removeKeychainValue(key: kDeviceID) } @@ -139,23 +161,62 @@ final class KeychainDataStore: DataStoreProtocol { } // セッションIDの削除 - func removeSessionID() { - removeKeychainValue(key: kSessionID) + func removeSessionID(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + return removeKeychainValue(key: kSessionID, serverNumber: serverNumber) + } + return removeKeychainValue(key: kSessionID) + } + + // サーバー識別名の書き込み + func writeServerName(serverName: String) { + setKeychainValue(key: kServerName, value: serverName) + } + + // サーバー識別名の読み込み + func readServerName(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kServerURL, serverNumber: serverNumber) + } + return getKeychainValue(key: kServerURL) + } + + // サーバー識別名の削除 + func removeServerName() { + removeKeychainValue(key: kServerName) } // KeyChain へ指定の値をセット private func setKeychainValue(key: String, value: String) { - let keychain = Keychain(service: service, accessGroup: groupId) + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) + keychain[key] = value + } + private func setKeychainValue(key: String, value: String, serverNumber: Int) { + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readBaseGroupId() + String(serverNumber)) keychain[key] = value } + // KeyChain から指定の値を取得 private func getKeychainValue(key: String) -> String? { - let keychain = Keychain(service: service, accessGroup: groupId) + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) + return keychain[key] + } + private func getKeychainValue(key: String, serverNumber: Int) -> String? { + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readBaseGroupId() + String(serverNumber)) return keychain[key] } + // KeyChain から指定の値を削除 private func removeKeychainValue(key: String) { - let keychain = Keychain(service: service, accessGroup: groupId) + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) + do { + try keychain.remove(key) + } catch { + log.e(error) + } + } + private func removeKeychainValue(key: String, serverNumber: Int) { + let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readBaseGroupId() + String(serverNumber)) do { try keychain.remove(key) } catch { diff --git a/AgileWorks/Common/DataStore/UserDefaultsDataStore.swift b/AgileWorks/Common/DataStore/UserDefaultsDataStore.swift index 1e39b583f20c109ce0f050bb374ddd37863cf1bf..aaa5091d003313a54dbcfce9dd06ae8c18ba57d1 100644 --- a/AgileWorks/Common/DataStore/UserDefaultsDataStore.swift +++ b/AgileWorks/Common/DataStore/UserDefaultsDataStore.swift @@ -17,9 +17,15 @@ final class UserDefaultsDataStore: DataStoreProtocol { private let kFirebaseRemovalKey: String = "FirebaseRemoval" private let kLastAcceptedLicenseVersionKey: String = "LastAppceptedLicenseVersion" private let kUpdateWidgetFlgKey : String = "UpdateWidgetFlg" + private let kServerListkey: String = "ServerList" + private let kGroupIdkey: String = "GroupId" + private let kTapServer: String = "TapServer" + private let baseGroupId = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + Configuration.shared.awShareBundleIdentifier + + private let severCount = 5 init() { - sharedDefaults = UserDefaults() + sharedDefaults = UserDefaults(suiteName: "group.jp.atled.agileworks")! } func writeFirebaseRemoval(flag: Bool) { @@ -41,17 +47,99 @@ final class UserDefaultsDataStore: DataStoreProtocol { func readLastAcceptedLicenseVersion() -> Int? { return sharedDefaults.integer(forKey: kLastAcceptedLicenseVersionKey) } - + //ウィジェット更新フラグ func writeUpdateWidgetFlg(update: Bool) { sharedDefaults.set(update, forKey: kUpdateWidgetFlgKey) } - + func readUpdateWidgetFlg() -> Bool? { return sharedDefaults.bool(forKey: kUpdateWidgetFlgKey) } - + func removeUpdateWidgetFlg(update: Bool) { sharedDefaults.removeObject(forKey: kUpdateWidgetFlgKey) } + + //接続先サーバー管理 + func writeServerList(list: [Int]) { + sharedDefaults.set(list, forKey: kServerListkey) + } + + func readServerList() -> [Int] { + if let serverList = sharedDefaults.array(forKey: kServerListkey) as? [Int] { + return serverList + } + return [] + } + + func removeServer() { + var serverList = readServerList() + if !serverList.isEmpty { + //先頭(ログアウト処理を行ったサーバー)をリストから削除 + serverList.removeFirst() + writeServerList(list: serverList) + } + } + + func removeAllServer() { + writeServerList(list: []) + } + + //サーバー接続先を保存できる識別番号を探す + func serchEmptyServerData() -> Int? { + var serverList = readServerList() + if serverList.isEmpty { + return serverList.count + } + for num in 0 ..< severCount { + if !serverList.contains(num) { + return num + } + } + return nil + } + + //サーバー追加 + func addServerList() { + var serverList = readServerList() + if let serverNumber = serchEmptyServerData() { + serverList.insert(serverNumber, at: 0) + writeServerList(list: serverList) + } + } + + //サーバー並び替え + func changeServerList(firstServer: Int) { + var serverList = readServerList() + if serverList.contains(firstServer) { + //要素を削除してから先頭に追加する + serverList.removeAll(where: { $0 == firstServer }) + serverList.insert(firstServer, at: 0) + writeServerList(list: serverList) + } + } + + func setTapServer(serverNumber: Int) { + sharedDefaults.set(serverNumber, forKey: kTapServer) + } + + func readTapServer() -> Int? { + return sharedDefaults.integer(forKey: kTapServer) + } + + func setGroupId(serverNumber: Int) { + sharedDefaults.set(baseGroupId + String(serverNumber), forKey: kGroupIdkey) + } + func readGroupId() -> String? { + if let groupId = sharedDefaults.string(forKey: kGroupIdkey) as? String{ + return groupId + }else { + setGroupId(serverNumber: 0) + return sharedDefaults.string(forKey: kGroupIdkey) + } + } + func readBaseGroupId() -> String { + return baseGroupId + } } diff --git a/AgileWorks/Common/OAuthService.swift b/AgileWorks/Common/OAuthService.swift index 078679976a433beb5f2236aeed921a077964376e..1a629126d4b4d25c7a187482aa9606a6e17ad726 100644 --- a/AgileWorks/Common/OAuthService.swift +++ b/AgileWorks/Common/OAuthService.swift @@ -53,10 +53,7 @@ public class OAuthService: NSObject { } KeychainDataStore().writeAccessToken(accessToken: accessToken) - //ウィジェットの更新 - if #available(iOS 14.0, *) { - WidgetCenter.shared.reloadAllTimelines() - } + completion(.success(())) } else { self.setAuthState(nil) diff --git a/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift b/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift index 3dae7caae66b3b29a4150915f74be6cdf8d5c1ff..d6433ccaf2d105e0b23d6fec3b7356dacb1fde93 100644 --- a/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift +++ b/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift @@ -12,21 +12,16 @@ struct DeleteDeviceEndpoint: APIEndpoint { typealias Response = DeviceResponse let method: HttpMethod = .POST - let path = "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/MobileAppApi/Device" + + let path: String var requestBody: Data? - var headerFields: [String: String]? { - return ["Authorization": "Bearer \(KeychainDataStore().readAccessToken() ?? "")", - "X-ATLED-AW-Device-Id": KeychainDataStore().readDeviceID()?.description ?? "", - "Content-Type": "application/json"] - } + var headerFields: [String: String]? - var pathParameters: [String]? { - return [KeychainDataStore().readDeviceID()?.description ?? ""] - } + var pathParameters: [String]? - init(request: DeviceRequest) { + init(request: DeviceRequest, serverNumber: Int?) { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase @@ -35,6 +30,13 @@ struct DeleteDeviceEndpoint: APIEndpoint { } catch { log.e(error.localizedDescription) } + self.path = "/" + (KeychainDataStore().readContextPath(serverNumber: serverNumber) ?? "") + "/Broker/MobileAppApi/Device" + + self.headerFields = ["Authorization": "Bearer \(KeychainDataStore().readAccessToken(serverNumber: serverNumber) ?? "")", + "X-ATLED-AW-Device-Id": KeychainDataStore().readDeviceID(serverNumber: serverNumber)?.description ?? "", + "Content-Type": "application/json"] + + self.pathParameters = [KeychainDataStore().readDeviceID(serverNumber: serverNumber)?.description ?? ""] } } diff --git a/AgileWorks/Common/WidgetView.swift b/AgileWorks/Common/WidgetView.swift index 49e429b313c07aa0d848af38b61a1e7fd1c8062c..90517e9a94f85b876389245e341b878ba276f4d7 100644 --- a/AgileWorks/Common/WidgetView.swift +++ b/AgileWorks/Common/WidgetView.swift @@ -39,11 +39,37 @@ func previewItems() -> [ApprovalItem] { } struct EntryData { + var serverNumber: Int? var approvalItems: [ApprovalItem] var message: String? var viewController: UIViewController? } +struct TodayWidgetView: View { + var entrys: [EntryData] + @State private var selection = 0 + var serverList = UserDefaultsDataStore().readServerList() + + var body: some View { + VStack (spacing: 0) { + //未ログインの場合はサーバー切り替えボタンを表示しない + if !UserDefaultsDataStore().readServerList().isEmpty { + Picker(selection: $selection, label: Text("")) { + ForEach(0.. EntryData { + var errorEntry = entry + errorEntry.message = "-" + return errorEntry + } + var body: some View { //API取得成功 if entry.message == nil { if let num = findApproval() { //「承認依頼」項目あり if #available(iOS 14.0, *) { - ApprovalItemView(approvalItem: entry.approvalItems[num], message: entry.message) - .widgetURL(URL(string: Configuration.shared.awURL + "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/Mobile#docList_" + "RequestApproval")) + let url = getURL(approvalItem: entry.approvalItems[num], serverNumber: entry.serverNumber) + ApprovalItemView(itemNum: num, entry: entry) + .widgetURL(URL(string: url)) } } else { //「承認依頼」項目なし - ApprovalItemView(approvalItem: ApprovalItem(code: "", name: "", count: 0), message: "-") + ApprovalItemView(itemNum: 0, entry: errorEntry()) } } else { //APIサーバーエラー if entry.message == "-" { - ApprovalItemView(approvalItem: ApprovalItem(code: "", name: "", count: 0), message: "-") + ApprovalItemView(itemNum: 0, entry: errorEntry()) } else {//ログイン・コネクションエラー - FailureWidgetView(entry: entry) + if #available(iOS 14.0, *) { + let url = getWorkURL(serverNumber: entry.serverNumber) + Link(destination: URL(string: url)!, label: { + FailureWidgetView(entry: entry) + }) + } } } } @@ -108,7 +149,8 @@ struct ApprovalWidgetView: View { //書類状況を表示するための各BOXビュー struct StatusBoxView: View { var entry: EntryData - + @State private var selection = 0 + let serverList = UserDefaultsDataStore().readServerList() //書類状態の種類をカウント func rowCnt() -> Int { var row = entry.approvalItems.count / 2 @@ -152,32 +194,39 @@ struct StatusBoxView: View { } var body: some View { - GeometryReader { geometry in - //ios13・14~状態確認ウィジェット - if geometry.size.height > kCellDefaultHight { - VStack(spacing: 5) { - ForEach(0.. kCellDefaultHight { + VStack(spacing: 5) { + ForEach(0.. String { - if let status = getStatus(code: approvalItem.code) { - return Configuration.shared.awURL + "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/Mobile#docList_" + status - } else { - return Configuration.shared.awURL + "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/Mobile#work" - } + func getApprovalItem() -> ApprovalItem { + return entry.approvalItems[itemNum] } //遷移先URLはアプリ側で設定するためURL末尾のステータスのみ設定(iOS13) func getStatusURL() -> String { //スキーム+ステータスで設定 + let approvalItem = getApprovalItem() if let status = getStatus(code: approvalItem.code) { return urlScheme + status } @@ -232,60 +276,100 @@ struct LinkView: View { var body: some View { if #available(iOS 14.0, *) { - Link(destination: URL(string: getURL())!, label: { - ApprovalItemView(approvalItem: approvalItem, message: message) + let url = getURL(approvalItem: getApprovalItem(), serverNumber: entry.serverNumber) + Link(destination: URL(string: url)!, label: { + ApprovalItemView(itemNum: itemNum, entry: entry, serverNameFlag: false) }) } else { - ApprovalItemView(approvalItem: approvalItem, message: message) - .onTapGesture { - if viewController != nil { - viewController!.extensionContext?.open(NSURL(fileURLWithPath: getStatusURL())as URL, completionHandler: nil) - } - } + ApprovalItemView(itemNum: itemNum, entry: entry) } } } //書類状況の内容を表示するビュー struct ApprovalItemView: View { - let approvalItem: ApprovalItem - let message: String? + let itemNum: Int + let entry: EntryData + let serverList = UserDefaultsDataStore().readServerList() + var serverNameFlag = true + + func getApprovalItem() -> ApprovalItem { + return entry.approvalItems[itemNum] + } var body: some View { - GeometryReader { geometry in - HStack(spacing: 0) { - Image(systemName: "folder") - .frame(width: geometry.size.width * 0.3, height: geometry.size.height) - .foregroundColor(Color.textColor) - if message == nil { - VStack(alignment: .leading) { - Text(approvalItem.name) - .frame(alignment: .leading) - .foregroundColor(Color.textColor) - Text("\(approvalItem.count)").font(.subheadline) - .frame(alignment: .leading) - .foregroundColor(Color.textColor) + VStack (spacing: 0) { + if #available(iOS 14.0, *) { + if serverNameFlag { + if let serverNumber = entry.serverNumber { + Text(KeychainDataStore().readServerName(serverNumber: serverNumber)!) + } + } + } + GeometryReader { geometry in + HStack(spacing: 0) { + Image(systemName: "folder") + .frame(width: geometry.size.width * 0.3, height: geometry.size.height) + .foregroundColor(Color.textColor) + if entry.message == nil { + VStack(alignment: .leading) { + Text(getApprovalItem().name) + .frame(alignment: .leading) + .foregroundColor(Color.textColor) + Text("\(getApprovalItem().count)").font(.subheadline) + .frame(alignment: .leading) + .foregroundColor(Color.textColor) + } + } else { //APIサーバーエラーの場合の表示 + Text(entry.message ?? "-") } - } else { //APIサーバーエラーの場合の表示 - Text(message ?? "-") } + Spacer() } - Spacer() + .background(Color.widgetBoxColor) + .cornerRadius(20) } - .background(Color.widgetBoxColor) - .cornerRadius(20) } } //エラー文言を表示するビュー struct FailureWidgetView: View { - var entry: EntryData + let entry: EntryData + let serverList = UserDefaultsDataStore().readServerList() var body: some View { - Text(entry.message ?? "") - .foregroundColor(Color.textColor) + if !serverList.isEmpty { + if let serverNumber = entry.serverNumber { + Text(KeychainDataStore().readServerName(serverNumber: serverNumber)!) + } + } + Text(entry.message ?? "") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .foregroundColor(Color.textColor) + } +} + +func getWorkURL(serverNumber: Int?) -> String { + var url = Configuration.shared.awURL + "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/Mobile#work" + if let serverNumber = serverNumber { + url += "?serverNumber=" + String(serverNumber) + } + return url +} + +//遷移先URLの取得(iOS14以降) +func getURL(approvalItem: ApprovalItem, serverNumber: Int?) -> String { + if let status = getStatus(code: approvalItem.code) { + var url = Configuration.shared.awURL + "/" + (KeychainDataStore().readContextPath() ?? "") + "/Broker/Mobile#docList_" + status + if let serverNumber = serverNumber { + return url + "?serverNumber=" + String(serverNumber) + } + return url + } else { + return getWorkURL(serverNumber: serverNumber) } } + func checkApprovalItems(approvalItems: [ApprovalItem]) -> [ApprovalItem] { var result = [ApprovalItem]() for item in approvalItems { @@ -296,6 +380,62 @@ func checkApprovalItems(approvalItems: [ApprovalItem]) -> [ApprovalItem] { return result } +enum WidgetAPIResult { + case success(T) + case nodate(String) + case failure(String) +} + +func fetch(completion: @escaping (WidgetAPIResult) -> Void) { + var serverList = UserDefaultsDataStore().readServerList() + if serverList.isEmpty { + completion(.failure(getDisplayString(key: "NotLogedin", comment: ""))) + return + } + + let approvalsEndpoint = GetApprovalsEndpoint() + + while UserDefaultsDataStore().readUpdateWidgetFlg() ?? false { + //別のウィジェットが更新中の場合は待つ + } + UserDefaultsDataStore().writeUpdateWidgetFlg(update: true) + + Session.send(approvalsEndpoint) { result in + UserDefaultsDataStore().writeUpdateWidgetFlg(update: false) + + switch result { + case .success(let response): + if !response.items.isEmpty { + completion(.success(response)) + } else { + completion(.nodate(getDisplayString(key: "NoData", comment: ""))) + } + case .failure(let error): + var errorMessage = "-" + switch error { + case .connectionError(let error): + if let message = errorMessageNSError(error: error) { + errorMessage = message + } + default: + break + } + completion(.failure(errorMessage)) + } + } +} + +func errorMessageNSError(error: Error) -> String? { + switch URLError.Code(rawValue: (error as NSError).code) { + case .notConnectedToInternet: // -1009 + return getDisplayString(key: "NotConnectNetworkError", comment: "") + case .timedOut: // -1001 + return getDisplayString(key: "TimedOutNetworkError", comment: "") + default: + return nil + } +} + //URLの末尾に設定するステータスを返す func getStatus(code: String) -> String? { var status: String? diff --git a/AgileWorks/Configurations/Debug-Production-Intents.xcconfig b/AgileWorks/Configurations/Debug-Production-Intents.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..26d77624699fb7ee63b9d7d2264a961a1db75efa --- /dev/null +++ b/AgileWorks/Configurations/Debug-Production-Intents.xcconfig @@ -0,0 +1,13 @@ +// +// Debug-Production-Intents.xcconfig +// AgileWorks +// +// Created by Azuma Kasumi on 2023/06/16. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +#include "../Pods/Target Support Files/Pods-All-AgileWorks/Pods-All-AgileWorks.debug-production.xcconfig" +#include "Build/Debug.xcconfig" +#include "Flavor/Production-Intents.xcconfig" diff --git a/AgileWorks/Configurations/Flavor/Production-Intents.xcconfig b/AgileWorks/Configurations/Flavor/Production-Intents.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..61a9968410fd3d8c5b7b3e0048ec2aa97f2c7626 --- /dev/null +++ b/AgileWorks/Configurations/Flavor/Production-Intents.xcconfig @@ -0,0 +1,17 @@ +// +// Production-Intents.xcconfig +// AgileWorks +// +// Created by Azuma Kasumi on 2023/06/16. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +#include "Base-Production.xcconfig" + +DISPLAY_NAME = AgileWorks + +PRODUCT_BUNDLE_IDENTIFIER = $(AGILE_WORKS_SHARE_BUNDLE_IDENTIFIER).IntentsExtension + +CODE_SIGN_ENTITLEMENTS = IntentsExtension/IntentsExtension.entitlements diff --git a/AgileWorks/Configurations/Release-Production-Intents.xcconfig b/AgileWorks/Configurations/Release-Production-Intents.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..22583ba4f8f2ae87ea357bfc59a3cd8c558a872f --- /dev/null +++ b/AgileWorks/Configurations/Release-Production-Intents.xcconfig @@ -0,0 +1,13 @@ +// +// Release-Production-Intents.xcconfig +// AgileWorks +// +// Created by Azuma Kasumi on 2023/06/16. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +#include "../Pods/Target Support Files/Pods-All-AgileWorks/Pods-All-AgileWorks.release-production.xcconfig" +#include "Build/Release.xcconfig" +#include "Flavor/Production-Intents.xcconfig" diff --git a/AgileWorks/IntentsExtension/Info.plist b/AgileWorks/IntentsExtension/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..5b98bb39859885d865f10b4db4a1b56e99111ba5 --- /dev/null +++ b/AgileWorks/IntentsExtension/Info.plist @@ -0,0 +1,36 @@ + + + + + AppConfig + + AGILE_WORKS_SHARE_BUNDLE_IDENTIFIER + $(AGILE_WORKS_SHARE_BUNDLE_IDENTIFIER) + OAUTH_CLIENTID + $(OAUTH_CLIENTID) + + AppIdentifierPrefix + $(AppIdentifierPrefix) + NSExtension + + NSExtensionAttributes + + IntentsRestrictedWhileLocked + + IntentsRestrictedWhileProtectedDataUnavailable + + IntentsSupported + + INSearchForMessagesIntent + INSendMessageIntent + INSetMessageAttributeIntent + WidgetConfigurationIntent + + + NSExtensionPointIdentifier + com.apple.intents-service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).IntentHandler + + + diff --git a/AgileWorks/IntentsExtension/IntentHandler.swift b/AgileWorks/IntentsExtension/IntentHandler.swift new file mode 100644 index 0000000000000000000000000000000000000000..5fbafd815a4ec8d0ddb76a68fbe95265cf8e473f --- /dev/null +++ b/AgileWorks/IntentsExtension/IntentHandler.swift @@ -0,0 +1,137 @@ +// +// IntentHandler.swift +// IntentsExtension +// +// Created by Azuma Kasumi on 2023/06/16. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import Intents + +// As an example, this class is set up to handle Message intents. +// You will want to replace this or add other intents as appropriate. +// The intents you wish to handle must be declared in the extension's Info.plist. + +// You can test your example integration by saying things to Siri like: +// "Send a message using " +// " John saying hello" +// "Search for messages in " + +class IntentHandler: INExtension, WidgetConfigurationIntentHandling{ + func provideParameterOptionsCollection(for intent: WidgetConfigurationIntent, searchTerm: String?, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + var widgetTypeIdentifiers: [ServerType] = [] + let serverList = UserDefaultsDataStore().readServerList() + for i in 0.. Any { + // This is the default implementation. If you want different objects to handle different intents, + // you can override this and return the handler you want for that particular intent. + + return self + } + + // MARK: - INSendMessageIntentHandling + + // Implement resolution methods to provide additional information about your intent (optional). + func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) { + if let recipients = intent.recipients { + + // If no recipients were provided we'll need to prompt for a value. + if recipients.count == 0 { + completion([INSendMessageRecipientResolutionResult.needsValue()]) + return + } + + var resolutionResults = [INSendMessageRecipientResolutionResult]() + for recipient in recipients { + let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts + switch matchingContacts.count { + case 2 ... Int.max: + // We need Siri's help to ask user to pick one from the matches. + resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)] + + case 1: + // We have exactly one matching contact + resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)] + + case 0: + // We have no contacts matching the description provided + resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()] + + default: + break + + } + } + completion(resolutionResults) + } else { + completion([INSendMessageRecipientResolutionResult.needsValue()]) + } + } + + func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + if let text = intent.content, !text.isEmpty { + completion(INStringResolutionResult.success(with: text)) + } else { + completion(INStringResolutionResult.needsValue()) + } + } + + // Once resolution is completed, perform validation on the intent and provide confirmation (optional). + + func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { + // Verify user is authenticated and your app is ready to send a message. + + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity) + completion(response) + } + + // Handle the completed intent (required). + + func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { + // Implement your application logic to send a message here. + + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity) + completion(response) + } + + // Implement handlers for each intent you wish to handle. As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes. + + // MARK: - INSearchForMessagesIntentHandling + + func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) { + // Implement your application logic to find a message that matches the information in the intent. + + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) + let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity) + // Initialize with found message's attributes + response.messages = [INMessage( + identifier: "identifier", + content: "I am so excited about SiriKit!", + dateSent: Date(), + sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil), + recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)] + )] + completion(response) + } + + // MARK: - INSetMessageAttributeIntentHandling + + func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) { + // Implement your application logic to set the message attribute here. + + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) + let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity) + completion(response) + } +} diff --git a/AgileWorks/IntentsExtension/IntentsExtensionDebug-Production.entitlements b/AgileWorks/IntentsExtension/IntentsExtensionDebug-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..df7a8b6d7ec98bc391c8c8954c2af1545824a741 --- /dev/null +++ b/AgileWorks/IntentsExtension/IntentsExtensionDebug-Production.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.application-groups + + group.jp.atled.agileworks + + keychain-access-groups + + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 + + + diff --git a/AgileWorks/IntentsExtension/IntentsExtensionRelease-Production.entitlements b/AgileWorks/IntentsExtension/IntentsExtensionRelease-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..df7a8b6d7ec98bc391c8c8954c2af1545824a741 --- /dev/null +++ b/AgileWorks/IntentsExtension/IntentsExtensionRelease-Production.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.application-groups + + group.jp.atled.agileworks + + keychain-access-groups + + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 + + + diff --git a/AgileWorks/TodayExtension/TodayExtension-Production.entitlements b/AgileWorks/TodayExtension/TodayExtension-Production.entitlements index 71e202e86c51a580927e119b7fcbf713d2418ec3..df7a8b6d7ec98bc391c8c8954c2af1545824a741 100644 --- a/AgileWorks/TodayExtension/TodayExtension-Production.entitlements +++ b/AgileWorks/TodayExtension/TodayExtension-Production.entitlements @@ -2,9 +2,17 @@ + com.apple.security.application-groups + + group.jp.atled.agileworks + keychain-access-groups - $(AppIdentifierPrefix)jp.atled.agileworks + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 diff --git a/AgileWorks/TodayExtension/TodayExtensionRelease-Production.entitlements b/AgileWorks/TodayExtension/TodayExtensionRelease-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..df7a8b6d7ec98bc391c8c8954c2af1545824a741 --- /dev/null +++ b/AgileWorks/TodayExtension/TodayExtensionRelease-Production.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.application-groups + + group.jp.atled.agileworks + + keychain-access-groups + + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 + + + diff --git a/AgileWorks/TodayExtension/View/TodayViewController.swift b/AgileWorks/TodayExtension/View/TodayViewController.swift index 304cc9db857c823dd13ec20222bd47d346d3056a..fd075a2ea4aa6f86a44b4c1514ebe515e0044313 100644 --- a/AgileWorks/TodayExtension/View/TodayViewController.swift +++ b/AgileWorks/TodayExtension/View/TodayViewController.swift @@ -25,24 +25,49 @@ class TodayViewController: UIViewController, NCWidgetProviding { } func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { + let serverList = UserDefaultsDataStore().readServerList() + var entrys: [EntryData] = [] + var entry: EntryData = EntryData(approvalItems: previewItems(), message: getDisplayString(key: "NotLogedin", comment: ""), viewController: self) + + if serverList.isEmpty { + entrys.append(entry) + } + + //ログイン中の書類件数を全て取得する + for server in serverList { + UserDefaultsDataStore().setGroupId(serverNumber: server) + entry = resultEntry() + entrys.append(entry) + + } + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + + //描画 + var rootView = TodayWidgetView(entrys: entrys) + self.callWidgetView(vc: UIHostingController(rootView: rootView)) + completionHandler(NCUpdateResult.newData) + } + + func resultEntry() -> EntryData{ + var fetchFlag = true + var entry = EntryData(approvalItems: previewItems(), message: getDisplayString(key: "NotLogedin", comment: ""), viewController: self) fetch { result in - DispatchQueue.main.async { - var rootView: StatusCheckWidgetView - switch result { - case .success(let response): - self.approvalItems = checkApprovalItems(approvalItems: response.items) - rootView = StatusCheckWidgetView(entry: EntryData(approvalItems: self.approvalItems, message: nil, viewController: self)) - completionHandler(.newData) - case .nodate(let message): - rootView = StatusCheckWidgetView(entry: EntryData(approvalItems: [ApprovalItem](), message: message, viewController: self)) - completionHandler(.noData) - case .failure(let message): - rootView = StatusCheckWidgetView(entry: EntryData(approvalItems: previewItems(), message: message, viewController: self)) - completionHandler(.failed) - } - self.callWidgetView(vc: UIHostingController(rootView: rootView)) + switch result { + case .success(let response): + self.approvalItems = checkApprovalItems(approvalItems: response.items) + + entry = EntryData(approvalItems: self.approvalItems, viewController: self) + case .nodate(let message): + entry = EntryData(approvalItems: [ApprovalItem](), message: message, viewController: self) + case .failure(let message): + entry = EntryData(approvalItems: previewItems(), message: message, viewController: self) } + fetchFlag = false + } + while fetchFlag{ + Thread.sleep(forTimeInterval: 0.5) } + return entry } func callWidgetView(vc: UIViewController) { @@ -84,47 +109,3 @@ extension TodayViewController: UITableViewDelegate { } } */ - -extension TodayViewController { - func fetch(completion: @escaping (APIResult) -> Void) { - guard KeychainDataStore().readAccessToken() != nil else { - completion(.failure(getDisplayString(key: "NotLogedin", comment: ""))) - return - } - - let approvalsEndpoint = GetApprovalsEndpoint() - - Session.send(approvalsEndpoint) { result in - switch result { - case .success(let response): - if !response.items.isEmpty { - completion(.success(response)) - } else { - completion(.nodate(getDisplayString(key: "NoData", comment: ""))) - } - case .failure(let error): - var errorMessage = "-" - switch error { - case .connectionError(let error): - if let message = self.errorMessageNSError(error: error) { - errorMessage = message - } - default: - break - } - completion(.failure(errorMessage)) - } - } - } - - func errorMessageNSError(error: Error) -> String? { - switch URLError.Code(rawValue: (error as NSError).code) { - case .notConnectedToInternet: // -1009 - return getDisplayString(key: "NotConnectNetworkError", comment: "") - case .timedOut: // -1001 - return getDisplayString(key: "TimedOutNetworkError", comment: "") - default: - return nil - } - } -} diff --git a/AgileWorks/WidgetExtension/WidgetConfiguration.intentdefinition b/AgileWorks/WidgetExtension/WidgetConfiguration.intentdefinition new file mode 100644 index 0000000000000000000000000000000000000000..459916adb2a9107dc359c7ac691d5b3a654989dd --- /dev/null +++ b/AgileWorks/WidgetExtension/WidgetConfiguration.intentdefinition @@ -0,0 +1,152 @@ + + + + + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + APtLEj + INIntentDefinitionSystemVersion + 21G217 + INIntentDefinitionToolsBuildVersion + 14B47b + INIntentDefinitionToolsVersion + 14.1 + INIntents + + + INIntentCategory + information + INIntentDescriptionID + AJiNw5 + INIntentEligibleForWidgets + + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 2 + INIntentName + WidgetConfiguration + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + サーバー名 + INIntentParameterDisplayNameID + dpfWQr + INIntentParameterDisplayPriority + 1 + INIntentParameterName + parameter + INIntentParameterObjectType + ServerType + INIntentParameterObjectTypeNamespace + APtLEj + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterSupportsSearch + + INIntentParameterTag + 2 + INIntentParameterType + Object + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Widget Configuration + INIntentTitleID + 4UDv2R + INIntentType + Custom + INIntentVerb + View + + + INTypes + + + INTypeDisplayName + ServerType + INTypeDisplayNameID + zid8zR + INTypeLastPropertyTag + 103 + INTypeName + ServerType + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + + + + diff --git a/AgileWorks/WidgetExtension/WidgetExtension.entitlements b/AgileWorks/WidgetExtension/WidgetExtension.entitlements index 71e202e86c51a580927e119b7fcbf713d2418ec3..df7a8b6d7ec98bc391c8c8954c2af1545824a741 100644 --- a/AgileWorks/WidgetExtension/WidgetExtension.entitlements +++ b/AgileWorks/WidgetExtension/WidgetExtension.entitlements @@ -2,9 +2,17 @@ + com.apple.security.application-groups + + group.jp.atled.agileworks + keychain-access-groups - $(AppIdentifierPrefix)jp.atled.agileworks + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 diff --git a/AgileWorks/WidgetExtension/WidgetExtension.swift b/AgileWorks/WidgetExtension/WidgetExtension.swift index cf996405c90e19502663560304c9f5f1bfe44136..7f9b2697fd5d938f69b3723bc2782a3c10546d2e 100644 --- a/AgileWorks/WidgetExtension/WidgetExtension.swift +++ b/AgileWorks/WidgetExtension/WidgetExtension.swift @@ -9,43 +9,76 @@ import WidgetKit import SwiftUI -struct Provider: TimelineProvider { +struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(date: Date(), approvalItems: previewItems(), message: nil) + let entryData = EntryData(approvalItems: previewItems()) + return SimpleEntry(date: Date(), entryData: entryData) } - func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { - let entry = SimpleEntry(date: Date(), approvalItems: previewItems(), message: nil) + func getSnapshot(for configuration: WidgetConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { + let entryData = EntryData(approvalItems: previewItems()) + let entry = SimpleEntry(date: Date(), entryData: entryData) completion(entry) } - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + func getTimeline(for configuration: WidgetConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { var entries: [SimpleEntry] = [] // Generate a timeline consisting of five entries an hour apart, starting from the current date. - let entryDate = Calendar.current.date(byAdding: .minute, value: 15, to: Date()) ?? Date() - + let updateTime = Calendar.current.date(byAdding: .minute, value: 15, to: Date()) ?? Date() + let serverNumber = getServerNumber(configuration: configuration) + if serverNumber != nil { + UserDefaultsDataStore().setGroupId(serverNumber: serverNumber!) + } + fetch { result in DispatchQueue.main.async { switch result { case .success(let response): - entries.append(SimpleEntry(date: entryDate, approvalItems: checkApprovalItems(approvalItems: response.items), message: nil)) + let approvalItems = checkApprovalItems(approvalItems: response.items) + let entryData = EntryData(serverNumber: getServerNumber(configuration: configuration), approvalItems: approvalItems) + entries.append(SimpleEntry(date: updateTime, entryData: entryData)) case .nodate(let message): - entries.append(SimpleEntry(date: entryDate, approvalItems: [ApprovalItem](), message: message)) + let entryData = EntryData(serverNumber: getServerNumber(configuration: configuration), approvalItems: [ApprovalItem](), message: message) + entries.append(SimpleEntry(date: updateTime, entryData: entryData)) case .failure(let message): - entries.append(SimpleEntry(date: entryDate, approvalItems: previewItems(), message: message)) + let entryData = EntryData(serverNumber: getServerNumber(configuration: configuration), approvalItems: previewItems(), message: message) + entries.append(SimpleEntry(date: updateTime, entryData: entryData)) } - let timeline = Timeline(entries: entries, policy: .after(entryDate)) + let timeline = Timeline(entries: entries, policy: .after(updateTime)) completion(timeline) } } + let serverList = UserDefaultsDataStore().readServerList() + if !serverList.isEmpty { + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + } + } + + func getServerNumber(configuration: WidgetConfigurationIntent) -> Int? { + var serverNumber: Int? + + let serverList = UserDefaultsDataStore().readServerList() + if serverList.isEmpty { + return nil + } + // ウィジェット構成 + if configuration.parameter == nil { + serverNumber = serverList.first! + } else { + serverNumber = Int((configuration.parameter?.identifier!)!)! + //ウィジェットの構成が古い(ログアウトされたサーバーが選択されている)場合 + if !serverList.contains(serverNumber!) { + serverNumber = serverList.first! + } + } + return serverNumber } } struct SimpleEntry: TimelineEntry { let date: Date - var approvalItems: [ApprovalItem] - var message: String? + var entryData: EntryData } //状況確認ウィジェット @@ -53,9 +86,8 @@ struct StatusCheckWidget: Widget { let kind: String = "StatusCheck" var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: Provider()) { entry in - - StatusCheckWidgetView(entry: EntryData(approvalItems: entry.approvalItems, message: entry.message, viewController: nil)) + IntentConfiguration(kind: kind, intent: WidgetConfigurationIntent.self, provider: Provider()) { entry in + StatusCheckWidgetView(entry: entry.entryData) } .configurationDisplayName(getDisplayString(key: "StatusCheckWidgetName", comment: "")) .description(getDisplayString(key: "StatusCheckWidgetDescription", comment: "")) @@ -68,8 +100,8 @@ struct ApprovalWidget: Widget { let kind: String = "Approval" var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: Provider()) { entry in - ApprovalWidgetView(entry: EntryData(approvalItems: entry.approvalItems, message: entry.message, viewController: nil)) + IntentConfiguration(kind: kind, intent: WidgetConfigurationIntent.self, provider: Provider()) { entry in + ApprovalWidgetView(entry: entry.entryData) } .configurationDisplayName(getDisplayString(key: "ApprovalWidgetName", comment: "")) .description(getDisplayString(key: "ApprovalWidgetDescription", comment: "")) @@ -94,56 +126,3 @@ struct WidgetExtension_Previews: PreviewProvider { } } -enum widgetAPIResult { - case success(T) - case nodate(String) - case failure(String) -} - -func fetch(completion: @escaping (widgetAPIResult) -> Void) { - guard KeychainDataStore().readAccessToken() != nil else { - completion(.failure(getDisplayString(key: "NotLogedin", comment: ""))) - return - } - - let approvalsEndpoint = GetApprovalsEndpoint() - - while UserDefaultsDataStore().readUpdateWidgetFlg() ?? false { - //別のウィジェットが更新中の場合は待つ - } - UserDefaultsDataStore().writeUpdateWidgetFlg(update: true) - Session.send(approvalsEndpoint) { result in - UserDefaultsDataStore().writeUpdateWidgetFlg(update: false) - switch result { - case .success(let response): - if !response.items.isEmpty { - completion(.success(response)) - } else { - completion(.nodate(getDisplayString(key: "NoData", comment: ""))) - } - case .failure(let error): - var errorMessage = "-" - switch error { - case .connectionError(let error): - if let message = errorMessageNSError(error: error) { - errorMessage = message - } - default: - break - } - completion(.failure(errorMessage)) - } - } -} - -func errorMessageNSError(error: Error) -> String? { - switch URLError.Code(rawValue: (error as NSError).code) { - case .notConnectedToInternet: // -1009 - return getDisplayString(key: "NotConnectNetworkError", comment: "") - case .timedOut: // -1001 - return getDisplayString(key: "TimedOutNetworkError", comment: "") - default: - return nil - } -} - diff --git a/AgileWorks/WidgetExtension/WidgetExtensionRelease-Production.entitlements b/AgileWorks/WidgetExtension/WidgetExtensionRelease-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..df7a8b6d7ec98bc391c8c8954c2af1545824a741 --- /dev/null +++ b/AgileWorks/WidgetExtension/WidgetExtensionRelease-Production.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.application-groups + + group.jp.atled.agileworks + + keychain-access-groups + + $(AppIdentifierPrefix)jp.atled.agileworks0 + $(AppIdentifierPrefix)jp.atled.agileworks1 + $(AppIdentifierPrefix)jp.atled.agileworks2 + $(AppIdentifierPrefix)jp.atled.agileworks3 + $(AppIdentifierPrefix)jp.atled.agileworks4 + + +