diff --git a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj index 2c0de42b3d96a987ae1e6f44bf0015c67ae16bfc..27e433d4d6c8adddc201f6096f0d2f613cbbaee1 100644 --- a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj +++ b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -11,11 +11,11 @@ 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 */; }; 430C967B2E966FC047726763 /* Pods_All_AgileWorks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF516C40B9AA44C977664C98 /* Pods_All_AgileWorks.framework */; }; + 5B3C0583429101EF000E5B66 /* Pods_All_AgileWorks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D4EC7F2A3C2CAD0096F9D2 /* Pods_All_AgileWorks.framework */; }; 752FE4D02966B9C2004922AD /* ISO8859 in Frameworks */ = {isa = PBXBuildFile; productRef = 752FE4CF2966B9C2004922AD /* ISO8859 */; }; 7549FC8F27EAF42C00FF9E0C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3AF4A84724A06A73006C0C0A /* Localizable.strings */; }; 7549FC9027EAF42C00FF9E0C /* English.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9D27BDF1C30051E201 /* English.strings */; }; @@ -23,6 +23,7 @@ 7549FC9227EAF42C00FF9E0C /* Chinese-Simplified.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9F27BDF2050051E201 /* Chinese-Simplified.strings */; }; 7549FC9327EAF42C00FF9E0C /* Chinese-Traditional.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917FA127BDF2250051E201 /* Chinese-Traditional.strings */; }; 756C7E7B282B6A5700C24F4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 756C7E7A282B6A5700C24F4D /* Assets.xcassets */; }; + 758F1BC02A5408D400DDEBC5 /* ClientCertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */; }; 75917F9C27BDF0F10051E201 /* Japanese.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9B27BDF0F10051E201 /* Japanese.strings */; }; 75917F9E27BDF1C30051E201 /* English.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9D27BDF1C30051E201 /* English.strings */; }; 75917FA027BDF2050051E201 /* Chinese-Simplified.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9F27BDF2050051E201 /* Chinese-Simplified.strings */; }; @@ -43,6 +44,26 @@ 75917FB527C371390051E201 /* StringsUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75917FB227C371390051E201 /* StringsUtility.swift */; }; 75917FB627C372C80051E201 /* XCGLoggerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8D55D82420609000A667B0 /* XCGLoggerExtensions.swift */; }; 75917FB727C373060051E201 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAB1F6C240D0D0000EA15FD /* UserDefaultsExtensions.swift */; }; + 75BC052D29ECE87500E21941 /* ServerSwitchingBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC052C29ECE87500E21941 /* ServerSwitchingBuilder.swift */; }; + 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 */; }; + 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 */; }; @@ -164,6 +185,8 @@ C57DCAB027C8AEFC000A2ABC /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57DCAAF27C8AEFC000A2ABC /* ProfileTableViewController.swift */; }; C5848EB827BC8EBC00AA796D /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5848EB727BC8EBB00AA796D /* MenuViewController.swift */; }; C5848EBA27BCE0F100AA796D /* MenuTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5848EB927BCE0F100AA796D /* MenuTableViewController.swift */; }; + C58DE59F2B2717B40004C4E6 /* OldServerCertificateDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58DE59D2B2717B40004C4E6 /* OldServerCertificateDataStore.swift */; }; + C58DE5A02B2717B40004C4E6 /* OldServerKeychainDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58DE59E2B2717B40004C4E6 /* OldServerKeychainDataStore.swift */; }; C599740827D992A5006F5AAC /* OpenLicenseTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C599740727D992A5006F5AAC /* OpenLicenseTableViewController.swift */; }; C599740B27D992E9006F5AAC /* OpenLicenseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C599740A27D992E9006F5AAC /* OpenLicenseTableViewCell.swift */; }; C599740D27D9E634006F5AAC /* OpenLicenseDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C599740C27D9E633006F5AAC /* OpenLicenseDetailViewController.swift */; }; @@ -194,6 +217,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 */; @@ -217,6 +247,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 */, @@ -224,6 +255,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 */ @@ -237,6 +279,10 @@ 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 = ""; }; + 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCertificateViewController.swift; 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 = ""; }; @@ -244,6 +290,23 @@ 75917F9F27BDF2050051E201 /* Chinese-Simplified.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = "Chinese-Simplified.strings"; sourceTree = ""; }; 75917FA127BDF2250051E201 /* Chinese-Traditional.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = "Chinese-Traditional.strings"; sourceTree = ""; }; 75917FB227C371390051E201 /* StringsUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringsUtility.swift; sourceTree = ""; }; + 75BC052C29ECE87500E21941 /* ServerSwitchingBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSwitchingBuilder.swift; sourceTree = ""; }; + 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; }; @@ -348,6 +411,8 @@ C57DCAAF27C8AEFC000A2ABC /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = ""; }; C5848EB727BC8EBB00AA796D /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; C5848EB927BCE0F100AA796D /* MenuTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuTableViewController.swift; sourceTree = ""; }; + C58DE59D2B2717B40004C4E6 /* OldServerCertificateDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OldServerCertificateDataStore.swift; sourceTree = ""; }; + C58DE59E2B2717B40004C4E6 /* OldServerKeychainDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OldServerKeychainDataStore.swift; sourceTree = ""; }; C599740727D992A5006F5AAC /* OpenLicenseTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenLicenseTableViewController.swift; sourceTree = ""; }; C599740A27D992E9006F5AAC /* OpenLicenseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenLicenseTableViewCell.swift; sourceTree = ""; }; C599740C27D9E633006F5AAC /* OpenLicenseDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenLicenseDetailViewController.swift; sourceTree = ""; }; @@ -380,6 +445,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC602A3C00B00096F9D2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 75D4EC652A3C00B00096F9D2 /* Intents.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 75EF9CA927E9E983003178A3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -402,6 +475,7 @@ files = ( 752FE4D02966B9C2004922AD /* ISO8859 in Frameworks */, 430C967B2E966FC047726763 /* Pods_All_AgileWorks.framework in Frameworks */, + 5B3C0583429101EF000E5B66 /* Pods_All_AgileWorks.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -411,12 +485,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 = ""; @@ -443,6 +520,7 @@ 3127EE0D241A2A9500535CC7 /* TodayExtension */ = { isa = PBXGroup; children = ( + 7576916B2A2F003400EAFCBC /* TodayExtensionRelease-Production.entitlements */, BD285CAF24D2ACA300DC712E /* View */, 3127EE13241A2A9500535CC7 /* Info.plist */, BD10D30624E38C2F00A0DFDC /* TodayExtension-Production.entitlements */, @@ -481,13 +559,54 @@ path = Strings; sourceTree = ""; }; + 75BC052729ECE1FB00E21941 /* ServerSwitching */ = { + isa = PBXGroup; + children = ( + 75BC052829ECE2F500E21941 /* Builder */, + 75BC052929ECE30900E21941 /* View */, + ); + path = ServerSwitching; + sourceTree = ""; + }; + 75BC052829ECE2F500E21941 /* Builder */ = { + isa = PBXGroup; + children = ( + 75BC052C29ECE87500E21941 /* ServerSwitchingBuilder.swift */, + ); + path = Builder; + sourceTree = ""; + }; + 75BC052929ECE30900E21941 /* View */ = { + isa = PBXGroup; + children = ( + 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 = ""; @@ -567,6 +686,7 @@ children = ( VRZQMTKT1ZML0OSDGAKBK8ID /* LoginViewController.storyboard */, TXU9U01HBM9MEYSGQITWCSE9 /* LoginViewController.swift */, + 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */, ); path = View; sourceTree = ""; @@ -600,6 +720,8 @@ BD0231F2242B6E06000A95BB /* DataStore */ = { isa = PBXGroup; children = ( + C58DE59D2B2717B40004C4E6 /* OldServerCertificateDataStore.swift */, + C58DE59E2B2717B40004C4E6 /* OldServerKeychainDataStore.swift */, BD5061B3242866780014F3FA /* Configuration.swift */, BD928CCD2407500400ED04C2 /* UserDefaultsDataStore.swift */, BDEA2CD4246CDAEF00D3E15F /* KeychainDataStore.swift */, @@ -660,6 +782,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 = ""; @@ -672,6 +796,7 @@ BD5061A1242845750014F3FA /* Production-Today.xcconfig */, 764D0DF7257F589D00AB6617 /* Production-Share.xcconfig */, 75EF9CBD27E9EB6E003178A3 /* Production-Widget.xcconfig */, + 75D4EC832A3C2EE40096F9D2 /* Production-Intents.xcconfig */, ); path = Flavor; sourceTree = ""; @@ -754,6 +879,7 @@ 3127EE0D241A2A9500535CC7 /* TodayExtension */, 7672AD6A257EFF500063884A /* ShareExtension */, 75EF9CB127E9E983003178A3 /* WidgetExtension */, + 75D4EC662A3C00B00096F9D2 /* IntentsExtension */, BDA1831023F3FD7F00C9A6DD /* Products */, 833C64FFE6F6CB735DE07EB7 /* Pods */, 07A802C56A3436654CD69C43 /* Frameworks */, @@ -767,6 +893,7 @@ 3127EE0A241A2A9500535CC7 /* TodayExtension.appex */, 7672AD69257EFF500063884A /* ShareExtension.appex */, 75EF9CAC27E9E983003178A3 /* WidgetExtension.appex */, + 75D4EC632A3C00B00096F9D2 /* IntentsExtension.appex */, ); name = Products; sourceTree = ""; @@ -774,6 +901,7 @@ BDA1831123F3FD7F00C9A6DD /* AgileWorks */ = { isa = PBXGroup; children = ( + 7576916A2A2EFEEC00EAFCBC /* AgileWorksRelease-Production.entitlements */, BD5061B2242865BC0014F3FA /* Configurations */, BDA6D6A42411FFAA00FA9C33 /* Settings.bundle */, BDA1831B23F3FD8000C9A6DD /* Assets.xcassets */, @@ -790,6 +918,7 @@ C57DCAA727C8954B000A2ABC /* Profile */, 76AE52DB25D358D700AFA45A /* License */, C50CF78A27D86D760042C210 /* OpenLicense */, + 75BC052729ECE1FB00E21941 /* ServerSwitching */, BDEB7D8D23F5293800EFAF31 /* Podfile */, BDA1832823F406A600C9A6DD /* README.md */, 75917F9A27BDEC880051E201 /* Strings */, @@ -1013,6 +1142,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" */; @@ -1066,6 +1213,7 @@ 3127EE15241A2A9500535CC7 /* PBXTargetDependency */, 7672AD72257EFF500063884A /* PBXTargetDependency */, 75EF9CB827E9E984003178A3 /* PBXTargetDependency */, + 75D4EC6B2A3C00B00096F9D2 /* PBXTargetDependency */, ); name = AgileWorks; packageProductDependencies = ( @@ -1081,13 +1229,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; }; @@ -1113,7 +1264,7 @@ ); mainGroup = BDA1830623F3FD7F00C9A6DD; packageReferences = ( - 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859" */, + 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859.git" */, ); productRefGroup = BDA1831023F3FD7F00C9A6DD /* Products */; projectDirPath = ""; @@ -1123,6 +1274,7 @@ 3127EE09241A2A9500535CC7 /* TodayExtension */, 7672AD68257EFF500063884A /* ShareExtension */, 75EF9CAB27E9E983003178A3 /* WidgetExtension */, + 75D4EC622A3C00B00096F9D2 /* IntentsExtension */, ); }; /* End PBXProject section */ @@ -1142,6 +1294,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 75D4EC612A3C00B00096F9D2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 75EF9CAA27E9E983003178A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1181,6 +1340,7 @@ C599740F27D9E64E006F5AAC /* OpenLicenseDetailViewController.storyboard in Resources */, BDA1833123F41A4C00C9A6DD /* Images.xcassets in Resources */, BDA1831F23F3FD8000C9A6DD /* LaunchScreen.storyboard in Resources */, + 75BC053229ED087000E21941 /* ServerSwitchingViewController.storyboard in Resources */, C59C21FE282E318100DDF5CC /* License-ja.rtf in Resources */, BDA1831C23F3FD8000C9A6DD /* Assets.xcassets in Resources */, 76AE52D725D358A800AFA45A /* License.rtf in Resources */, @@ -1300,6 +1460,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 */, @@ -1309,6 +1470,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; @@ -1317,6 +1493,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 */, @@ -1328,6 +1505,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 */, @@ -1360,16 +1538,19 @@ files = ( 764D0DB5257F450500AB6617 /* CertificateDataStore.swift in Sources */, C5E5A4C827853BEF00668C7D /* QRCodeReader.swift in Sources */, + C58DE59F2B2717B40004C4E6 /* OldServerCertificateDataStore.swift in Sources */, BD6C1AE424A471070057756F /* PutDeviceEndpoint.swift in Sources */, BDA1831323F3FD7F00C9A6DD /* AppDelegate.swift in Sources */, BD3E90862474F50B00B449A7 /* IntExtensions.swift in Sources */, C5848EB827BC8EBC00AA796D /* MenuViewController.swift in Sources */, BDA74C5624501917000D4351 /* Log.swift in Sources */, + C58DE5A02B2717B40004C4E6 /* OldServerKeychainDataStore.swift in Sources */, 85X3ZXEG18PWU7J17GNGF3UG /* SplashPresenter.swift in Sources */, KQWCT9WOHFNQBPP7I7C3OL6Q /* SplashViewController.swift in Sources */, BDBBF838243C9EA300EEB25D /* GetApprovalsEndpoint.swift in Sources */, BD7DC0F224C0608800C3FBED /* ErrorResponse.swift in Sources */, C52221BC27FD67EA00494390 /* SubViewStack.swift in Sources */, + 75BC052F29ECE8AC00E21941 /* ServerSwitchingViewController.swift in Sources */, BD7A8007241A16B90040B418 /* DeepLinkParser.swift in Sources */, BD7A8009241A16B90040B418 /* NotificationParser.swift in Sources */, BDEE85F82408A887006A6BF7 /* MainTabBarViewController.swift in Sources */, @@ -1389,6 +1570,8 @@ BD86A6B124067EB3007B48F1 /* APIResult.swift in Sources */, BDAB1F71240D0E5C00EA15FD /* GetSessionEndpoint.swift in Sources */, BDAABA1D24A3627B0077EC69 /* DeviceResponse.swift in Sources */, + 75BC053429EDA23700E21941 /* MordalViewController.swift in Sources */, + 75BC052D29ECE87500E21941 /* ServerSwitchingBuilder.swift in Sources */, BDA1832B23F4079400C9A6DD /* RootViewController.swift in Sources */, C57DCAAE27C89823000A2ABC /* ProfileViewController.swift in Sources */, OZPVEGK5OCQVK53RHNWXAVSG /* LoginViewController.swift in Sources */, @@ -1399,8 +1582,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 */, @@ -1411,11 +1594,13 @@ UMOPI6WFDHHR8CWFUZDQE9U2 /* LoginUseCase.swift in Sources */, BD6C1AED24A480C70057756F /* DeviceService.swift in Sources */, 76AE530025D3626800AFA45A /* LicenseViewController.swift in Sources */, + 758F1BC02A5408D400DDEBC5 /* ClientCertificateViewController.swift in Sources */, BDAB1F6D240D0D0000EA15FD /* UserDefaultsExtensions.swift in Sources */, C50CF78E27D86DC90042C210 /* OpenLicenseBuilder.swift in Sources */, 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 */, @@ -1441,6 +1626,11 @@ target = 3127EE09241A2A9500535CC7 /* TodayExtension */; targetProxy = 3127EE14241A2A9500535CC7 /* PBXContainerItemProxy */; }; + 75D4EC6B2A3C00B00096F9D2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 75D4EC622A3C00B00096F9D2 /* IntentsExtension */; + targetProxy = 75D4EC6A2A3C00B00096F9D2 /* PBXContainerItemProxy */; + }; 75EF9CB827E9E984003178A3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 75EF9CAB27E9E983003178A3 /* WidgetExtension */; @@ -1496,6 +1686,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; @@ -1510,16 +1701,182 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.TodayExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.today; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "AgileWorks App TodayExtension Ad Hoc"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App TodayExtension"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; 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 IntentsExtension"; + 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 IntentsExtension"; + 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 */; @@ -1556,7 +1913,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; @@ -1588,10 +1945,10 @@ MARKETING_VERSION = 1.0.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.WidgetExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.widget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App WidgetExtension"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; @@ -1678,10 +2035,10 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.WidgetExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.widget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App WidgetExtension"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1710,10 +2067,10 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.share; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App ShareExtenstion"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1737,10 +2094,10 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.ShareExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.share; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "AgileWorks App ShareExtension Development"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App ShareExtenstion"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1911,10 +2268,10 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.TodayExtension; + PRODUCT_BUNDLE_IDENTIFIER = jp.atled.agileworks.today; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "AgileWorks App TodayExtension Development"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App Wildcard"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "AgileWorks App TodayExtension"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1984,6 +2341,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; @@ -2079,6 +2437,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 = ( @@ -2118,7 +2485,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859" */ = { + 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Cosmo/ISO8859.git"; requirement = { @@ -2131,7 +2498,7 @@ /* Begin XCSwiftPackageProductDependency section */ 752FE4CF2966B9C2004922AD /* ISO8859 */ = { isa = XCSwiftPackageProductDependency; - package = 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859" */; + package = 752FE4CE2966B9C1004922AD /* XCRemoteSwiftPackageReference "ISO8859.git" */; productName = ISO8859; }; /* End XCSwiftPackageProductDependency section */ 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..f691bfa5310b3166e5abad87bf86e233bd846951 100644 --- a/AgileWorks/AgileWorks/AgileWorks-Production.entitlements +++ b/AgileWorks/AgileWorks/AgileWorks-Production.entitlements @@ -4,9 +4,18 @@ 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 + $(AppIdentifierPrefix)jp.atled.agileworks diff --git a/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements b/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..f691bfa5310b3166e5abad87bf86e233bd846951 --- /dev/null +++ b/AgileWorks/AgileWorks/AgileWorksRelease-Production.entitlements @@ -0,0 +1,21 @@ + + + + + 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 + $(AppIdentifierPrefix)jp.atled.agileworks + + + diff --git a/AgileWorks/AgileWorks/App/AppDelegate.swift b/AgileWorks/AgileWorks/App/AppDelegate.swift index 84ff5cbe806798d0cf7d35164a944c2dcd89b32e..90cefab25d84003d8081b233df8f2e40b21618fb 100644 --- a/AgileWorks/AgileWorks/App/AppDelegate.swift +++ b/AgileWorks/AgileWorks/App/AppDelegate.swift @@ -60,7 +60,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func setupAppAuth() { - OIDURLSessionProvider.setSession(Session.shared) + OIDURLSessionProvider.setSession(Session.shared(serverNumber: nil)) + } + + 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 @@ -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..77102984917768fce52688884dcd6fe0583b9988 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().removeSystemName() + 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..fda742365c68d36fd8c9c30b11110aab48069e2f 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) - Session.send(deleteDevice) { result in + let deleteDevice = DeleteDeviceEndpoint(request: request, serverNumber: serverNumber) + Session.send(serverNumber: serverNumber, 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/ClientCertificateViewController.swift b/AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..c80a6886ae5024418e33c4a12f262b704f51d431 --- /dev/null +++ b/AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift @@ -0,0 +1,96 @@ +// +// ClientCertificateTableViewController.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/07/04. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit + +class ClientCertificateViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + @IBOutlet weak var tableView: UITableView! + //インポート済みの証明書リスト + var certificates = CertificateDataStore().readAllClientCertificate() + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + + tableView.dataSource = self + tableView.delegate = self + + tableView.allowsMultipleSelection = false + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + + cell.selectionStyle = UITableViewCell.SelectionStyle.none + + //ラベル名取得 + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + cell.textLabel?.text = label + + //選択中のラベルの場合はチェックマークをつける + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel == label { + cell.accessoryType = .checkmark + self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) + } + return cell + } + + //セルの数 + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if certificates == nil { + return 0 + } + return certificates!.count + } + + // セルが選択された時に呼び出される + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + // チェックマークを入れる + cell!.accessoryType = .checkmark + + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel != label { + //キーチェーンにラベル名を保存 + KeychainDataStore().writeCertificateLabel(label: label) + } else { + //設定済みの場合は解除 + cell!.accessoryType = .none + KeychainDataStore().removeCertificateLabel() + } + } + + // セルの選択が外れた時に呼び出される + func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + // チェックマークを外す + cell!.accessoryType = .none + } + + //クライアント証明書をスワイプ削除 + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + CertificateDataStore().removeClientCertificate(label: label) + + //選択中の証明書が削除された場合 + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel == label { + KeychainDataStore().removeCertificateLabel() + } + certificates = CertificateDataStore().readAllClientCertificate() + + tableView.deleteRows(at: [indexPath], with: .automatic) + } + } +} diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard b/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard index ae4f5c3a6f0b89c7653e25dd2d476c912775e7df..422edd7862018de1a591d20e5922ff3c3567b777 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -19,17 +19,17 @@ - + - + - + - + @@ -95,21 +95,31 @@ - + @@ -208,7 +218,7 @@ - + @@ -227,11 +237,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - @@ -245,7 +286,7 @@ - + diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift index d12e5f151fa59651dba0e01b258ea0dfdbe20874..ffb1dd3395d95f7cbc7dbad72c8945b7c813deba 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift @@ -30,11 +30,13 @@ class LoginViewController: UIViewController { @IBOutlet private var disablingView: UIView! @IBOutlet private var clientCertificateLabel: UILabel! @IBOutlet private var clientCertificateSettingView: UIView! - @IBOutlet private var clientCertificateRemovalButton: UIButton! + @IBOutlet weak var clientCertificateSelectButton: UIButton! @IBOutlet private var qrCodeReadLabel: UILabel! @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) @@ -50,6 +52,8 @@ class LoginViewController: UIViewController { return context } + private var navVC: UINavigationController? = nil + let qrCodeReader = QRCodeReader() func inject(presenter: LoginPresenter) { @@ -58,39 +62,44 @@ 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) + let certificates = CertificateDataStore().readAllClientCertificate() + clientCertificateSettingView.isHidden = certificates?.isEmpty ?? true + //固定文言表示 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 +123,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() { @@ -163,11 +178,57 @@ class LoginViewController: UIViewController { self.view.endEditing(true) } + @IBAction func clientCertificateSelectButtonTapped(_ sender: Any) { + let csVC = clientCertificateSelectView() + navVC = UINavigationController(rootViewController: csVC) + //navVC.setNavigationBarHidden(false, animated: true) + let chevronLeftImage: UIImage? = UIImage(systemName: "chevron.left") + let backButton = UIBarButtonItem(image: chevronLeftImage, style: .plain, target: self, action: #selector(onTapBackButton(_:))) + navVC!.navigationBar.topItem!.leftBarButtonItem = backButton + navVC!.modalPresentationStyle = .fullScreen + self.present(navVC!, animated: true) + } + @objc func onTapBackButton(_ sender: UIBarButtonItem) { + navVC?.dismiss(animated: true) + } + + private func clientCertificateSelectView() -> UIViewController { + let storyboard = UIStoryboard(name: "LoginViewController", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "ClientCertificateTableViewController") + return vc + } + @IBAction private func loginTapped() { guard let serverHost = serverHost else { return } guard let context = context else { return } + //サーバーの重複チェック + let serverList = UserDefaultsDataStore().readServerList() + var serverCheckFlg = false + for serverNumber in serverList { + let serverCheck = KeychainDataStore().readServerURL(serverNumber: serverNumber) + if serverCheck == serverHost { + serverCheckFlg = true + alertDialog(alertMessage :"LoginServerAlert") + break + } + } + + if serverCheckFlg { + return + } + + //サーバー切り替え + if let serverId = UserDefaultsDataStore().serchEmptyServerData() { + UserDefaultsDataStore().setGroupId(serverNumber: serverId) + } else { + //ログインできる接続先が上限数に達している場合 + alertDialog(alertMessage :"LoginServerOver") + return + } + disableLogin() + //サーバー名コンテキストパスの保存 KeychainDataStore().writeServerURL(serverURL: serverHost) KeychainDataStore().writeContextPath(contextPath: context) @@ -186,6 +247,14 @@ class LoginViewController: UIViewController { } } } + + private func alertDialog(alertMessage :String) { + let alert = UIAlertController(title: "", message: getLocalizableStrings(key: alertMessage, comment: ""), preferredStyle: .alert) + let ok = UIAlertAction(title: "OK", style: .default) + alert.addAction(ok) + present(alert, animated: true, completion: nil) + + } private func registDevice() { // デバイス登録 @@ -206,12 +275,23 @@ class LoginViewController: UIViewController { Session.send(sessionEndpoint) { result in switch result { // セッション情報取得成功 - case .success(let response): + case .success(var response): setStringsName(language: response.user.displayLanguage) KeychainDataStore().writeSessionID(sessionID: response.sessionId) + KeychainDataStore().writeSystemName(systemName: response.systemName) 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): @@ -289,7 +369,7 @@ class LoginViewController: UIViewController { private func setLoginControlesEnabled(_ enabled: Bool) { disablingView.isHidden = enabled - let controls: [UIControl] = [serverTextField, contextTextField, loginButton, clientCertificateRemovalButton, qrCodeStartupButton] + let controls: [UIControl] = [serverTextField, contextTextField, loginButton, clientCertificateSelectButton, qrCodeStartupButton] for view in controls { view.isEnabled = enabled } @@ -300,7 +380,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) } @@ -309,10 +389,11 @@ class LoginViewController: UIViewController { } } + @IBAction private func clientCertificateRemovalTapped(_ sender: Any) { let alertController = UIAlertController(title: "", message: getLocalizableStrings(key: "ClientCertificateRemovalConfirmation", comment: ""), preferredStyle: .alert) let okAction = UIAlertAction(title: getLocalizableStrings(key: "ClientCertificateRemovalOK", comment: ""), style: .default) { _ in - CertificateDataStore().removeClientCertificate() + CertificateDataStore().removeClientCertificate(label: "") self.clientCertificateSettingView.isHidden = true } let cancelAction = UIAlertAction(title: getLocalizableStrings(key: "ClientCertificateRemovalCancel", comment: ""), style: .cancel, handler: nil) @@ -320,9 +401,9 @@ class LoginViewController: UIViewController { alertController.addAction(cancelAction) 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/Main/View/MainTabBarViewController.swift b/AgileWorks/AgileWorks/Main/View/MainTabBarViewController.swift index a9a9227077474bf806c17fcccbdcac4b2ded5c5c..7d8547138066985a4a9003ddb8f743a94f5c91bb 100644 --- a/AgileWorks/AgileWorks/Main/View/MainTabBarViewController.swift +++ b/AgileWorks/AgileWorks/Main/View/MainTabBarViewController.swift @@ -8,7 +8,7 @@ import UIKit -class MainTabBarViewController: UITabBarController { +class MainTabBarViewController: UITabBarController , UIAdaptivePresentationControllerDelegate { private var wireframe: MainTabBarViewWireframe! var screenPath: String? { @@ -103,7 +103,19 @@ class MainTabBarViewController: UITabBarController { } } } + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + wireframe.reloadWebview() + } +} + +/* +//通知モーダルが閉じられたのを検知 +extension MainTabBarViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + wireframe.reloadWebview() + } } + */ extension MainTabBarViewController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { @@ -135,8 +147,16 @@ extension MainTabBarViewController: UITabBarControllerDelegate { } } webView.loadViewIfNeeded() - webView.reloadWebView(sameTab: selectedTabIndex == tabBarController.selectedIndex) - selectedTabIndex = tabBarController.selectedIndex + + let serverList = UserDefaultsDataStore().readServerList() + let serverNumber = serverList.first + if webView.displayedServerNumber != serverNumber { + webView.loadURL = wireframe.createLoadUrl(urlPath: ViewURL.home.rawValue) + webView.remakeWebView(url: webView.loadURL) + } else { + webView.reloadWebView(sameTab: selectedTabIndex == tabBarController.selectedIndex) + selectedTabIndex = tabBarController.selectedIndex + } } } } diff --git a/AgileWorks/AgileWorks/Main/Wireframe/MainTabBarViewWireframe.swift b/AgileWorks/AgileWorks/Main/Wireframe/MainTabBarViewWireframe.swift index 1ee5b97f02d211ff1fe26fb0276de345419568e8..b20026bdfae2624bb46389d323cdb59f4063f1cb 100644 --- a/AgileWorks/AgileWorks/Main/Wireframe/MainTabBarViewWireframe.swift +++ b/AgileWorks/AgileWorks/Main/Wireframe/MainTabBarViewWireframe.swift @@ -11,12 +11,14 @@ import UIKit protocol MainTabBarViewWireframe: AnyObject { func showApprovalDetail(url: String) func showDocForm(notificationData: NotificationData) + func reloadWebview() + func createLoadUrl(urlPath: String) -> String } class MainTabBarViewWireframeImpl { - weak var viewController: UITabBarController! + weak var viewController: MainTabBarViewController! - required init(viewController: UITabBarController) { + required init(viewController: MainTabBarViewController) { self.viewController = viewController } } @@ -32,8 +34,30 @@ extension MainTabBarViewWireframeImpl: MainTabBarViewWireframe { } return false } + + func openViewController(tabNumber: Int) -> WebViewController { + // モーダルでViewが表示されているか。 + if let presentNavi = self.viewController.presentedViewController as? UINavigationController { + // 表示されている場合、閉じる + presentNavi.dismiss(animated: true, completion: nil) + } + + viewController.selectedIndex = tabNumber + + let view = viewController.children[tabNumber] as! UINavigationController + let VC = view.viewControllers[view.viewControllers.count - 1] as! WebViewController + return VC + } + //ウィジェットからの起動 func showApprovalDetail(url: String) { + //接続先モーダルが表示されていたら閉じる + if let modal = self.viewController.presentedViewController as? ServerSwitchingViewController { + if modal != nil { + modal.dismiss(animated: true, completion: nil) + } + } + //アプリ起動のみ if url.contains(urlSchemeName) { return @@ -42,23 +66,15 @@ extension MainTabBarViewWireframeImpl: MainTabBarViewWireframe { if alertCheck() { return } - // モーダルでViewが表示されているか。 - if let presentNavi = self.viewController.presentedViewController as? UINavigationController { - // 表示されている場合、閉じる - presentNavi.dismiss(animated: true, completion: nil) - } - let workNum = 1 - viewController.selectedIndex = workNum - - let view = viewController.children[workNum] as! UINavigationController - let workVC = view.viewControllers[view.viewControllers.count - 1] as! WebViewController + let workTabNumber = 1 + let workVC = openViewController(tabNumber: workTabNumber) //ウィジェットから起動する workVC.openByWidget = true //viewの読み込み workVC.loadViewIfNeeded() - workVC.widgetLoadWebView(url: url) + workVC.remakeWebView(url: url) } // 通知から書類表示 func showDocForm(notificationData: NotificationData) { @@ -66,13 +82,27 @@ extension MainTabBarViewWireframeImpl: MainTabBarViewWireframe { if alertCheck() { return } + + if notificationData.url.isEmpty { + return + } + let modalWebVC = WebViewBuilderImpl().buildVC() // URL , タイトル設定 modalWebVC.loadURL = notificationData.url modalWebVC.navigationItem.title = notificationData.title modalWebVC.showNotification = true + modalWebVC.displayedServerNumber = getDocumentServerNumber(url: URL(string: notificationData.url)) let nav = UINavigationController(rootViewController: modalWebVC) nav.modalPresentationStyle = .fullScreen + + let homeTabNumber = 0 + viewController.selectedIndex = homeTabNumber + + let view = viewController.children[homeTabNumber] as! UINavigationController + let homeVC = view.viewControllers[view.viewControllers.count - 1] as! WebViewController + modalWebVC.presentationController?.delegate = homeVC + // 既にモーダルでViewが表示されているか。 if let presentNavi = self.viewController.presentedViewController as? UINavigationController { // されている場合、開き直しの確認 @@ -83,6 +113,22 @@ extension MainTabBarViewWireframeImpl: MainTabBarViewWireframe { } } + func getDocumentServerNumber(url: URL?) -> Int? { + guard let _ = url else { + return nil + } + let serverList = UserDefaultsDataStore().readServerList() + for serverNumber in serverList { + UserDefaultsDataStore().setGroupId(serverNumber: serverNumber) + let serverURL = KeychainDataStore().readServerURL() + if serverURL == url?.host { + return serverNumber + } + } + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + return nil + } + // 書類上書き確認アラート表示 func showOverrideAlter(oldNavi: UINavigationController, newNavi: UINavigationController) { // 開き直し確認 @@ -100,4 +146,20 @@ extension MainTabBarViewWireframeImpl: MainTabBarViewWireframe { // アラート表示 AppDelegate.shared.rootViewController.showAlertScreen(view: oldNavi, title: "", message: message, defaultAction: defaultAction, cancelAction: cancelAction) } + + func reloadWebview() { + let homeNumber = 0 + let homeVC = openViewController(tabNumber: homeNumber) + + //viewの読み込み + homeVC.loadViewIfNeeded() + homeVC.loadURL = createLoadUrl(urlPath: ViewURL.home.rawValue) + homeVC.remakeWebView(url: homeVC.loadURL) + } + + // WebView でロードするURL生成 + func createLoadUrl(urlPath: String) -> String { + return Configuration.shared.awURL + "/" + Configuration.shared.awContextPath + urlPath + } + } diff --git a/AgileWorks/AgileWorks/ServerSwitching/Builder/ServerSwitchingBuilder.swift b/AgileWorks/AgileWorks/ServerSwitching/Builder/ServerSwitchingBuilder.swift new file mode 100644 index 0000000000000000000000000000000000000000..c40d8c974be303c23be3b03f0d2c95e7e597b500 --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/Builder/ServerSwitchingBuilder.swift @@ -0,0 +1,32 @@ +// +// ServerSwitchingBuilder.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/04/17. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit + +protocol ServerSwitchingBuilder { + func build() -> UIViewController +} + +struct ServerSwitchingBuilderImpl: ServerSwitchingBuilder { + let identifier = "ServerSwitchingViewController" + + func build() -> UIViewController { + let viewController = storyboard() + + return viewController + } +} + +extension ServerSwitchingBuilderImpl { + private func storyboard() -> ServerSwitchingViewController { + let storyboard = UIStoryboard(name: identifier, bundle: Bundle.main) + let viewController = storyboard.instantiateViewController(withIdentifier: identifier) as! ServerSwitchingViewController + return viewController + } +} + diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift b/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..0ca1c7cf8938c23b4a9a9203ebb1d378a1479175 --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/View/MordalViewController.swift @@ -0,0 +1,31 @@ +// +// MordalViewController.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/04/18. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit +import FloatingPanel + +class MordalViewController: UIViewController { + @IBOutlet private var accessPoint: UILabel! + @IBOutlet private var addServer: UIButton! + + override func viewDidLoad() { + super.viewDidLoad() + accessPoint.text = getDisplayString(key: "accessPoint", comment: "") + addServer.setTitle(getDisplayString(key: "AddConnection", comment: ""), for: .normal) + } + + 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..6520864444dd407f8fbdf8704dcd3a466e81b1b0 --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingTableViewController.swift @@ -0,0 +1,114 @@ +// +// 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 systemName = cell.viewWithTag(1) as! UILabel + let serverNumber = serverList[indexPath.row] + systemName.text = KeychainDataStore().readSystemName(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 + switch result { + case .success(let response): + let requestApproval = "REQUEST_APPROVAL" + for item in response.items where item.code == requestApproval { + let approvalCountLabel = String(item.count) + DispatchQueue.main.async{ + label.text = approvalCountLabel + } + } + 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 new file mode 100644 index 0000000000000000000000000000000000000000..652e871aad5798aa9cf31fd09f3e65d6fbafd9fe --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/View/ServerSwitchingViewController.swift @@ -0,0 +1,106 @@ +// +// ServerSwitchingViewController.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/04/17. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit +import FloatingPanel + +class ServerSwitchingViewController: UIViewController, UIGestureRecognizerDelegate { + let fpc = FloatingPanelController() + + override func viewDidLoad() { + super.viewDidLoad() + + //モーダル外の背景色 + self.view.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3) + + //タップ検知 + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped(_:))) + tapGesture.delegate = self + self.view.addGestureRecognizer(tapGesture) + + //モーダルの設定 + fpc.delegate = self + //角丸 + fpc.surfaceView.cornerRadius = 24.0 + //下スワイプで閉じる + fpc.isRemovalInteractionEnabled = true + let modalVC = MordalViewController.fromStoryboard() + self.view.addSubview(fpc.view) + fpc.set(contentViewController: modalVC) + fpc.addPanel(toParent: self) + } + + func closeView() { + self.dismiss(animated: true, completion: nil) + } + + //モーダル外タップで閉じる + @objc func tapped(_ sender: UITapGestureRecognizer) { + closeView() + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if touch.view == self.view { + return true + } + return false + } +} + +extension ServerSwitchingViewController: FloatingPanelControllerDelegate { + // カスタマイズしたレイアウトに変更 + func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? { + return CustomFloatingPanelLayout() + } + //モーダルがスワイプ削除される + func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint){ + closeView() + } +} + +class CustomFloatingPanelLayout: FloatingPanelLayout { + // 初期位置 + var initialPosition: FloatingPanelPosition { + return .half + } + + // カスタマイズした高さ + func insetFor(position: FloatingPanelPosition) -> CGFloat? { + switch position { + case .full: return 16.0 + case .half: return 350.0 + case .tip: return 44.0 + default: return nil + } + } + + func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint]{ + var screenHeight = UIScreen.main.bounds.height + var screenWidth = UIScreen.main.bounds.width + var mordalWidth = screenWidth + //iPad + if UIDevice.current.userInterfaceIdiom == .pad { + mordalWidth = 500 + } else { //iPhone + //端末が横向き + if screenWidth > screenHeight { + mordalWidth = screenHeight + } + } + + return [ + surfaceView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + surfaceView.widthAnchor.constraint(equalToConstant: mordalWidth), + ] + } + + // サポートするモーダルサイズ + var supportedPositions: Set { + return [.half] + } +} diff --git a/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard b/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard new file mode 100644 index 0000000000000000000000000000000000000000..6cdbd825352059f406623a8f1618b81f3ae03149 --- /dev/null +++ b/AgileWorks/AgileWorks/ServerSwitching/View/serverSwitchingViewController.storyboard @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AgileWorks/AgileWorks/Splash/View/SplashViewController.swift b/AgileWorks/AgileWorks/Splash/View/SplashViewController.swift index fea006443507ba4509c1ad24e131ae3f0f3c3e6d..de7daeaa90ec3a6f5bfaa234b214fe7256e2914b 100644 --- a/AgileWorks/AgileWorks/Splash/View/SplashViewController.swift +++ b/AgileWorks/AgileWorks/Splash/View/SplashViewController.swift @@ -47,6 +47,14 @@ class SplashViewController: UIViewController { private func makeServiceCall() { activityIndicator.startAnimating() + // サーバ情報引き継ぎ + updateServerSettings() + + let serverList = UserDefaultsDataStore().readServerList() + if !serverList.isEmpty { + UserDefaultsDataStore().setGroupId(serverNumber: serverList.first!) + } + guard KeychainDataStore().readOAuthState() != nil else { self.switchToLogoutOrLicenseView() return @@ -99,6 +107,46 @@ class SplashViewController: UIViewController { } } } + + // サーバ切り替え対応前アプリのサーバ情報引き継ぎ + private func updateServerSettings() { + // 旧バージョン時のログイン情報を引き継ぐ + guard OldServerKeychainDataStore().readOAuthState() != nil else { + return + } + // 旧Keychainに保存されてる内容を、0番目のKeychainにコピーして、旧Keychain情報をクリアする + let keychain = KeychainDataStore() + let oldKeychain = OldServerKeychainDataStore() + keychain.writeAccessToken(accessToken: oldKeychain.readAccessToken()!) + keychain.writeOAuthState(authState: oldKeychain.readOAuthState()!) + keychain.writeLanguage(language: oldKeychain.readLanguage()!) + keychain.writeServerURL(serverURL: oldKeychain.readServerURL()!) + keychain.writeContextPath(contextPath: oldKeychain.readContextPath()!) + keychain.writeDeviceID(deviceID: oldKeychain.readDeviceID()!) + keychain.writeSessionID(sessionID: oldKeychain.readSessionID()!) + // 旧情報クリア + oldKeychain.removeAccessToken() + oldKeychain.removeOAuthState() + oldKeychain.removeLanguage() + oldKeychain.removeServerURL() + oldKeychain.removeContextPath() + oldKeychain.removeDeviceID() + oldKeychain.removeSessionID() + // サーバリストの追加 + UserDefaultsDataStore().addServerList() + // ライセンス情報は同意済みのため、現バージョンをセット + writeAcceptedLicenseVersionToCurrentVersion() + // クライアント証明書の引き継ぎ + guard OldServerCertificateDataStore().readClientCertificate() != nil else { + return + } + let label = CertificateDataStore().writeNewClientCertificate(identity: OldServerCertificateDataStore().readClientCertificate()!) + // 旧バージョンでクライアント証明書が設定されている=クライアント証明書を使用しての通信を実施しているので + // その情報も引き継ぐ + KeychainDataStore().writeCertificateLabel(label: "ClientCert" + label!) + // 証明書情報削除 + OldServerCertificateDataStore().removeClientCertificate() + } } extension SplashViewController: SplashView { diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings index f39da1e1cf100c442fd80a7d1f44a3a624727e5b..4436813b3988ede0e3bd6e4039350f752aa9587e 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登录失败。 请重新登录。"; "CameraStartupErrorMessage" = "未能激活摄像机。"; +"LoginServerAlert" = "您已登录连接。 请注销后重试。"; +"LoginServerOver" = "已超过您可以登录的最大连接数。\n请退出并重试。"; // WebView "HomeTitle" = "家"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "开放许可证"; +//server +"accessPoint" = "接入点"; +"AddConnection" = "+添加连接点"; +"ConnectionName" = "连接名称"; + // Notification "DocOverrideConfirm" = "由通知点选的文件已经显示了。\n你想放弃当前的编辑并显示一个新的文件吗?"; +"DocDifferentServer" = "您目前使用的是其他连接登录。\n您想切换到正在查看的文档的连接点吗?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "访问令牌更新错误"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "输入的客户证书。"; "IncorrectPassphrase" = "通行证短语是不同的。"; "CertificateImportError" = "在证书导入过程中发生了一个错误。"; +"CertificateStorageLimit" = "超过证书存储限制。\n请从登录界面删除。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings index de02fa7380691b602ca77b5f0023f23fb9816ea3..4ea4cb4b55e1c9dd109f48bbb1929e6d8060ed40 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登錄失敗。請重新登錄。"; "CameraStartupErrorMessage" = "未能激活攝像機。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; +"LoginServerOver" = "ログインできる接続先の上限数を超えました。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "開放許可證"; +//server +"accessPoint" = "接続先"; +"AddConnection" = "+接続先を追加"; +"ConnectionName" = "接続先名"; + // Notification "DocOverrideConfirm" = "由通知點選的文件已經顯示了。 \n你想放棄當前的編輯並顯示一個新的文件嗎?"; +"DocDifferentServer" = "現在別の接続先でログイン中です。\n表示中の書類の接続先に切り替えますか?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "訪問令牌更新錯誤"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "輸入的客戶證書。"; "IncorrectPassphrase" = "通行證短語是不同的。"; "CertificateImportError" = "在證書導入過程中發生了一個錯誤。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/English.strings b/AgileWorks/AgileWorks/Strings/English.strings index e4bc950a994a503421cdecde94afe88a78e77136..fe0c4399d3d87cf6c614e0ed30c59146ed8d4bd3 100644 --- a/AgileWorks/AgileWorks/Strings/English.strings +++ b/AgileWorks/AgileWorks/Strings/English.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "cancel"; "LoginErrorMessage" = "Login failed. Please login again."; "CameraStartupErrorMessage" = "Camera failed to start."; +"LoginServerAlert" = "You are already logged in to the connection.\nPlease log out and try again."; +"LoginServerOver" = "The maximum number of connections you can log in to has been exceeded.\nPlease log out and try again."; // WebView "HomeTitle" = "HOME"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "Open license"; +//server +"accessPoint" = "Access point"; +"AddConnection" = "+Add a connection"; +"ConnectionName" = "Connection name"; + // Notification "DocOverrideConfirm" = "A document by a notification tap is already displayed.\nDo you want to discard the current edits and display a new document?"; +"DocDifferentServer" = "You are currently logged in with a different connection.\nWould you like to switch to the connection of the document you are viewing?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "Access token update error"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "Client certificate imported."; "IncorrectPassphrase" = "The passphrase is different."; "CertificateImportError" = "An error occurred during certificate import."; +"CertificateStorageLimit" = "Certificate storage limit has been exceeded.\nPlease delete it from the login screen."; // Widget Status "Draft" = "Draft"; diff --git a/AgileWorks/AgileWorks/Strings/Japanese.strings b/AgileWorks/AgileWorks/Strings/Japanese.strings index a4fb0de5af508a02d66a75b1b3b94c722c3abe63..dc2a8409f3811b99cb7a37112dfae2f66a5cad6d 100644 --- a/AgileWorks/AgileWorks/Strings/Japanese.strings +++ b/AgileWorks/AgileWorks/Strings/Japanese.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "キャンセル"; "LoginErrorMessage" = "ログインに失敗しました。再度ログインしてください。"; "CameraStartupErrorMessage" = "カメラの起動に失敗しました。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; +"LoginServerOver" = "ログインできる接続先の上限数を超えました。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "オープンソースライセンス"; +//server +"accessPoint" = "接続先"; +"AddConnection" = "+接続先を追加"; +"ConnectionName" = "接続先名"; + // Notification "DocOverrideConfirm" = "既に通知タップによる書類が表示されています。\n現在の編集内容を破棄し、新たな書類を表示しますか?"; +"DocDifferentServer" = "現在別の接続先でログイン中です。\n表示中の書類の接続先に切り替えますか?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "アクセストークン更新エラー"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "クライアント証明書をインポートしました。"; "IncorrectPassphrase" = "パスフレーズが違います。"; "CertificateImportError" = "証明書のインポート中にエラーが発生しました。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "下書き"; diff --git a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings index e01736386372f869e0fb071ca5ddeb2f1166e749..40821af2c7ef593fb88f47c36cd4251fd34e128e 100644 --- a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "cancel"; "LoginErrorMessage" = "Login failed. Please login again."; "CameraStartupErrorMessage" = "Camera failed to start."; +"LoginServerAlert" = "You are already logged in to the connection.\nPlease log out and try again."; +"LoginServerOver" = "The maximum number of connections you can log in to has been exceeded.\nPlease log out and try again."; // WebView "HomeTitle" = "HOME"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "Open license"; +//server +"accessPoint" = "Access point"; +"AddConnection" = "+Add a connection"; +"ConnectionName" = "Connection name"; + // Notification "DocOverrideConfirm" = "A document by a notification tap is already displayed.\nDo you want to discard the current edits and display a new document?"; +"DocDifferentServer" = "You are currently logged in with a different connection.\nWould you like to switch to the connection of the document you are viewing?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "Access token update error"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "Client certificate imported."; "IncorrectPassphrase" = "The passphrase is different."; "CertificateImportError" = "An error occurred during certificate import."; +"CertificateStorageLimit" = "Certificate storage limit has been exceeded.\nPlease delete it from the login screen."; // Widget Status "Draft" = "Draft"; diff --git a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings index 31e6319a3148ba35ab0d4e742984994f19843b6f..155bbcc02f30e64b4c96b3d0007662598d505076 100644 --- a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "キャンセル"; "LoginErrorMessage" = "ログインに失敗しました。再度ログインしてください。"; "CameraStartupErrorMessage" = "カメラの起動に失敗しました。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; +"LoginServerOver" = "ログインできる接続先の上限数を超えました。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "HOME"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "オープンソースライセンス"; +//server +"accessPoint" = "接続先"; +"AddConnection" = "+接続先を追加"; +"ConnectionName" = "接続先名"; + // Notification "DocOverrideConfirm" = "既に通知タップによる書類が表示されています。\n現在の編集内容を破棄し、新たな書類を表示しますか?"; +"DocDifferentServer" = "現在別の接続先でログイン中です。\n表示中の書類の接続先に切り替えますか?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "アクセストークン更新エラー"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "クライアント証明書をインポートしました。"; "IncorrectPassphrase" = "パスフレーズが違います。"; "CertificateImportError" = "証明書のインポート中にエラーが発生しました。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "下書き"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings index 6dc7b16d9397e0b3d69d30a8164c3c48bf11f59f..2867ee6e688932f830e627e031602b2dc3383cdd 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登录失败。 请重新登录。"; "CameraStartupErrorMessage" = "未能激活摄像机。"; +"LoginServerAlert" = "您已登录连接。 请注销后重试。"; +"LoginServerOver" = "已超过您可以登录的最大连接数。\n请退出并重试。"; // WebView "HomeTitle" = "家"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "开放许可证"; +//server +"accessPoint" = "接入点"; +"AddConnection" = "+添加连接点"; +"ConnectionName" = "连接名称"; + // Notification "DocOverrideConfirm" = "由通知点选的文件已经显示了。\n你想放弃当前的编辑并显示一个新的文件吗?"; +"DocDifferentServer" = "您目前使用的是其他连接登录。\n您想切换到正在查看的文档的连接点吗?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "访问令牌更新错误"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "输入的客户证书。"; "IncorrectPassphrase" = "通行证短语是不同的。"; "CertificateImportError" = "在证书导入过程中发生了一个错误。"; +"CertificateStorageLimit" = "超过证书存储限制。\n请从登录界面删除。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings index 6e180c6ebe7a7493c6f4499408d9c571eeb32d77..6679c80cd24559321f615886ede9fb33149b8c01 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings @@ -24,6 +24,8 @@ "ClientCertificateRemovalCancel" = "取消"; "LoginErrorMessage" = "登錄失敗。請重新登錄。"; "CameraStartupErrorMessage" = "未能激活攝像機。"; +"LoginServerAlert" = "ログイン済のサーバーです。ログアウトを行ってから再度お試しください。"; +"LoginServerOver" = "ログインできる接続先の上限数を超えました。ログアウトを行ってから再度お試しください。"; // WebView "HomeTitle" = "家"; @@ -56,8 +58,14 @@ // Open License "OpenLicenseViewTitle" = "開放許可證"; +//server +"accessPoint" = "接続先"; +"AddConnection" = "+接続先を追加"; +"ConnectionName" = "接続先名"; + // Notification "DocOverrideConfirm" = "由通知點選的文件已經顯示了。 \n你想放棄當前的編輯並顯示一個新的文件嗎?"; +"DocDifferentServer" = "現在別の接続先でログイン中です。\n表示中の書類の接続先に切り替えますか?"; // Refresh Token Error "RefreshTokenUpdateErrorTitle" = "訪問令牌更新錯誤"; @@ -99,6 +107,7 @@ "CertificateImportSuccess" = "輸入的客戶證書。"; "IncorrectPassphrase" = "通行證短語是不同的。"; "CertificateImportError" = "在證書導入過程中發生了一個錯誤。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift b/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift index 77850b6377c6da78d1e02a382f6469346eeaa2ed..f6f1f22d3c5b0827553aac8f87e7111727e598b8 100644 --- a/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/MenuTableViewController.swift @@ -16,8 +16,10 @@ class MenuTableViewController: UITableViewController { @IBOutlet private var licenseCell: UITableViewCell! @IBOutlet private var licenseLabel: UILabel! @IBOutlet private var openLicenseCell: UITableViewCell! - @IBOutlet private var openLicenseLable: UILabel! - + @IBOutlet private var openLicenseLabel: UILabel! + @IBOutlet private var serverCell: UITableViewCell! + @IBOutlet private var serverLabel: UILabel! + override func viewDidLoad() { super.viewDidLoad() self.tableView.separatorColor = UIColor(named: getDisplayString(key: "SeparatorColor", comment: "")) @@ -40,6 +42,8 @@ class MenuTableViewController: UITableViewController { self.pushToLicense() } else if let staticIndexPath = tableView.indexPath(for: self.openLicenseCell), staticIndexPath == indexPath { self.pushToOpenLicense() + } else if let staticIndecPath = tableView.indexPath(for: self.serverCell), staticIndecPath == indexPath { + self.pushToServer() } // メニュークローズ if let parent = self.parent as? MenuViewController { @@ -53,7 +57,8 @@ class MenuTableViewController: UITableViewController { self.profileLabel.text = getDisplayString(key: "ProfileTitle", comment: "") self.logoutLabel.text = getDisplayString(key: "LogoutTitle", comment: "") self.licenseLabel.text = getDisplayString(key: "LicenseTitle", comment: "") - self.openLicenseLable.text = getDisplayString(key: "OpenLicenseTitle", comment: "") + self.openLicenseLabel.text = getDisplayString(key: "OpenLicenseTitle", comment: "") + self.serverLabel.text = getDisplayString(key: "accessPoint", comment: "") } // プロファイルセルタップ処理 private func pushToProfile() { @@ -74,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) @@ -107,4 +112,17 @@ class MenuTableViewController: UITableViewController { self.tableView.deselectRow(at: indexPath, animated: true) } } + + //接続先サーバーセルタップ処理 + private func pushToServer() { + let serverSwitching = ServerSwitchingBuilderImpl().build() + serverSwitching.modalPresentationStyle = .overFullScreen + serverSwitching.modalTransitionStyle = .crossDissolve + self.present(serverSwitching, animated: false, completion: nil) + + // セルの選択解除 + if let indexPath = self.tableView.indexPath(for: self.serverCell) { + self.tableView.deselectRow(at: indexPath, animated: true) + } + } } diff --git a/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift b/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift index ba9747577d62cef25e01b70c2020562c64fc0394..1700e93c630508f1a5203b21e0d8254198cc6da4 100644 --- a/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/MenuViewController.swift @@ -11,7 +11,7 @@ import UIKit class MenuViewController: UIViewController { @IBOutlet private var menuView: UIView! @IBOutlet private var nameLabel: UILabel! - + @IBOutlet private var serverLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() @@ -61,15 +61,19 @@ class MenuViewController: UIViewController { // 各種セットアップ private func setup() { self.nameLabel.text = "" + self.serverLabel.text = "" // セッション情報取得 loadSessionInfo { result in switch result { - case .success(let response): + case .success(var response): let userName = response.user.name let sessionId = response.sessionId + let systemName = response.systemName DispatchQueue.main.async { self.nameLabel.text = userName KeychainDataStore().writeSessionID(sessionID: sessionId) + KeychainDataStore().writeSystemName(systemName: systemName) + self.serverLabel.text = KeychainDataStore().readSystemName() } case .failure: DispatchQueue.main.async { diff --git a/AgileWorks/AgileWorks/WebView/View/WebViewController.storyboard b/AgileWorks/AgileWorks/WebView/View/WebViewController.storyboard index 19d9eca851f2e4f6f01c77bb8f1c1bc7dcf3a9be..897f2d648b252cc8cbd3040641fd53084b4d18e6 100644 --- a/AgileWorks/AgileWorks/WebView/View/WebViewController.storyboard +++ b/AgileWorks/AgileWorks/WebView/View/WebViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -20,15 +20,15 @@ - + @@ -298,7 +332,7 @@ - + @@ -313,6 +347,7 @@ + @@ -332,7 +367,7 @@ - + diff --git a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift index 4a17a825a818fecf4c7fa03cae9aaff226c4b794..edee03256efde2a90d0b9553e141194d43101d2d 100644 --- a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift @@ -38,6 +38,8 @@ class WebViewController: UIViewController { var tabTitleKey: String! // 現在表示中のURL var nowURL: String! + //表示中のサーバー + var displayedServerNumber: Int? = nil // 描画完了フラグ var drawingComp = false @@ -106,6 +108,11 @@ class WebViewController: UIViewController { // WebView セットアップ private func setWebView() { + //表示中のサーバー番号 + if displayedServerNumber == nil { + let serverList = UserDefaultsDataStore().readServerList() + displayedServerNumber = serverList.first + } let userContentController = WKUserContentController() let script = "document.cookie='JSESSIONID=\(KeychainDataStore().readSessionID() ?? "")'" let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false) @@ -176,7 +183,7 @@ class WebViewController: UIViewController { } // WidgetからWebView ロード処理 - func widgetLoadWebView(url: String) { + func remakeWebView(url: String) { //webviewの生成 self.setWebView() loadWebView(url: url) @@ -217,8 +224,9 @@ class WebViewController: UIViewController { Session.send(sessionEndpoint) { result in switch result { // セッション情報取得成功 - case .success(let response): + case .success(var response): KeychainDataStore().writeSessionID(sessionID: response.sessionId) + KeychainDataStore().writeSystemName(systemName: response.systemName) setStringsName(language: response.user.displayLanguage) if isInit { // 画面ロード @@ -262,7 +270,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) } // その他エラーの場合 @@ -360,11 +368,37 @@ class WebViewController: UIViewController { @IBAction private func closeTapped() { if subViewStack.pop() == nil { if self.showNotification { - self.dismiss(animated: true, completion: nil) + //ログイン中のサーバーと表示中の書類サーバーの差異チェック + let serverList = UserDefaultsDataStore().readServerList() + let firstServerNumver = serverList.first + if displayedServerNumber != firstServerNumver { + showServerSwichAlert() + } else { + self.dismiss(animated: true, completion: nil) + } } } } + //サーバー切り替え確認アラート + func showServerSwichAlert() { + let message = getDisplayString(key: "DocDifferentServer", comment: "") + // OK アクション設定 + let defaultAction = UIAlertAction(title: getDisplayString(key: "YES", comment: ""), style: .default) { _ in + UserDefaultsDataStore().changeServerList(firstServer: self.displayedServerNumber!) + self.dismiss(animated: true, completion: nil) + } + // キャンセルアクション設定 + let cancelAction = UIAlertAction(title: getDisplayString(key: "NO", comment: ""), style: .cancel) { _ in + let serverList = UserDefaultsDataStore().readServerList() + let firstServerNumver = serverList.first + UserDefaultsDataStore().setGroupId(serverNumber: firstServerNumver!) + self.dismiss(animated: true, completion: nil) + } + // アラート表示 + AppDelegate.shared.rootViewController.showAlertScreen(view:self, title: "", message: message, defaultAction: defaultAction, cancelAction: cancelAction) + } + // 接続確認ボタンタップ処理 @IBAction private func checkConnectTapped() { // 2重タップ防止 @@ -601,7 +635,7 @@ extension WebViewController: WKNavigationDelegate { private func download(url: URL, saveAs fileName: String) { var request = downloadRequest(url: url) var tempPath: URL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) - Session.shared.dataTask(with: request) { data, res, err in + Session.shared(serverNumber: nil).dataTask(with: request) { data, res, err in guard let data = data, err == nil else { log.e(err) return @@ -714,3 +748,17 @@ extension WebViewController: UIScrollViewDelegate { scrollView.pinchGestureRecognizer?.isEnabled = false } } + +extension WebViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + AppDelegate.shared.rootViewController.switchToMainScreen() + } + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + super.dismiss(animated: flag, completion: completion) + guard let presentationController = presentationController else { + return + } + presentationController.delegate?.presentationControllerDidDismiss?(presentationController) + } +} diff --git a/AgileWorks/Common/DataStore/CertificateDataStore.swift b/AgileWorks/Common/DataStore/CertificateDataStore.swift index 45f08c40f30a16b7ce3db51ac18a628564b77e6f..19f4f3bd4239999331668868a43da49a8f4f71b7 100644 --- a/AgileWorks/Common/DataStore/CertificateDataStore.swift +++ b/AgileWorks/Common/DataStore/CertificateDataStore.swift @@ -17,27 +17,40 @@ let kClientCertLabel = "ClientCert" /// その実装に用いている `Keychain` は `kSecIdentity` の読み書きに対応していないため別途定義。 class CertificateDataStore { /// クライアント証明書を読み込む。取得できなければ `nil` 。 - func readClientCertificate() -> SecIdentity? { - return readIdentity(label: kClientCertLabel) + func readClientCertificate(label: String?) -> SecIdentity? { + guard let label else { + return nil + } + return readIdentity(label: label) + } + func readAllClientCertificate() -> [NSDictionary]? { + return readAllIdentity() } /// クライアント証明書を利用した `URLCredential` (セッション内で有効) を取得する。取得できなければ `nil`。 - func getClientCertificateSessionCredential() -> URLCredential? { - if let identity = readClientCertificate() { + func getClientCertificateSessionCredential(label: String?) -> URLCredential? { + if let identity = readClientCertificate(label: label) { return sessionCredential(identity: identity) } else { return nil } } - /// クライアント証明書を書き込む。 - func writeClientCertificate(identity: SecIdentity) { - writeIdentity(label: kClientCertLabel, identity: identity) + //証明書の書き込み + func writeNewClientCertificate(identity: SecIdentity) -> String? { + let labelID = serchLabelID() + if let labelID = labelID { + var newLabel = kClientCertLabel + labelID + writeIdentity(label: newLabel, identity: identity) + } else { + log.error("Certificate storage limit exceeded") + } + return labelID } /// クライアント証明書を削除する。 - func removeClientCertificate() { - removeIdentity(label: kClientCertLabel) + func removeClientCertificate(label: String) { + removeIdentity(label: label) } private func readIdentity(label: String) -> SecIdentity? { @@ -69,6 +82,36 @@ class CertificateDataStore { } } + //証明書リスト呼び出し + private func readAllIdentity() -> [NSDictionary]? { + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + //kSecAttrLabel as String: label, + kSecReturnRef as String: true, + kSecReturnPersistentRef as String: true, + kSecMatchLimit as String: kSecMatchLimitAll + ] + var result: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &result) + switch status { + case errSecSuccess: + if let result = result { + let array = (result as? NSArray) as? [NSDictionary] + return array + } else { + log.error("unexpected nil result") + return nil + } + + case errSecItemNotFound: + return nil + + default: + log.error("keychain reading error: OSStatus = \(status)") + return nil + } + } + private func writeIdentity(label: String, identity: SecIdentity) { let query: [String: Any] = [ kSecValueRef as String: identity, @@ -80,6 +123,28 @@ class CertificateDataStore { } } + private func serchLabelID() -> String? { + var labelID = 0 + while labelID < 20 { + let label = kClientCertLabel + String(labelID) + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + kSecAttrLabel as String: label, + kSecReturnRef as String: true, + kSecReturnPersistentRef as String: true + ] + var result: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &result) + switch status { + case errSecItemNotFound: + return String(labelID) + default: + labelID += 1 + } + } + return nil + } + private func removeIdentity(label: String) { let query: [String: Any] = [ kSecClass as String: kSecClassIdentity, @@ -95,3 +160,4 @@ class CertificateDataStore { return URLCredential(identity: identity, certificates: nil, persistence: .forSession) } } + diff --git a/AgileWorks/Common/DataStore/KeychainDataStore.swift b/AgileWorks/Common/DataStore/KeychainDataStore.swift index 681a27c146c33e8e13104f779fb020c1ba38129b..03de57d9aad1e30614ffe427c83b906ed362b5b2 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,8 @@ final class KeychainDataStore: DataStoreProtocol { private let kContextPath: String = "ContextPath" private let kDeviceID: String = "DeviceID" private let kSessionID: String = "SessionID" + private let kSystemName: String = "SystemName" + private let kCertificateLabel: String = "CertificateLabel" // アクセストークンの書き込み func writeAccessToken(accessToken: String) { @@ -27,18 +28,25 @@ 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() { - removeKeychainValue(key: kAccessToken) + func removeAccessToken(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kAccessToken, serverNumber: serverNumber) + }else { + 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 +55,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,8 +66,12 @@ final class KeychainDataStore: DataStoreProtocol { } // 認可ステータスの削除 - func removeOAuthState() { - removeKeychainValue(key: kOAuthState) + func removeOAuthState(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kOAuthState, serverNumber: serverNumber) + }else { + removeKeychainValue(key: kOAuthState) + } } // 言語設定の書き込み @@ -83,7 +95,10 @@ final class KeychainDataStore: DataStoreProtocol { } // サーバーURLの読み込み - func readServerURL() -> String? { + func readServerURL(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kServerURL, serverNumber: serverNumber) + } return getKeychainValue(key: kServerURL) } @@ -98,7 +113,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 +131,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,8 +149,12 @@ final class KeychainDataStore: DataStoreProtocol { } // デバイスIDの削除 - func removeDeviceID() { - removeKeychainValue(key: kDeviceID) + func removeDeviceID(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kDeviceID, serverNumber: serverNumber) + } else { + removeKeychainValue(key: kDeviceID) + } } // セッションIDの書き込み @@ -139,23 +168,83 @@ final class KeychainDataStore: DataStoreProtocol { } // セッションIDの削除 - func removeSessionID() { - removeKeychainValue(key: kSessionID) + func removeSessionID(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kSessionID, serverNumber: serverNumber) + } else { + removeKeychainValue(key: kSessionID) + } } + // サーバー識別名の書き込み + func writeSystemName(systemName: String?) { + if systemName != nil { + setKeychainValue(key: kSystemName, value: systemName!) + } else { + //systemNameがレスポンスに無い場合、serverURLをsystemNameとして設定 + let systemName = readServerURL() + setKeychainValue(key: kSystemName, value: systemName!) + } + } + + // サーバー識別名の読み込み + func readSystemName(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kSystemName, serverNumber: serverNumber) + } + return getKeychainValue(key: kSystemName) + } + + // サーバー識別名の削除 + func removeSystemName() { + removeKeychainValue(key: kSystemName) + } + + func writeCertificateLabel(label: String) { + setKeychainValue(key: kCertificateLabel, value: label) + } + + func readCertificateLabel(serverNumber: Int? = nil) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kCertificateLabel, serverNumber: serverNumber) + } + return getKeychainValue(key: kCertificateLabel) + } + + func removeCertificateLabel() { + removeKeychainValue(key: kCertificateLabel) + } // 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/OldServerCertificateDataStore.swift b/AgileWorks/Common/DataStore/OldServerCertificateDataStore.swift new file mode 100644 index 0000000000000000000000000000000000000000..6b4ce7cab92bac995828eb5c370a2ec70fafd4d6 --- /dev/null +++ b/AgileWorks/Common/DataStore/OldServerCertificateDataStore.swift @@ -0,0 +1,97 @@ +// +// OldServerCertificateDataStore.swift +// AgileWorks +// +// Created by Gk40002148 on 2023/12/06. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import Foundation + +// クライアント証明書格納用ラベル +//let clientCertLabel = "ClientCert" + +/// 証明書情報のデータストア。 +/// +/// `KeychainDataStore` と同様にキーチェーンへのアクセスを行うが、`KeychainDataStore` が +/// その実装に用いている `Keychain` は `kSecIdentity` の読み書きに対応していないため別途定義。 +class OldServerCertificateDataStore { + /// クライアント証明書を読み込む。取得できなければ `nil` 。 + func readClientCertificate() -> SecIdentity? { + return readIdentity(label: kClientCertLabel) + } + + /// クライアント証明書を利用した `URLCredential` (セッション内で有効) を取得する。取得できなければ `nil`。 + func getClientCertificateSessionCredential() -> URLCredential? { + if let identity = readClientCertificate() { + return sessionCredential(identity: identity) + } else { + return nil + } + } + + /// クライアント証明書を書き込む。 + func writeClientCertificate(identity: SecIdentity) { + writeIdentity(label: kClientCertLabel, identity: identity) + } + + /// クライアント証明書を削除する。 + func removeClientCertificate() { + removeIdentity(label: kClientCertLabel) + } + + private func readIdentity(label: String) -> SecIdentity? { + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + kSecAttrLabel as String: label, + kSecReturnRef as String: true, + kSecReturnPersistentRef as String: true + ] + var result: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &result) + switch status { + case errSecSuccess: + if let result = result { + let dic = result as! [String: Any] + let identity = dic[kSecValueRef as String] as! SecIdentity + return identity + } else { + log.error("unexpected nil result") + return nil + } + + case errSecItemNotFound: + return nil + + default: + log.error("keychain reading error: OSStatus = \(status)") + return nil + } + } + + private func writeIdentity(label: String, identity: SecIdentity) { + let query: [String: Any] = [ + kSecValueRef as String: identity, + kSecAttrLabel as String: label, + ] + let status = SecItemAdd(query as CFDictionary, nil) + if status != errSecSuccess { + log.error("keychain writing error: OSStatus = \(status)") + } + } + + private func removeIdentity(label: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + kSecAttrLabel as String: label + ] + let status = SecItemDelete(query as CFDictionary) + if status != errSecSuccess { + log.error("keychain writing error: OSStatus = \(status)") + } + } + + private func sessionCredential(identity: SecIdentity) -> URLCredential { + return URLCredential(identity: identity, certificates: nil, persistence: .forSession) + } +} diff --git a/AgileWorks/Common/DataStore/OldServerKeychainDataStore.swift b/AgileWorks/Common/DataStore/OldServerKeychainDataStore.swift new file mode 100644 index 0000000000000000000000000000000000000000..a6e3446cbc2445e5a8dac8e0f06367b5f84954c0 --- /dev/null +++ b/AgileWorks/Common/DataStore/OldServerKeychainDataStore.swift @@ -0,0 +1,165 @@ +// +// OldServerKeychainDataStore.swift +// AgileWorks +// +// Created by Gk40002148 on 2023/12/06. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import AppAuth +import KeychainAccess +import UIKit + +final class OldServerKeychainDataStore: 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" + private let kServerURL: String = "ServerURL" + private let kContextPath: String = "ContextPath" + private let kDeviceID: String = "DeviceID" + private let kSessionID: String = "SessionID" + + // アクセストークンの書き込み + func writeAccessToken(accessToken: String) { + setKeychainValue(key: kAccessToken, value: accessToken) + } + + // アクセストークンの読み込み + func readAccessToken() -> String? { + return getKeychainValue(key: kAccessToken) + } + + // アクセストークンの削除 + func removeAccessToken() { + removeKeychainValue(key: kAccessToken) + } + + // 認可ステータスの書き込み + func writeOAuthState(authState: OIDAuthState) { + let keychain = Keychain(service: service, accessGroup: groupId) + guard let object = try? NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: true) else { + return + } + keychain[data: kOAuthState] = object + } + + // 認可ステータスの読み込み + func readOAuthState() -> OIDAuthState? { + let keychain = Keychain(service: service, accessGroup: groupId) + if let object = keychain[data: kOAuthState] { + guard let authState = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(object) as? OIDAuthState else { + return nil + } + return authState + } + return nil + } + + // 認可ステータスの削除 + func removeOAuthState() { + removeKeychainValue(key: kOAuthState) + } + + // 言語設定の書き込み + func writeLanguage(language: String) { + setKeychainValue(key: kLanguage, value: language) + } + + // 言語設定の読み込み + func readLanguage() -> String? { + return getKeychainValue(key: kLanguage) + } + + // 言語設定の削除 + func removeLanguage() { + removeKeychainValue(key: kLanguage) + } + + // サーバーURLの書き込み + func writeServerURL(serverURL: String) { + setKeychainValue(key: kServerURL, value: serverURL) + } + + // サーバーURLの読み込み + func readServerURL() -> String? { + return getKeychainValue(key: kServerURL) + } + + // サーバーURLの削除 + func removeServerURL() { + removeKeychainValue(key: kServerURL) + } + + // コンテキストパスの書き込み + func writeContextPath(contextPath: String) { + setKeychainValue(key: kContextPath, value: contextPath) + } + + // コンテキストパスの読み込み + func readContextPath() -> String? { + return getKeychainValue(key: kContextPath) + } + + // コンテキストパスの削除 + func removeContextPath() { + removeKeychainValue(key: kContextPath) + } + + // デバイスIDの書き込み + func writeDeviceID(deviceID: Int) { + setKeychainValue(key: kDeviceID, value: deviceID.description) + } + + // デバイスIDの読み込み + func readDeviceID() -> Int? { + guard let deviceIDString = getKeychainValue(key: kDeviceID) else { + return nil + } + guard let deviceID = Int(deviceIDString) else { + return nil + } + return deviceID + } + + // デバイスIDの削除 + func removeDeviceID() { + removeKeychainValue(key: kDeviceID) + } + + // セッションIDの書き込み + func writeSessionID(sessionID: String) { + setKeychainValue(key: kSessionID, value: sessionID) + } + + // セッションIDの読み込み + func readSessionID() -> String? { + return getKeychainValue(key: kSessionID) + } + + // セッションIDの削除 + func removeSessionID() { + removeKeychainValue(key: kSessionID) + } + + // KeyChain へ指定の値をセット + private func setKeychainValue(key: String, value: String) { + let keychain = Keychain(service: service, accessGroup: groupId) + keychain[key] = value + } + // KeyChain から指定の値を取得 + private func getKeychainValue(key: String) -> String? { + let keychain = Keychain(service: service, accessGroup: groupId) + return keychain[key] + } + // KeyChain から指定の値を削除 + private func removeKeychainValue(key: String) { + let keychain = Keychain(service: service, accessGroup: groupId) + do { + try keychain.remove(key) + } catch { + log.e(error) + } + } +} 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/Entity/SessionInfo.swift b/AgileWorks/Common/Entity/SessionInfo.swift index 695a4e85af69b993c0d59e4217327c46100fc6d0..a9a3e60037e7ce265498ff52be5e6c6a58ba6adc 100644 --- a/AgileWorks/Common/Entity/SessionInfo.swift +++ b/AgileWorks/Common/Entity/SessionInfo.swift @@ -34,4 +34,5 @@ struct SessionInfo: Codable { let unit: [Unit] let role: [Role] let sessionId: String + var systemName: String? } 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/Utility/URLAuthenticationUtility.swift b/AgileWorks/Common/Utility/URLAuthenticationUtility.swift index cd151b6357cb1ef832408c2d4a570bda95d531a3..3eadaec8e9ccba174e09ba4cacb1acc721a97af3 100644 --- a/AgileWorks/Common/Utility/URLAuthenticationUtility.swift +++ b/AgileWorks/Common/Utility/URLAuthenticationUtility.swift @@ -8,12 +8,13 @@ import Foundation -func handleURLAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { +func handleURLAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void, serverNumber: Int? = nil) { var handled = false switch challenge.protectionSpace.authenticationMethod { case NSURLAuthenticationMethodClientCertificate: - if let credential = CertificateDataStore().getClientCertificateSessionCredential() { + var certificateLabel = KeychainDataStore().readCertificateLabel(serverNumber: serverNumber) + if let credential = CertificateDataStore().getClientCertificateSessionCredential(label: certificateLabel) { completionHandler(.useCredential, credential) handled = true } diff --git a/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift b/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift index 3dae7caae66b3b29a4150915f74be6cdf8d5c1ff..7d5506ffe1e7887321c41498ef3e46d4d27e5c8a 100644 --- a/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift +++ b/AgileWorks/Common/WebClient/DeleteDeviceEndpoint.swift @@ -12,21 +12,18 @@ 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]? + + var baseURL: URL - init(request: DeviceRequest) { + init(request: DeviceRequest, serverNumber: Int?) { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase @@ -35,6 +32,16 @@ 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 ?? ""] + + let serverURL = KeychainDataStore().readServerURL(serverNumber: serverNumber) ?? "" + self.baseURL = URL(string: "https://" + serverURL)! } } diff --git a/AgileWorks/Common/WebClient/Session.swift b/AgileWorks/Common/WebClient/Session.swift index da0f712ec3c119f40eb09df4fef1398c50e62e7f..bb3886b6cfe4793ef4fda2f1b7b0d5e26d70462e 100644 --- a/AgileWorks/Common/WebClient/Session.swift +++ b/AgileWorks/Common/WebClient/Session.swift @@ -17,25 +17,29 @@ enum SessionError: Error { class Session { private static let kRetryCount = 3 - private static let privateShared = URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) + //private static let privateShared = URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) private static var retryCount = kRetryCount - class var shared: URLSession { + class func shared(serverNumber: Int? = nil) -> URLSession { + var sessionDelegate = SessionDelegate() + + sessionDelegate.serverNumber = serverNumber + let privateShared = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) return privateShared } private class SessionDelegate: NSObject, URLSessionDelegate { + var serverNumber: Int? = nil func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - handleURLAuthenticationChallenge(challenge, completionHandler: completionHandler) + handleURLAuthenticationChallenge(challenge, completionHandler: completionHandler, serverNumber: serverNumber) } } @discardableResult - class func send(_ request: T, completion: @escaping (Result) -> Void) -> URLSessionTask? { + class func send(serverNumber: Int? = nil, _ request: T, completion: @escaping (Result) -> Void) -> URLSessionTask? { let req = request.request log.debug("Session Request: \(String(describing: req.url))\nHTTPMethod: \(String(describing: req.httpMethod) )\nHeaders: \(String(describing: req.allHTTPHeaderFields))\nHTTPBody: \(String(data: req.httpBody ?? Data(), encoding: .utf8)!)") - - let task = shared.dataTask(with: req) { data, response, error in + let task = shared(serverNumber: serverNumber).dataTask(with: req) { data, response, error in let result: Result let res = response as? HTTPURLResponse diff --git a/AgileWorks/Common/WidgetView.swift b/AgileWorks/Common/WidgetView.swift index 49e429b313c07aa0d848af38b61a1e7fd1c8062c..7120725734f2638707cbed98cb52dcc499bae39c 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,14 +276,15 @@ 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) + ApprovalItemView(itemNum: itemNum, entry: entry) .onTapGesture { - if viewController != nil { - viewController!.extensionContext?.open(NSURL(fileURLWithPath: getStatusURL())as URL, completionHandler: nil) + if entry.viewController != nil { + entry.viewController!.extensionContext?.open(NSURL(fileURLWithPath: getStatusURL())as URL, completionHandler: nil) } } } @@ -248,44 +293,92 @@ struct LinkView: View { //書類状況の内容を表示するビュー 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().readSystemName(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().readSystemName(serverNumber: serverNumber)!) + } + } + Text(entry.message ?? "") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .foregroundColor(Color.textColor) + } +} + +func getWorkURL(serverNumber: Int?) -> String { + let serverURL = KeychainDataStore().readServerURL(serverNumber: serverNumber) ?? "" + let contextPath = KeychainDataStore().readContextPath(serverNumber: serverNumber) ?? "" + var url = "https://" + serverURL + "/" + contextPath + "/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) { + let serverURL = KeychainDataStore().readServerURL(serverNumber: serverNumber) ?? "" + let contextPath = KeychainDataStore().readContextPath(serverNumber: serverNumber) ?? "" + let url: String = "https://" + serverURL + "/" + contextPath + "/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 +389,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..3ef13528415c6a2f1acab484a31729846f3ccb6f --- /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/ShareExtension/View/ShareViewController.swift b/AgileWorks/ShareExtension/View/ShareViewController.swift index b0be7d8afcd58ac7334fe8707ca2474f531f158a..e0086a8753e3a29121d7d2a045b72c35b794fa64 100644 --- a/AgileWorks/ShareExtension/View/ShareViewController.swift +++ b/AgileWorks/ShareExtension/View/ShareViewController.swift @@ -47,9 +47,19 @@ class ShareViewController: UITableViewController { @IBAction func onImportButtonTapped(_ sender: UIBarButtonItem) { importCertificateFromContext() { identity, error in if let identity = identity { - CertificateDataStore().writeClientCertificate(identity: identity) - self.alertInUiThread(message: getDisplayString(key: "CertificateImportSuccess", comment: "")) { - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + //書き込み + let result = CertificateDataStore().writeNewClientCertificate(identity: identity) + + if let _ = result { + //成功 + self.alertInUiThread(message: getDisplayString(key: "CertificateImportSuccess", comment: "")) { + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + } + } else { + //保存上限オーバー + self.alertInUiThread(message: getDisplayString(key: "CertificateStorageLimit", comment: "")) { + self.extensionContext?.cancelRequest(withError: error ?? ShareExtensionError.genericError) + } } } else { if let error = error as NSError?, 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..76334221fe842b57d54064d4bb0bb5cd6b448203 100644 --- a/AgileWorks/TodayExtension/View/TodayViewController.swift +++ b/AgileWorks/TodayExtension/View/TodayViewController.swift @@ -25,24 +25,52 @@ 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) + + } + + if !serverList.isEmpty { + 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 +112,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 + + +