diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index bfd5665b3df9ca083acf706473f239588606431c..21712b0210d8b06eee76ef084a7bedc4ee71fba4 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -97,8 +97,8 @@ 1A0C4EE31F1D673600550433 /* InjectionBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0C4EE21F1D673600550433 /* InjectionBag.swift */; }; 1A0C4EE51F1D67DF00550433 /* WalkthroughCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A0C4EE41F1D67DF00550433 /* WalkthroughCoordinator.swift */; }; 1A20417A1F1E547F00C08435 /* Stateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2041791F1E547F00C08435 /* Stateable.swift */; }; - 1A20417C1F1E56FF00C08435 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A20417B1F1E56FF00C08435 /* WelcomeViewModel.swift */; }; - 1A2041881F1EA1EA00C08435 /* CreateAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2041871F1EA1EA00C08435 /* CreateAccountViewModel.swift */; }; + 1A20417C1F1E56FF00C08435 /* WelcomeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A20417B1F1E56FF00C08435 /* WelcomeVM.swift */; }; + 1A2041881F1EA1EA00C08435 /* CreateAccountVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2041871F1EA1EA00C08435 /* CreateAccountVM.swift */; }; 1A20418B1F1EA58A00C08435 /* ViewModelBased.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A20418A1F1EA58A00C08435 /* ViewModelBased.swift */; }; 1A20418D1F1EABCC00C08435 /* StateableResponsive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A20418C1F1EABCC00C08435 /* StateableResponsive.swift */; }; 1A20418F1F1EAC0E00C08435 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A20418E1F1EAC0E00C08435 /* Coordinator.swift */; }; @@ -182,6 +182,10 @@ 261E4EF229F8191300ABB37C /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261E4EF129F8191300ABB37C /* LoadingView.swift */; }; 2625814E2B4DE40200314C42 /* MessagePanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2625814D2B4DE40200314C42 /* MessagePanelView.swift */; }; 262581502B4EF15D00314C42 /* MessagePanelVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2625814F2B4EF15D00314C42 /* MessagePanelVM.swift */; }; + 262F1CFA2C88BBBC002BF597 /* LinkToAccountVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 262F1CF92C88BBBC002BF597 /* LinkToAccountVM.swift */; }; + 2632D2F12CA5A82F00E60096 /* ImportFromArchiveVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2632D2F02CA5A82F00E60096 /* ImportFromArchiveVM.swift */; }; + 2632D2F32CA5AF9100E60096 /* ConnectToManagerVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2632D2F22CA5AF9100E60096 /* ConnectToManagerVM.swift */; }; + 2632D2F52CA5AFBB00E60096 /* ConnectSipVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2632D2F42CA5AFBB00E60096 /* ConnectSipVM.swift */; }; 263B7158246D9390007044C4 /* SmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7157246D9390007044C4 /* SmartListCell.swift */; }; 263B715A246D9556007044C4 /* IncognitoSmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7159246D9556007044C4 /* IncognitoSmartListCell.swift */; }; 263B715C246D96E5007044C4 /* IncognitoSmartListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 263B715B246D96E5007044C4 /* IncognitoSmartListCell.xib */; }; @@ -523,8 +527,6 @@ BB3E5815291C138600E85BEA /* SwarmInfoViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB3E5814291C138600E85BEA /* SwarmInfoViewController.storyboard */; }; BB4C6E2629229131001C901A /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4C6E2529229131001C901A /* ColorExtension.swift */; }; BB6690042A99043200875848 /* SharedActionsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6690032A99043200875848 /* SharedActionsPresenter.swift */; }; - BB6690072A99069900875848 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6690052A99069200875848 /* WelcomeViewController.swift */; }; - BB6690082A99069900875848 /* WelcomeViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB6690062A99069400875848 /* WelcomeViewController.storyboard */; }; BB68E6602AB1F03000F02AB7 /* ScreenHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB68E65F2AB1F03000F02AB7 /* ScreenHelper.swift */; }; BB8BBCA329F04DA1007228BA /* LocationSharingServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BBCA229F04DA1007228BA /* LocationSharingServiceTests.swift */; }; BB90263528F0918700B85859 /* PaddingTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB90263428F0918700B85859 /* PaddingTextField.swift */; }; @@ -744,8 +746,8 @@ 1A1E476C1F0E808500EA9A36 /* Reusable.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reusable.framework; path = Carthage/Build/iOS/Reusable.framework; sourceTree = "<group>"; }; 1A1E476E1F0E894600EA9A36 /* SwiftyBeaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyBeaver.framework; path = Carthage/Build/iOS/SwiftyBeaver.framework; sourceTree = "<group>"; }; 1A2041791F1E547F00C08435 /* Stateable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stateable.swift; sourceTree = "<group>"; }; - 1A20417B1F1E56FF00C08435 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = "<group>"; }; - 1A2041871F1EA1EA00C08435 /* CreateAccountViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateAccountViewModel.swift; sourceTree = "<group>"; }; + 1A20417B1F1E56FF00C08435 /* WelcomeVM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeVM.swift; sourceTree = "<group>"; }; + 1A2041871F1EA1EA00C08435 /* CreateAccountVM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateAccountVM.swift; sourceTree = "<group>"; }; 1A20418A1F1EA58A00C08435 /* ViewModelBased.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelBased.swift; sourceTree = "<group>"; }; 1A20418C1F1EABCC00C08435 /* StateableResponsive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateableResponsive.swift; sourceTree = "<group>"; }; 1A20418E1F1EAC0E00C08435 /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; }; @@ -832,6 +834,10 @@ 2625814D2B4DE40200314C42 /* MessagePanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePanelView.swift; sourceTree = "<group>"; }; 2625814F2B4EF15D00314C42 /* MessagePanelVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePanelVM.swift; sourceTree = "<group>"; }; 262AA981262C724700DC34AD /* libfmt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfmt.a; path = ../DEPS/arm64/lib/libfmt.a; sourceTree = "<group>"; }; + 262F1CF92C88BBBC002BF597 /* LinkToAccountVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkToAccountVM.swift; sourceTree = "<group>"; }; + 2632D2F02CA5A82F00E60096 /* ImportFromArchiveVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportFromArchiveVM.swift; sourceTree = "<group>"; }; + 2632D2F22CA5AF9100E60096 /* ConnectToManagerVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToManagerVM.swift; sourceTree = "<group>"; }; + 2632D2F42CA5AFBB00E60096 /* ConnectSipVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectSipVM.swift; sourceTree = "<group>"; }; 26376721245315E600CDC51F /* Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Debug.entitlements; sourceTree = "<group>"; }; 263B7157246D9390007044C4 /* SmartListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartListCell.swift; sourceTree = "<group>"; }; 263B7159246D9556007044C4 /* IncognitoSmartListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoSmartListCell.swift; sourceTree = "<group>"; }; @@ -1195,8 +1201,6 @@ BB3E5814291C138600E85BEA /* SwarmInfoViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SwarmInfoViewController.storyboard; sourceTree = "<group>"; }; BB4C6E2529229131001C901A /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; }; BB6690032A99043200875848 /* SharedActionsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedActionsPresenter.swift; sourceTree = "<group>"; }; - BB6690052A99069200875848 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; }; - BB6690062A99069400875848 /* WelcomeViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = WelcomeViewController.storyboard; sourceTree = "<group>"; }; BB68E65F2AB1F03000F02AB7 /* ScreenHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenHelper.swift; sourceTree = "<group>"; }; BB8BBCA229F04DA1007228BA /* LocationSharingServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingServiceTests.swift; sourceTree = "<group>"; }; BB90263428F0918700B85859 /* PaddingTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingTextField.swift; sourceTree = "<group>"; }; @@ -1920,22 +1924,12 @@ children = ( 26C4D07C2C61562800B1218C /* Models */, 26F5442B2C5815640069A7A4 /* Views */, - 1A0C4ED51F1D49D800550433 /* Welcome */, 1A0C4EE41F1D67DF00550433 /* WalkthroughCoordinator.swift */, ); name = Walkthrough; path = Features/Walkthrough; sourceTree = "<group>"; }; - 1A0C4ED51F1D49D800550433 /* Welcome */ = { - isa = PBXGroup; - children = ( - BB6690062A99069400875848 /* WelcomeViewController.storyboard */, - BB6690052A99069200875848 /* WelcomeViewController.swift */, - ); - name = Welcome; - sourceTree = "<group>"; - }; 1A0C4EDF1F1D624100550433 /* Coordinators */ = { isa = PBXGroup; children = ( @@ -2395,8 +2389,12 @@ 26C4D07C2C61562800B1218C /* Models */ = { isa = PBXGroup; children = ( - 1A20417B1F1E56FF00C08435 /* WelcomeViewModel.swift */, - 1A2041871F1EA1EA00C08435 /* CreateAccountViewModel.swift */, + 1A20417B1F1E56FF00C08435 /* WelcomeVM.swift */, + 1A2041871F1EA1EA00C08435 /* CreateAccountVM.swift */, + 262F1CF92C88BBBC002BF597 /* LinkToAccountVM.swift */, + 2632D2F02CA5A82F00E60096 /* ImportFromArchiveVM.swift */, + 2632D2F22CA5AF9100E60096 /* ConnectToManagerVM.swift */, + 2632D2F42CA5AFBB00E60096 /* ConnectSipVM.swift */, ); path = Models; sourceTree = "<group>"; @@ -2818,7 +2816,6 @@ 0EABD3852303600B00DE7ACF /* default.wav in Resources */, 26AF9F3D2977126500C7069A /* LogViewController.storyboard in Resources */, 6613A612214AFF4700B497D1 /* ScanViewController.storyboard in Resources */, - BB6690082A99069900875848 /* WelcomeViewController.storyboard in Resources */, 1ABE07DF1F0D91A800D36361 /* LaunchScreen.storyboard in Resources */, 2662FC81246B793500FA7782 /* IncognitoSmartListViewController.storyboard in Resources */, 1A5DC0381F35675E0075E8EF /* ContactRequestCell.xib in Resources */, @@ -2959,6 +2956,7 @@ 0E5806F523BE4307007D1F5D /* PlayerViewModel.swift in Sources */, 1DF75AC2296E09240055EA87 /* Button+Helpers.swift in Sources */, 02B22E091DF7585F000358C9 /* DaemonService.swift in Sources */, + 2632D2F52CA5AFBB00E60096 /* ConnectSipVM.swift in Sources */, 5CE66F761FBF769B00EE9291 /* InitialLoadingViewController.swift in Sources */, 66ACB430214AE28C00A94162 /* ScanViewController.swift in Sources */, 26454FBD2B7FDED400CFF06C /* AboutSwiftUIView.swift in Sources */, @@ -2972,6 +2970,7 @@ 268C87F02C1BA78E00593D7C /* UtilitiesViews.swift in Sources */, 26A34ED52C6E83D500A41DD4 /* ImportFromArchiveView.swift in Sources */, BB3B0A412971AEE30083CAD8 /* LocationSharingView.swift in Sources */, + 262F1CFA2C88BBBC002BF597 /* LinkToAccountVM.swift in Sources */, 267AD77E252B979F00047593 /* ConferenceParticipant.swift in Sources */, 268C87EC2C1BA28A00593D7C /* ManageAccountView.swift in Sources */, 0E49097A1FEAC9E1005CAA50 /* CallViewController.swift in Sources */, @@ -3056,7 +3055,7 @@ 264EA87E2977080300B6FB6F /* LogViewModel.swift in Sources */, 62B60AF92048A40D001BEACF /* DataTransferAdapter.mm in Sources */, 1A5DC0371F35675E0075E8EF /* ContactRequestCell.swift in Sources */, - 1A20417C1F1E56FF00C08435 /* WelcomeViewModel.swift in Sources */, + 1A20417C1F1E56FF00C08435 /* WelcomeVM.swift in Sources */, 265DFB09292FD25000834B97 /* DefaultTransferView.swift in Sources */, 1A5DC03D1F35678D0075E8EF /* RequestItem.swift in Sources */, BB1E8C7729159E1F005AE1D6 /* SwarmInfoViewController.swift in Sources */, @@ -3081,7 +3080,6 @@ 62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */, 26625BB72BA9DF81009D2DDB /* ConversationsViewModel.swift in Sources */, 1DF75AC6296E0C2A0055EA87 /* AddMoreParticipantsInSwarm.swift in Sources */, - BB6690072A99069900875848 /* WelcomeViewController.swift in Sources */, 1A2D18B71F29164700B2C785 /* SmartlistViewModel.swift in Sources */, 62AA15C31FFC39C80064A063 /* VideoAdapterDelegate.swift in Sources */, 2692557A2C5D1E1200F85A1E /* JamsConnectView.swift in Sources */, @@ -3135,7 +3133,7 @@ 268C87F42C1CA93B00593D7C /* EncryptAccountView.swift in Sources */, 268C87EE2C1BA74C00593D7C /* EditProfileView.swift in Sources */, 1A3D28A91F0EBF0200B524EE /* UIView+Ring.swift in Sources */, - 1A2041881F1EA1EA00C08435 /* CreateAccountViewModel.swift in Sources */, + 1A2041881F1EA1EA00C08435 /* CreateAccountVM.swift in Sources */, 62E55B6D1F758E6F00D3FEF4 /* String+Helpers.swift in Sources */, 1ABE07D21F0D8FE800D36361 /* Images.swift in Sources */, 0273C3081E0C68BF00CF00BA /* DesignableButton.swift in Sources */, @@ -3165,6 +3163,7 @@ 0E5A668322F0B1F100AA6820 /* ProgressView.swift in Sources */, 26EF35EF29075A5300D97E14 /* MessageStackView.swift in Sources */, BB1E8C7329159DFC005AE1D6 /* MembersList.swift in Sources */, + 2632D2F32CA5AF9100E60096 /* ConnectToManagerVM.swift in Sources */, 66266FC4215C18F8002757A6 /* Emoji+Helpers.swift in Sources */, 0E0FF1B71FC398B3003898C2 /* ConversationDataHepler.swift in Sources */, 62DFAB2E1F9FF0D0002D6F9C /* NetworkService.swift in Sources */, @@ -3200,6 +3199,7 @@ 26BCBBD32A964ABC0001EE38 /* ConferenceActionsModel.swift in Sources */, 0E6F5453223C3C7500ECC3CE /* AccountItemView.swift in Sources */, 26EEB19C2BD98CCA00B8B04B /* SearchBar.swift in Sources */, + 2632D2F12CA5A82F00E60096 /* ImportFromArchiveVM.swift in Sources */, 0EF49AA123828CBC0064CD98 /* ParticipantProfileInfo.swift in Sources */, BB68E6602AB1F03000F02AB7 /* ScreenHelper.swift in Sources */, 0EF49AA123828CBC0064CD98 /* ParticipantProfileInfo.swift in Sources */, diff --git a/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme b/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme index f8684778289a910899b44f988ac7b9060544f01d..2c4c68674f6394c52f2e775ef3a00d23e7b51147 100644 --- a/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme +++ b/Ring/Ring.xcodeproj/xcshareddata/xcschemes/Ring.xcscheme @@ -60,7 +60,7 @@ </Testables> </TestAction> <LaunchAction - buildConfiguration = "Debug" + buildConfiguration = "Debug with lint" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" diff --git a/Ring/Ring/About/AboutSwiftUIVM.swift b/Ring/Ring/About/AboutSwiftUIVM.swift index 9c45bbc15f3bcfbf07c34fd3141b4071c7044986..b6a90290c7cd4642175e80069f5057101a280ba5 100644 --- a/Ring/Ring/About/AboutSwiftUIVM.swift +++ b/Ring/Ring/About/AboutSwiftUIVM.swift @@ -17,8 +17,9 @@ */ import Foundation +import RxSwift -class AboutSwiftUIVM { +class AboutSwiftUIVM: Dismissable { let declarationText = L10n.AboutJami.declaration1 + " [jami.net](https://jami.net) " + L10n.AboutJami.declaration2 let noWarrantyText = L10n.AboutJami.noWarranty1 + " [NU General Public License](https://www.gnu.org/licenses/gpl-3.0.html), " + L10n.AboutJami.noWarranty2 let mainUrlText = "© 2015-2024 [Savoir-Faire linux](https://savoirfairelinux.com)" @@ -28,6 +29,9 @@ class AboutSwiftUIVM { let createdLabel: String = L10n.AboutJami.createdBy let artworkLabel: String = L10n.AboutJami.artworkBy + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + func openContributeLink() { if let url = URL(string: "https://jami.net/contribute/") { UIApplication.shared.open(url) diff --git a/Ring/Ring/Constants/Generated/Storyboards.swift b/Ring/Ring/Constants/Generated/Storyboards.swift index 08c912c8e4a150777275b4d4cee9e962356a7a0f..1ab07f43c0de0c80cd3ecee363f4f5b0779e38f8 100644 --- a/Ring/Ring/Constants/Generated/Storyboards.swift +++ b/Ring/Ring/Constants/Generated/Storyboards.swift @@ -99,11 +99,6 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType<Ring.SmartlistViewController>(storyboard: SmartlistViewController.self) } - internal enum WelcomeViewController: StoryboardType { - internal static let storyboardName = "WelcomeViewController" - - internal static let initialScene = InitialSceneType<Ring.WelcomeViewController>(storyboard: WelcomeViewController.self) - } } internal enum StoryboardSegue { diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift index b1c337b98687e49c05dc3c79dfd1d6585e5319b0..570f6032e0ac2d94dca142a8ad27b5bb4ec1709a 100644 --- a/Ring/Ring/Constants/Generated/Strings.swift +++ b/Ring/Ring/Constants/Generated/Strings.swift @@ -35,8 +35,8 @@ internal enum L10n { internal static let advancedFeatures = L10n.tr("Localizable", "account.advancedFeatures", fallback: "Advanced Features") /// Configure internal static let configure = L10n.tr("Localizable", "account.configure", fallback: "Configure") - /// Configure a SIP Account - internal static let createSipAccount = L10n.tr("Localizable", "account.createSipAccount", fallback: "Configure a SIP Account") + /// Configure SIP Account + internal static let createSipAccount = L10n.tr("Localizable", "account.createSipAccount", fallback: "Configure SIP Account") /// Enable Account internal static let enableAccount = L10n.tr("Localizable", "account.enableAccount", fallback: "Enable Account") /// Me @@ -71,8 +71,8 @@ internal enum L10n { internal static let accountHeader = L10n.tr("Localizable", "accountPage.accountHeader", fallback: "Account") /// Account identity internal static let accountIdentity = L10n.tr("Localizable", "accountPage.accountIdentity", fallback: "Account identity") - /// These settings will only apply for this account - internal static let accountSettingsExplanation = L10n.tr("Localizable", "accountPage.accountSettingsExplanation", fallback: "These settings will only apply for this account") + /// These settings will only apply to this account. + internal static let accountSettingsExplanation = L10n.tr("Localizable", "accountPage.accountSettingsExplanation", fallback: "These settings will only apply to this account.") /// App settings internal static let appSettings = L10n.tr("Localizable", "accountPage.appSettings", fallback: "App settings") /// These settings will apply on all the application @@ -87,12 +87,12 @@ internal enum L10n { internal static let boothModeExplanation = L10n.tr("Localizable", "accountPage.boothModeExplanation", fallback: "In booth mode conversation history not saved and jami functionality limited by making outgoing calls. When you enable booth mode all your conversations will be removed.") /// Bootstrap internal static let bootstrap = L10n.tr("Localizable", "accountPage.bootstrap", fallback: "Bootstrap") - /// Allow incoming calls from unknown contacts - internal static let callsFromUnknownContacts = L10n.tr("Localizable", "accountPage.callsFromUnknownContacts", fallback: "Allow incoming calls from unknown contacts") + /// Allow calls from unknown contacts. + internal static let callsFromUnknownContacts = L10n.tr("Localizable", "accountPage.callsFromUnknownContacts", fallback: "Allow calls from unknown contacts.") /// Change password internal static let changePassword = L10n.tr("Localizable", "accountPage.changePassword", fallback: "Change password") - /// Password incorrect - internal static let changePasswordError = L10n.tr("Localizable", "accountPage.changePasswordError", fallback: "Password incorrect") + /// Incorrect password + internal static let changePasswordError = L10n.tr("Localizable", "accountPage.changePasswordError", fallback: "Incorrect password") /// Connectivity and configurations internal static let connectivityAndConfiguration = L10n.tr("Localizable", "accountPage.connectivityAndConfiguration", fallback: "Connectivity and configurations") /// Connectivity @@ -143,8 +143,8 @@ internal enum L10n { internal static let enableSRTP = L10n.tr("Localizable", "accountPage.enableSRTP", fallback: "Encrypt media streams (SRTP)") /// Encrypt account with a password internal static let encryptAccount = L10n.tr("Localizable", "accountPage.encryptAccount", fallback: "Encrypt account with a password") - /// Generating PIN… - internal static let generatingPin = L10n.tr("Localizable", "accountPage.generatingPin", fallback: "Generating PIN…") + /// Generating PIN code… + internal static let generatingPin = L10n.tr("Localizable", "accountPage.generatingPin", fallback: "Generating PIN code…") /// Invite friends internal static let inviteFriends = L10n.tr("Localizable", "accountPage.inviteFriends", fallback: "Invite friends") /// Link another device @@ -161,8 +161,8 @@ internal enum L10n { internal static let noBoothMode = L10n.tr("Localizable", "accountPage.noBoothMode", fallback: "To enable Booth mode encrypt your account first.") /// Unable to receive notifications when proxy is disabled. internal static let noProxyExplanationLabel = L10n.tr("Localizable", "accountPage.noProxyExplanationLabel", fallback: "Unable to receive notifications when proxy is disabled.") - /// Notifications for Jami are disabled. Please enable them in your device settings - internal static let notificationError = L10n.tr("Localizable", "accountPage.notificationError", fallback: "Notifications for Jami are disabled. Please enable them in your device settings") + /// Notifications for Jami are disabled. Enable it in device settings in order to use this feature. + internal static let notificationError = L10n.tr("Localizable", "accountPage.notificationError", fallback: "Notifications for Jami are disabled. Enable it in device settings in order to use this feature.") /// Notifications internal static let notificationsHeader = L10n.tr("Localizable", "accountPage.notificationsHeader", fallback: "Notifications") /// Notifications @@ -173,8 +173,8 @@ internal enum L10n { internal static let otherDevices = L10n.tr("Localizable", "accountPage.otherDevices", fallback: "Other linked devices") /// Password created internal static let passwordCreated = L10n.tr("Localizable", "accountPage.passwordCreated", fallback: "Password created") - /// Your Jami account is only stored locally on this device as an archive containing your accoutn keys. Access to this archive can be protected by a password. - internal static let passwordExplanation = L10n.tr("Localizable", "accountPage.passwordExplanation", fallback: "Your Jami account is only stored locally on this device as an archive containing your accoutn keys. Access to this archive can be protected by a password.") + /// A Jami account is created and stored locally only on this device as an archive containing its account keys. Access to the archive can optionally be protected with a password. + internal static let passwordExplanation = L10n.tr("Localizable", "accountPage.passwordExplanation", fallback: "A Jami account is created and stored locally only on this device as an archive containing its account keys. Access to the archive can optionally be protected with a password.") /// This account is password encrypted, enter a password to generate PIN code internal static let passwordForPin = L10n.tr("Localizable", "accountPage.passwordForPin", fallback: "This account is password encrypted, enter a password to generate PIN code") /// Enter account password @@ -187,10 +187,10 @@ internal enum L10n { internal static let peerDiscovery = L10n.tr("Localizable", "accountPage.peerDiscovery", fallback: "Enable local peer discovery") /// Connect to other DHT nodes advertising on our local network internal static let peerDiscoveryExplanation = L10n.tr("Localizable", "accountPage.peerDiscoveryExplanation", fallback: "Connect to other DHT nodes advertising on our local network") - /// Failed to generate PIN - internal static let pinError = L10n.tr("Localizable", "accountPage.pinError", fallback: "Failed to generate PIN") - /// Install and launch Jami, select import from another device and scan QR code or manually enter the PIN. - internal static let pinExplanationMessage = L10n.tr("Localizable", "accountPage.pinExplanationMessage", fallback: "Install and launch Jami, select import from another device and scan QR code or manually enter the PIN.") + /// Failed to generate PIN code + internal static let pinError = L10n.tr("Localizable", "accountPage.pinError", fallback: "Failed to generate PIN code") + /// Install and launch Jami, select import from another device and scan QR code or manually enter a PIN code. + internal static let pinExplanationMessage = L10n.tr("Localizable", "accountPage.pinExplanationMessage", fallback: "Install and launch Jami, select import from another device and scan QR code or manually enter a PIN code.") /// On another device internal static let pinExplanationTitle = L10n.tr("Localizable", "accountPage.pinExplanationTitle", fallback: "On another device") /// Profile @@ -301,18 +301,18 @@ internal enum L10n { internal static let accountAddedTitle = L10n.tr("Localizable", "alerts.accountAddedTitle", fallback: "Account added") /// Unable to find account on the Jami network. Make sure it was exported on Jami from an existing device, and that provided credentials are correct. internal static let accountCannotBeFoundMessage = L10n.tr("Localizable", "alerts.accountCannotBeFoundMessage", fallback: "Unable to find account on the Jami network. Make sure it was exported on Jami from an existing device, and that provided credentials are correct.") - /// Unable to find account - internal static let accountCannotBeFoundTitle = L10n.tr("Localizable", "alerts.accountCannotBeFoundTitle", fallback: "Unable to find account") + /// Account error + internal static let accountCannotBeFoundTitle = L10n.tr("Localizable", "alerts.accountCannotBeFoundTitle", fallback: "Account error") /// An error occurred while creating the account. internal static let accountDefaultErrorMessage = L10n.tr("Localizable", "alerts.accountDefaultErrorMessage", fallback: "An error occurred while creating the account.") /// Account error internal static let accountDefaultErrorTitle = L10n.tr("Localizable", "alerts.accountDefaultErrorTitle", fallback: "Account error") /// Linking account internal static let accountLinkedTitle = L10n.tr("Localizable", "alerts.accountLinkedTitle", fallback: "Linking account") - /// An error occurred while adding Jami account as unable to connect to the distributed network. Check your device connectivity. - internal static let accountNoNetworkMessage = L10n.tr("Localizable", "alerts.accountNoNetworkMessage", fallback: "An error occurred while adding Jami account as unable to connect to the distributed network. Check your device connectivity.") - /// Unable to connect to the network - internal static let accountNoNetworkTitle = L10n.tr("Localizable", "alerts.accountNoNetworkTitle", fallback: "Unable to connect to the network") + /// A connectivity error occurred while adding Jami account to the distributed network. Please try again. If the problem persists, contact your system administrator. + internal static let accountNoNetworkMessage = L10n.tr("Localizable", "alerts.accountNoNetworkMessage", fallback: "A connectivity error occurred while adding Jami account to the distributed network. Please try again. If the problem persists, contact your system administrator.") + /// Network error + internal static let accountNoNetworkTitle = L10n.tr("Localizable", "alerts.accountNoNetworkTitle", fallback: "Network error") /// Already sharing location with this user internal static let alreadylocationSharing = L10n.tr("Localizable", "alerts.alreadylocationSharing", fallback: "Already sharing location with this user") /// Are you sure you want to block this contact? The conversation history with this contact will also be deleted permanently. @@ -331,8 +331,8 @@ internal enum L10n { internal static let dbFailedMessage = L10n.tr("Localizable", "alerts.dbFailedMessage", fallback: "Please close application and try to open it again") /// An error happened when launching Jami internal static let dbFailedTitle = L10n.tr("Localizable", "alerts.dbFailedTitle", fallback: "An error happened when launching Jami") - /// Unable to connect to provided account manager. Please check your credentials - internal static let errorWrongCredentials = L10n.tr("Localizable", "alerts.errorWrongCredentials", fallback: "Unable to connect to provided account manager. Please check your credentials") + /// An error occurred while connecting to Jami Account Management Server (JAMS). Please try again. If the problem persists, contact your system administrator. + internal static let errorWrongCredentials = L10n.tr("Localizable", "alerts.errorWrongCredentials", fallback: "An error occurred while connecting to Jami Account Management Server (JAMS). Please try again. If the problem persists, contact your system administrator.") /// Incoming call from internal static let incomingCallAllertTitle = L10n.tr("Localizable", "alerts.incomingCallAllertTitle", fallback: "Incoming call from ") /// Ignore @@ -363,10 +363,10 @@ internal enum L10n { internal static let profileTakePhoto = L10n.tr("Localizable", "alerts.profileTakePhoto", fallback: "Take photo") /// Upload photo internal static let profileUploadPhoto = L10n.tr("Localizable", "alerts.profileUploadPhoto", fallback: "Upload photo") - /// Record an audio message - internal static let recordAudioMessage = L10n.tr("Localizable", "alerts.recordAudioMessage", fallback: "Record an audio message") - /// Record a video message - internal static let recordVideoMessage = L10n.tr("Localizable", "alerts.recordVideoMessage", fallback: "Record a video message") + /// Record audio message + internal static let recordAudioMessage = L10n.tr("Localizable", "alerts.recordAudioMessage", fallback: "Record audio message") + /// Record video message + internal static let recordVideoMessage = L10n.tr("Localizable", "alerts.recordVideoMessage", fallback: "Record video message") /// Send file internal static let uploadFile = L10n.tr("Localizable", "alerts.uploadFile", fallback: "Send file") /// Open gallery @@ -385,8 +385,8 @@ internal enum L10n { internal static let documentPickerButton = L10n.tr("Localizable", "backupAccount.documentPickerButton", fallback: "Open backup location") /// Access to the selected location was denied. internal static let errorAccessDenied = L10n.tr("Localizable", "backupAccount.errorAccessDenied", fallback: "Access to the selected location was denied.") - /// The export operation failed. - internal static let errorFailed = L10n.tr("Localizable", "backupAccount.errorFailed", fallback: "The export operation failed.") + /// An error occurred while exporting the account. + internal static let errorFailed = L10n.tr("Localizable", "backupAccount.errorFailed", fallback: "An error occurred while exporting the account.") /// The selected file path is invalid. Please choose a different location. internal static let errorWrongLocation = L10n.tr("Localizable", "backupAccount.errorWrongLocation", fallback: "The selected file path is invalid. Please choose a different location.") /// This Jami account exists only on this device. The account will be lost if this device is lost or if the application is uninstalled. It is recommended to make a backup of this account. @@ -449,8 +449,8 @@ internal enum L10n { internal static let deletedMessage = L10n.tr("Localizable", "conversation.deletedMessage", fallback: "deleted a message") /// edited internal static let edited = L10n.tr("Localizable", "conversation.edited", fallback: "edited") - /// Failed to save image to gallery - internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage", fallback: "Failed to save image to gallery") + /// An error occurred while saving image to gallery. + internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage", fallback: "An error occurred while saving image to gallery.") /// You are currently receiving a live location from internal static let explanationReceivingLocationFrom = L10n.tr("Localizable", "conversation.explanationReceivingLocationFrom", fallback: "You are currently receiving a live location from ") /// You are currently sharing your location with @@ -494,8 +494,8 @@ internal enum L10n { internal static let customize = L10n.tr("Localizable", "createAccount.customize", fallback: "Customize") /// Encrypt internal static let encrypt = L10n.tr("Localizable", "createAccount.encrypt", fallback: "Encrypt") - /// A Jami account is created and stored locally only on this device, as an archive containing your account keys. Access to this archive can optionally be protected by a password. - internal static let encryptExplanation = L10n.tr("Localizable", "createAccount.encryptExplanation", fallback: "A Jami account is created and stored locally only on this device, as an archive containing your account keys. Access to this archive can optionally be protected by a password.") + /// A Jami account is created and stored locally only on this device as an archive containing its account keys. Access to the archive can optionally be protected with a password. + internal static let encryptExplanation = L10n.tr("Localizable", "createAccount.encryptExplanation", fallback: "A Jami account is created and stored locally only on this device as an archive containing its account keys. Access to the archive can optionally be protected with a password.") /// Encryption enabled internal static let encryptionEnabled = L10n.tr("Localizable", "createAccount.encryptionEnabled", fallback: "Encryption enabled") /// Encrypt account with password @@ -512,8 +512,8 @@ internal enum L10n { internal static let newAccount = L10n.tr("Localizable", "createAccount.newAccount", fallback: "New account") /// Configure an existing SIP account internal static let sipConfigure = L10n.tr("Localizable", "createAccount.sipConfigure", fallback: "Configure an existing SIP account") - /// Username registration in progress… It could take a few moments. - internal static let timeoutMessage = L10n.tr("Localizable", "createAccount.timeoutMessage", fallback: "Username registration in progress… It could take a few moments.") + /// Username registration is in progress. Please wait… + internal static let timeoutMessage = L10n.tr("Localizable", "createAccount.timeoutMessage", fallback: "Username registration is in progress. Please wait…") /// Account Created internal static let timeoutTitle = L10n.tr("Localizable", "createAccount.timeoutTitle", fallback: "Account Created") /// username already taken @@ -528,8 +528,8 @@ internal enum L10n { internal static let waitCreateAccountTitle = L10n.tr("Localizable", "createAccount.waitCreateAccountTitle", fallback: "Adding account") } internal enum CreateProfile { - /// Create your avatar - internal static let createYourAvatar = L10n.tr("Localizable", "createProfile.createYourAvatar", fallback: "Create your avatar") + /// Create profile picture + internal static let createProfilePicture = L10n.tr("Localizable", "createProfile.createProfilePicture", fallback: "Create profile picture") /// Enter a display name internal static let enterNameLabel = L10n.tr("Localizable", "createProfile.enterNameLabel", fallback: "Enter a display name") /// Enter name @@ -554,12 +554,12 @@ internal enum L10n { internal static let readableStatusCanceled = L10n.tr("Localizable", "dataTransfer.readableStatusCanceled", fallback: "Canceled") /// Initializing… internal static let readableStatusCreated = L10n.tr("Localizable", "dataTransfer.readableStatusCreated", fallback: "Initializing…") + /// Decline + internal static let readableStatusDecline = L10n.tr("Localizable", "dataTransfer.readableStatusDecline", fallback: "Decline") /// Error internal static let readableStatusError = L10n.tr("Localizable", "dataTransfer.readableStatusError", fallback: "Error") /// Transferring internal static let readableStatusOngoing = L10n.tr("Localizable", "dataTransfer.readableStatusOngoing", fallback: "Transferring") - /// Refuse - internal static let readableStatusRefuse = L10n.tr("Localizable", "dataTransfer.readableStatusRefuse", fallback: "Refuse") /// Complete internal static let readableStatusSuccess = L10n.tr("Localizable", "dataTransfer.readableStatusSuccess", fallback: "Complete") /// Recording video while multitasking with multiple apps may result in lower quality videos. For best results, record when not multitasking @@ -646,8 +646,8 @@ internal enum L10n { internal static let blockContact = L10n.tr("Localizable", "global.blockContact", fallback: "Block contact") /// Call internal static let call = L10n.tr("Localizable", "global.call", fallback: "Call") - /// Camera access is disabled. Please enable it in settings. - internal static let cameraDisabled = L10n.tr("Localizable", "global.cameraDisabled", fallback: "Camera access is disabled. Please enable it in settings.") + /// Camera access is disabled. Enable it in device settings in order to use this feature. + internal static let cameraDisabled = L10n.tr("Localizable", "global.cameraDisabled", fallback: "Camera access is disabled. Enable it in device settings in order to use this feature.") /// Cancel internal static let cancel = L10n.tr("Localizable", "global.cancel", fallback: "Cancel") /// Close @@ -658,6 +658,8 @@ internal enum L10n { internal static let copy = L10n.tr("Localizable", "global.copy", fallback: "Copy") /// Create internal static let create = L10n.tr("Localizable", "global.create", fallback: "Create") + /// Decline + internal static let decline = L10n.tr("Localizable", "global.decline", fallback: "Decline") /// Delete file from device internal static let deleteFile = L10n.tr("Localizable", "global.deleteFile", fallback: "Delete file from device") /// Delete message @@ -690,8 +692,6 @@ internal enum L10n { internal static let preview = L10n.tr("Localizable", "global.preview", fallback: "Preview") /// Recommended internal static let recommended = L10n.tr("Localizable", "global.recommended", fallback: "Recommended") - /// Refuse - internal static let refuse = L10n.tr("Localizable", "global.refuse", fallback: "Refuse") /// Register username internal static let registerAUsername = L10n.tr("Localizable", "global.registerAUsername", fallback: "Register username") /// Remove @@ -720,8 +720,8 @@ internal enum L10n { internal static let buttonTitle = L10n.tr("Localizable", "importFromArchive.buttonTitle", fallback: "Import") /// Import a Jami account from a local archive file. internal static let explanation = L10n.tr("Localizable", "importFromArchive.explanation", fallback: "Import a Jami account from a local archive file.") - /// If the account is encrypted with a password, please enter it below. - internal static let passwordExplanation = L10n.tr("Localizable", "importFromArchive.passwordExplanation", fallback: "If the account is encrypted with a password, please enter it below.") + /// If the account is encrypted with a password, please fill the following field. + internal static let passwordExplanation = L10n.tr("Localizable", "importFromArchive.passwordExplanation", fallback: "If the account is encrypted with a password, please fill the following field.") /// Select archive file internal static let selectArchiveButton = L10n.tr("Localizable", "importFromArchive.selectArchiveButton", fallback: "Select archive file") /// Import from archive @@ -730,22 +730,22 @@ internal enum L10n { internal enum Invitations { /// accepted internal static let accepted = L10n.tr("Localizable", "invitations.accepted", fallback: "accepted") - /// banned - internal static let banned = L10n.tr("Localizable", "invitations.banned", fallback: "banned") + /// blocked + internal static let blocked = L10n.tr("Localizable", "invitations.blocked", fallback: "blocked") + /// declined + internal static let declined = L10n.tr("Localizable", "invitations.declined", fallback: "declined") /// Invitations received internal static let list = L10n.tr("Localizable", "invitations.list", fallback: "Invitations received") /// No invitations internal static let noInvitations = L10n.tr("Localizable", "invitations.noInvitations", fallback: "No invitations") /// pending internal static let pending = L10n.tr("Localizable", "invitations.pending", fallback: "pending") - /// refused - internal static let refused = L10n.tr("Localizable", "invitations.refused", fallback: "refused") } internal enum LinkDevice { /// An error occurred while exporting the account. internal static let defaultError = L10n.tr("Localizable", "linkDevice.defaultError", fallback: "An error occurred while exporting the account.") - /// To complete the process, you need to open Jami on the new device and choose the option “Link this device to an account.” Your pin is valid for 10 minutes - internal static let explanationMessage = L10n.tr("Localizable", "linkDevice.explanationMessage", fallback: "To complete the process, you need to open Jami on the new device and choose the option “Link this device to an account.” Your pin is valid for 10 minutes") + /// Open Jami on the new device and choose “Link this device to an account” to complete the process. The PIN code will expire in 10 minutes. + internal static let explanationMessage = L10n.tr("Localizable", "linkDevice.explanationMessage", fallback: "Open Jami on the new device and choose “Link this device to an account” to complete the process. The PIN code will expire in 10 minutes.") /// Verifying internal static let hudMessage = L10n.tr("Localizable", "linkDevice.hudMessage", fallback: "Verifying") /// A network error occurred while exporting the account. @@ -756,22 +756,22 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "linkDevice.title", fallback: "Link new device") } internal enum LinkToAccount { - /// A PIN is required to use an existing Jami account on this device - internal static let explanationMessage = L10n.tr("Localizable", "linkToAccount.explanationMessage", fallback: "A PIN is required to use an existing Jami account on this device") - /// To generate the PIN code, go to the account management settings on the device containing the account you want to link to. Select “Link new device”. You will receive the necessary PIN to complete this form. The PIN is only valid for 10 minutes. - internal static let explanationPinMessage = L10n.tr("Localizable", "linkToAccount.explanationPinMessage", fallback: "To generate the PIN code, go to the account management settings on the device containing the account you want to link to. Select “Link new device”. You will receive the necessary PIN to complete this form. The PIN is only valid for 10 minutes.") + /// A PIN code is required to use an existing Jami account on this device. + internal static let explanationMessage = L10n.tr("Localizable", "linkToAccount.explanationMessage", fallback: "A PIN code is required to use an existing Jami account on this device.") + /// To generate the PIN code, go to the account management settings on the device containing the account you want to link to. Select “Link new device”. You will receive the necessary PIN code to complete this form. The PIN code will expire in 10 minutes. + internal static let explanationPinMessage = L10n.tr("Localizable", "linkToAccount.explanationPinMessage", fallback: "To generate the PIN code, go to the account management settings on the device containing the account you want to link to. Select “Link new device”. You will receive the necessary PIN code to complete this form. The PIN code will expire in 10 minutes.") /// Link internal static let linkButtonTitle = L10n.tr("Localizable", "linkToAccount.linkButtonTitle", fallback: "Link") - /// Choose “Link new device” from the other Jami app to show the QR code or PIN - internal static let linkDeviceMessage = L10n.tr("Localizable", "linkToAccount.linkDeviceMessage", fallback: "Choose “Link new device” from the other Jami app to show the QR code or PIN") + /// Choose “Link new device” from another Jami app to show the QR code or generate a PIN code. + internal static let linkDeviceMessage = L10n.tr("Localizable", "linkToAccount.linkDeviceMessage", fallback: "Choose “Link new device” from another Jami app to show the QR code or generate a PIN code.") /// Link device internal static let linkDeviceTitle = L10n.tr("Localizable", "linkToAccount.linkDeviceTitle", fallback: "Link device") - /// Enter PIN - internal static let pinLabel = L10n.tr("Localizable", "linkToAccount.pinLabel", fallback: "Enter PIN") - /// PIN - internal static let pinPlaceholder = L10n.tr("Localizable", "linkToAccount.pinPlaceholder", fallback: "PIN") - /// Scan QR Code - internal static let scanQRCode = L10n.tr("Localizable", "linkToAccount.scanQRCode", fallback: "Scan QR Code") + /// Enter PIN code + internal static let pinLabel = L10n.tr("Localizable", "linkToAccount.pinLabel", fallback: "Enter PIN code") + /// PIN code + internal static let pinPlaceholder = L10n.tr("Localizable", "linkToAccount.pinPlaceholder", fallback: "PIN code") + /// Scan QR code + internal static let scanQRCode = L10n.tr("Localizable", "linkToAccount.scanQRCode", fallback: "Scan QR code") /// Account linking internal static let waitLinkToAccountTitle = L10n.tr("Localizable", "linkToAccount.waitLinkToAccountTitle", fallback: "Account linking") } @@ -782,12 +782,12 @@ internal enum L10n { internal static let accountManagerPlaceholder = L10n.tr("Localizable", "linkToAccountManager.accountManagerPlaceholder", fallback: "JAMS URL") /// Enter JAMS credentials internal static let enterCredentials = L10n.tr("Localizable", "linkToAccountManager.enterCredentials", fallback: "Enter JAMS credentials") - /// Enter the Jami Account Management Server (JAMS) URL - internal static let jamsExplanation = L10n.tr("Localizable", "linkToAccountManager.jamsExplanation", fallback: "Enter the Jami Account Management Server (JAMS) URL") + /// Enter Jami Account Management Server (JAMS) URL + internal static let jamsExplanation = L10n.tr("Localizable", "linkToAccountManager.jamsExplanation", fallback: "Enter Jami Account Management Server (JAMS) URL") /// Sign In internal static let signIn = L10n.tr("Localizable", "linkToAccountManager.signIn", fallback: "Sign In") - /// JAMS Account - internal static let title = L10n.tr("Localizable", "linkToAccountManager.title", fallback: "JAMS Account") + /// JAMS account + internal static let title = L10n.tr("Localizable", "linkToAccountManager.title", fallback: "JAMS account") } internal enum LogView { /// Open diagnostic log settings @@ -892,14 +892,12 @@ internal enum L10n { internal static let about = L10n.tr("Localizable", "swarm.about", fallback: "About") /// Add description internal static let addDescription = L10n.tr("Localizable", "swarm.addDescription", fallback: "Add description") - /// Add Member - internal static let addMember = L10n.tr("Localizable", "swarm.addMember", fallback: "Add Member") /// Administrator internal static let admin = L10n.tr("Localizable", "swarm.admin", fallback: "Administrator") /// Admin invites only internal static let adminInvitesOnly = L10n.tr("Localizable", "swarm.adminInvitesOnly", fallback: "Admin invites only") - /// Banned - internal static let banned = L10n.tr("Localizable", "swarm.banned", fallback: "Banned") + /// Blocked + internal static let blocked = L10n.tr("Localizable", "swarm.blocked", fallback: "Blocked") /// Change swarm picture internal static let changePicture = L10n.tr("Localizable", "swarm.changePicture", fallback: "Change swarm picture") /// Color @@ -910,14 +908,16 @@ internal enum L10n { internal static let customize = L10n.tr("Localizable", "swarm.customize", fallback: "Customize swarm") /// Customize swarm profile internal static let customizeProfile = L10n.tr("Localizable", "swarm.customizeProfile", fallback: "Customize swarm profile") - /// You can add or invite members at any time after the swarm has been created - internal static let explanationText = L10n.tr("Localizable", "swarm.explanationText", fallback: "You can add or invite members at any time after the swarm has been created") + /// Members can be invited at any time after the swarm has been created + internal static let explanationText = L10n.tr("Localizable", "swarm.explanationText", fallback: "Members can be invited at any time after the swarm has been created") /// Identifier internal static let identifier = L10n.tr("Localizable", "swarm.identifier", fallback: "Identifier") /// Ignore the swarm internal static let ignoreSwarm = L10n.tr("Localizable", "swarm.ignoreSwarm", fallback: "Ignore the swarm") /// Invited internal static let invited = L10n.tr("Localizable", "swarm.invited", fallback: "Invited") + /// Invite members + internal static let inviteMembers = L10n.tr("Localizable", "swarm.inviteMembers", fallback: "Invite members") /// Private swarm internal static let invitesOnly = L10n.tr("Localizable", "swarm.invitesOnly", fallback: "Private swarm") /// Leave @@ -940,8 +940,8 @@ internal enum L10n { internal static let publicChat = L10n.tr("Localizable", "swarm.publicChat", fallback: "Public swarm") /// Select Contacts internal static let selectContacts = L10n.tr("Localizable", "swarm.selectContacts", fallback: "Select Contacts") - /// Type of swarm - internal static let typeOfSwarm = L10n.tr("Localizable", "swarm.typeOfSwarm", fallback: "Type of swarm") + /// Type + internal static let typeOfSwarm = L10n.tr("Localizable", "swarm.typeOfSwarm", fallback: "Type") /// Unknown internal static let unknown = L10n.tr("Localizable", "swarm.unknown", fallback: "Unknown") } @@ -954,8 +954,8 @@ internal enum L10n { internal static let searchBar = L10n.tr("Localizable", "swarmcreation.searchBar", fallback: "Search for contact…") } internal enum Welcome { - /// Connect to a Jami Account Manager Server - internal static let connectToManager = L10n.tr("Localizable", "welcome.connectToManager", fallback: "Connect to a Jami Account Manager Server") + /// Connect to Jami Account Management Server (JAMS) + internal static let connectToJAMS = L10n.tr("Localizable", "welcome.connectToJAMS", fallback: "Connect to Jami Account Management Server (JAMS)") /// Join internal static let createAccount = L10n.tr("Localizable", "welcome.createAccount", fallback: "Join") /// I already have an account diff --git a/Ring/Ring/Coordinators/AppCoordinator.swift b/Ring/Ring/Coordinators/AppCoordinator.swift index 98184ae3c19a8a144b6acaac21410a896f6a6d97..52ca9f9cd6f95f80783d4a03e29a236d28f69f23 100644 --- a/Ring/Ring/Coordinators/AppCoordinator.swift +++ b/Ring/Ring/Coordinators/AppCoordinator.swift @@ -58,7 +58,7 @@ final class AppCoordinator: Coordinator, StateableResponsive { // MARK: - // MARK: StateableResponsive - let disposeBag = DisposeBag() + var disposeBag = DisposeBag() let stateSubject = PublishSubject<State>() // MARK: - diff --git a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift index 4d2e7e3f3e6aa447e4cf31a32aa5b1222d925cee..0be4e47dfa94fca8015e73a09d84c0d1e105f856 100644 --- a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift +++ b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift @@ -40,7 +40,7 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa private var navigationViewController = UINavigationController() let injectionBag: InjectionBag - let disposeBag = DisposeBag() + var disposeBag = DisposeBag() let stateSubject = PublishSubject<State>() let callService: CallsService diff --git a/Ring/Ring/Features/Walkthrough/Models/ConnectSipVM.swift b/Ring/Ring/Features/Walkthrough/Models/ConnectSipVM.swift new file mode 100644 index 0000000000000000000000000000000000000000..39a5a5957b1617ac364c53429a5cf542d76e4069 --- /dev/null +++ b/Ring/Ring/Features/Walkthrough/Models/ConnectSipVM.swift @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Foundation +import RxSwift +import SwiftUI + +class ConnectSipVM: ObservableObject, ViewModel, Dismissable { + @Published var username: String = "" + @Published var password: String = "" + @Published var server: String = "" + @Published var isTextFieldFocused: Bool = true + + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + + var connectAction: ((_ username: String, _ password: String, _ server: String) -> Void)? + + required init(with injectionBag: InjectionBag) { + } + + func connect() { + dismissView() + connectAction?(username, password, server) + } +} diff --git a/Ring/Ring/Features/Walkthrough/Models/ConnectToManagerVM.swift b/Ring/Ring/Features/Walkthrough/Models/ConnectToManagerVM.swift new file mode 100644 index 0000000000000000000000000000000000000000..bf19d50e9efa6945e6f920f812efe1ad0d10f4b7 --- /dev/null +++ b/Ring/Ring/Features/Walkthrough/Models/ConnectToManagerVM.swift @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Foundation +import RxSwift +import SwiftUI + +class ConnectToManagerVM: ObservableObject, ViewModel, Dismissable { + @Published var username: String = "" + @Published var password: String = "" + @Published var server: String = "" + @Published var isTextFieldFocused: Bool = true + + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + + var connectAction: ((_ username: String, _ password: String, _ server: String) -> Void)? + + var isSignInDisabled: Bool { + username.isEmpty || password.isEmpty || server.isEmpty + } + + var signInButtonColor: Color { + return isSignInDisabled ? Color(UIColor.secondaryLabel) : .jamiColor + } + + required init(with injectionBag: InjectionBag) { + } + + func connect() { + if !isSignInDisabled { + dismissView() + connectAction?(username, password, server) + } + } +} diff --git a/Ring/Ring/Features/Walkthrough/Models/CreateAccountViewModel.swift b/Ring/Ring/Features/Walkthrough/Models/CreateAccountVM.swift similarity index 98% rename from Ring/Ring/Features/Walkthrough/Models/CreateAccountViewModel.swift rename to Ring/Ring/Features/Walkthrough/Models/CreateAccountVM.swift index 619b021bdbd92a3c7560097a36d85be1c932c2b7..f26454977084db1afb0201469e952b04d8a377db 100644 --- a/Ring/Ring/Features/Walkthrough/Models/CreateAccountViewModel.swift +++ b/Ring/Ring/Features/Walkthrough/Models/CreateAccountVM.swift @@ -182,11 +182,14 @@ extension AccountCreationError: LocalizedError { } } -class CreateAccountViewModel: ObservableObject, ViewModel { +class CreateAccountVM: ObservableObject, ViewModel, Dismissable { @Published var isJoinButtonDisabled = false @Published var usernameValidationState: UsernameValidationState = .unknown @Published var username: String = "" + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + private let disposeBag = DisposeBag() private let nameService: NameService diff --git a/Ring/Ring/Features/Walkthrough/Models/ImportFromArchiveVM.swift b/Ring/Ring/Features/Walkthrough/Models/ImportFromArchiveVM.swift new file mode 100644 index 0000000000000000000000000000000000000000..7a97bdeb749731edda10b89882f8f3da4ce71cac --- /dev/null +++ b/Ring/Ring/Features/Walkthrough/Models/ImportFromArchiveVM.swift @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Foundation +import RxSwift +import SwiftUI + +class ImportFromArchiveVM: ObservableObject, ViewModel, Dismissable { + @Published var password: String = "" + @Published var selectedFileURL: URL? + @Published var pickerPresented: Bool = false + + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + + var importAction: ((_ url: URL, _ password: String) -> Void)? + + var isImportButtonDisabled: Bool { + return selectedFileURL == nil + } + + var importButtonColor: Color { + return isImportButtonDisabled ? Color(UIColor.secondaryLabel) : .jamiColor + } + + var selectedFileText: String { + return selectedFileURL?.lastPathComponent ?? L10n.ImportFromArchive.selectArchiveButton + } + + required init(with injectionBag: InjectionBag) { + } + + func importAccount() { + if let selectedFileURL = selectedFileURL { + dismissView() + importAction?(selectedFileURL, password) + } + } + + func selectFile() { + withAnimation { + pickerPresented = true + } + } +} diff --git a/Ring/Ring/Features/Walkthrough/Models/LinkToAccountVM.swift b/Ring/Ring/Features/Walkthrough/Models/LinkToAccountVM.swift new file mode 100644 index 0000000000000000000000000000000000000000..07973bf99e4ccfd504660f80f72ac72fd997e1b4 --- /dev/null +++ b/Ring/Ring/Features/Walkthrough/Models/LinkToAccountVM.swift @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Foundation +import RxSwift +import SwiftUI + +class LinkToAccountVM: ObservableObject, ViewModel, Dismissable { + @Published var pin: String = "" + @Published var password: String = "" + @Published var scannedCode: String? + @Published var animatableScanSwitch: Bool = true + @Published var notAnimatableScanSwitch: Bool = true + + // MARK: - Rx Dismissable + var dismiss = PublishSubject<Bool>() + + var linkAction: ((_ pin: String, _ password: String) -> Void)? + + var isLinkButtonEnabled: Bool { + return !pin.isEmpty + } + + var linkButtonColor: Color { + return pin.isEmpty ? Color(UIColor.secondaryLabel) : .jamiColor + } + + required init(with injectionBag: InjectionBag) { + } + + func link() { + dismissView() + linkAction?(pin, password) + } + + func switchToQRCode() { + notAnimatableScanSwitch = true + withAnimation { + animatableScanSwitch = true + } + } + + func switchToManualEntry() { + notAnimatableScanSwitch = false + withAnimation { + animatableScanSwitch = false + } + } + + func didScanQRCode(_ code: String) { + self.pin = code + self.scannedCode = code + } +} diff --git a/Ring/Ring/Features/Walkthrough/Models/WelcomeViewModel.swift b/Ring/Ring/Features/Walkthrough/Models/WelcomeVM.swift similarity index 83% rename from Ring/Ring/Features/Walkthrough/Models/WelcomeViewModel.swift rename to Ring/Ring/Features/Walkthrough/Models/WelcomeVM.swift index 679e3f924c42ba61a0a80e8f6820525857097be4..85ba4fc3bf4f16af310bb28c5bd61996ab325c83 100644 --- a/Ring/Ring/Features/Walkthrough/Models/WelcomeViewModel.swift +++ b/Ring/Ring/Features/Walkthrough/Models/WelcomeVM.swift @@ -23,9 +23,10 @@ import Foundation import RxSwift import SwiftUI -class WelcomeViewModel: Stateable, ViewModel, ObservableObject { +class WelcomeVM: Stateable, ViewModel, ObservableObject { @Published var creationState: AccountCreationState = .initial + @Published var notCancelable = true // MARK: - Rx Stateable private let stateSubject = PublishSubject<State>() @@ -40,8 +41,6 @@ class WelcomeViewModel: Stateable, ViewModel, ObservableObject { let disposeBag = DisposeBag() - var notCancelable = true - let registrationTimeout: CGFloat = 30 var profileName: String = "" @@ -58,6 +57,50 @@ class WelcomeViewModel: Stateable, ViewModel, ObservableObject { self.stateSubject.onNext(WalkthroughState.completed) } + func openAccountCreation() { + self.stateSubject.onNext(WalkthroughState.accountCreation(createAction: { [weak self] (name, password, profileName, profileImage) in + guard let self = self else { return } + self.setProfileInfo(profileName: profileName, profileImage: profileImage) + self.createAccount(name: name, password: password) + })) + } + + func openLinkDevice() { + self.stateSubject.onNext(WalkthroughState.linkDevice(linkAction: { [weak self] pin, password in + guard let self = self else { return } + self.linkDevice(pin: pin, password: password) + })) + } + + func openImportArchive() { + self.stateSubject.onNext(WalkthroughState.importArchive(importAction: { [weak self] url, password in + guard let self = self else { return } + self.importFromArchive(path: url, password: password) + })) + } + + func openJAMS() { + self.stateSubject.onNext(WalkthroughState.connectJAMS(connectAction: { [weak self] username, password, server in + guard let self = self else { return } + self.connectToAccountManager(userName: username, + password: password, + server: server) + })) + } + + func openAboutJami() { + self.stateSubject.onNext(WalkthroughState.aboutJami) + } + + func openSIP() { + self.stateSubject.onNext(WalkthroughState.connectSIP(connectAction: { [weak self] username, password, server in + guard let self = self else { return } + self.createSipAccount(userName: username, + password: password, + server: server) + })) + } + func setProfileInfo(profileName: String, profileImage: UIImage?) { self.profileName = profileName self.profileImage = profileImage @@ -65,7 +108,7 @@ class WelcomeViewModel: Stateable, ViewModel, ObservableObject { } // MARK: - Create account -extension WelcomeViewModel { +extension WelcomeVM { func createAccount(name: String, password: String) { self.creationState = .started @@ -193,7 +236,7 @@ extension WelcomeViewModel { } // MARK: - link account -extension WelcomeViewModel { +extension WelcomeVM { func linkDevice(pin: String, password: String) { self.creationState = .started self.accountService @@ -214,7 +257,7 @@ extension WelcomeViewModel { } // MARK: - import account -extension WelcomeViewModel { +extension WelcomeVM { func importFromArchive(path: URL, password: String) { guard path.startAccessingSecurityScopedResource() else { self.setState(state: .error(error: .unknown)) @@ -262,7 +305,7 @@ extension WelcomeViewModel { } // MARK: - connect to account manager -extension WelcomeViewModel { +extension WelcomeVM { func connectToAccountManager(userName: String, password: String, server: String) { @@ -283,7 +326,7 @@ extension WelcomeViewModel { } // MARK: - configure SIP account -extension WelcomeViewModel { +extension WelcomeVM { func createSipAccount(userName: String, password: String, server: String) { diff --git a/Ring/Ring/Features/Walkthrough/Views/CreateAccountView.swift b/Ring/Ring/Features/Walkthrough/Views/CreateAccountView.swift index 8950399904ad1e8eb7d676551de5a0141d24a9a3..27240dac791e6436719b69ded6ba0e130909b108 100644 --- a/Ring/Ring/Features/Walkthrough/Views/CreateAccountView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/CreateAccountView.swift @@ -19,8 +19,7 @@ import SwiftUI struct CreateAccountView: View { - @StateObject var model: CreateAccountViewModel - let dismissAction: () -> Void + @ObservedObject var viewModel: CreateAccountVM let createAction: (String, String, String, UIImage?) -> Void @SwiftUI.State private var isTextFieldFocused = true @@ -34,11 +33,9 @@ struct CreateAccountView: View { @SwiftUI.State private var profileName: String = "" init(injectionBag: InjectionBag, - dismissAction: @escaping () -> Void, createAction: @escaping (String, String, String, UIImage?) -> Void) { - _model = StateObject(wrappedValue: - CreateAccountViewModel(with: injectionBag)) - self.dismissAction = dismissAction + _viewModel = ObservedObject(wrappedValue: + CreateAccountVM(with: injectionBag)) self.createAction = createAction } @@ -83,7 +80,7 @@ struct CreateAccountView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) .onChange(of: name) { newValue in - model.usernameUpdated(to: newValue) + viewModel.usernameUpdated(to: newValue) } } @@ -167,13 +164,13 @@ struct CreateAccountView: View { } @ViewBuilder private var footerView: some View { - if model.usernameValidationState.message.isEmpty { + if viewModel.usernameValidationState.message.isEmpty { Text("valid name") .foregroundColor(.clear) .font(.footnote) } else { - Text(model.usernameValidationState.message) - .foregroundColor(Color(model.usernameValidationState.textColor)) + Text(viewModel.usernameValidationState.message) + .foregroundColor(Color(viewModel.usernameValidationState.textColor)) .font(.footnote) .accessibilityIdentifier(AccessibilityIdentifiers.createAccountErrorLabel) } @@ -181,7 +178,7 @@ struct CreateAccountView: View { private var cancelButton: some View { Button(action: { - dismissAction() + viewModel.dismissView() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color(UIColor.label)) @@ -191,14 +188,15 @@ struct CreateAccountView: View { private var createButton: some View { Button(action: { + viewModel.dismissView() createAction(name, password, profileName, profileImage) }, label: { Text(L10n.Global.create) - .foregroundColor(model.isJoinButtonDisabled ? + .foregroundColor(viewModel.isJoinButtonDisabled ? Color(UIColor.secondaryLabel) : .jamiColor) }) - .disabled(model.isJoinButtonDisabled) + .disabled(viewModel.isJoinButtonDisabled) .accessibilityIdentifier(AccessibilityIdentifiers.joinButton) } diff --git a/Ring/Ring/Features/Walkthrough/Views/ImportFromArchiveView.swift b/Ring/Ring/Features/Walkthrough/Views/ImportFromArchiveView.swift index 9380dd296dbc83354684da374d6ec3f7589da55c..b42032ae69091b47aa4e29ee6ffb34abaedab4cc 100644 --- a/Ring/Ring/Features/Walkthrough/Views/ImportFromArchiveView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/ImportFromArchiveView.swift @@ -19,21 +19,13 @@ import SwiftUI struct ImportFromArchiveView: View { - @StateObject var model: CreateAccountViewModel - let dismissAction: () -> Void - let createAction: (URL, String) -> Void - - @SwiftUI.State private var password = "" - @SwiftUI.State private var selectedFileURL: URL? - @SwiftUI.State private var pickerPresented = false + @ObservedObject var viewModel: ImportFromArchiveVM init(injectionBag: InjectionBag, - dismissAction: @escaping () -> Void, - createAction: @escaping (URL, String) -> Void) { - _model = StateObject(wrappedValue: - CreateAccountViewModel(with: injectionBag)) - self.dismissAction = dismissAction - self.createAction = createAction + importAction: @escaping (_ url: URL, _ password: String) -> Void) { + _viewModel = ObservedObject(wrappedValue: + ImportFromArchiveVM(with: injectionBag)) + viewModel.importAction = importAction } var body: some View { @@ -47,14 +39,14 @@ struct ImportFromArchiveView: View { .padding(.vertical) Text(L10n.ImportFromArchive.passwordExplanation) .multilineTextAlignment(.center) - WalkthroughPasswordView(text: $password, + WalkthroughPasswordView(text: $viewModel.password, placeholder: L10n.Global.password) .padding(.bottom) } .frame(maxWidth: 500) } - .sheet(isPresented: $pickerPresented) { - DocumentPicker(fileURL: $selectedFileURL, type: .item) + .sheet(isPresented: $viewModel.pickerPresented) { + DocumentPicker(fileURL: $viewModel.selectedFileURL, type: .item) } .padding(.horizontal) } @@ -77,21 +69,17 @@ struct ImportFromArchiveView: View { private var importButton: some View { Button(action: { - if let selectedFileURL = selectedFileURL { - createAction(selectedFileURL, password) - } + viewModel.importAccount() }, label: { Text(L10n.ImportFromArchive.buttonTitle) - .foregroundColor(selectedFileURL == nil ? - Color(UIColor.secondaryLabel) : - .jamiColor) + .foregroundColor(viewModel.importButtonColor) }) - .disabled(selectedFileURL == nil) + .disabled(viewModel.isImportButtonDisabled) } private var cancelButton: some View { Button(action: { - dismissAction() + viewModel.dismissView() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color(UIColor.label)) @@ -100,11 +88,9 @@ struct ImportFromArchiveView: View { private var selectFileButton: some View { Button(action: { - withAnimation { - pickerPresented = true - } + viewModel.selectFile() }, label: { - Text(selectedFileURL?.lastPathComponent ?? L10n.ImportFromArchive.selectArchiveButton) + Text(viewModel.selectedFileText) .foregroundColor(Color(UIColor.label)) .padding(.horizontal, 12) .padding(.vertical, 12) diff --git a/Ring/Ring/Features/Walkthrough/Views/JamsConnectView.swift b/Ring/Ring/Features/Walkthrough/Views/JamsConnectView.swift index 696a2bc2031caccb74dd6bc431c26ff83a449877..b6dfd2d5943ca60c45035247c3f68aabd154109a 100644 --- a/Ring/Ring/Features/Walkthrough/Views/JamsConnectView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/JamsConnectView.swift @@ -19,14 +19,14 @@ import SwiftUI struct JamsConnectView: View { - let dismissAction: () -> Void - let connectAction: (_ username: String, - _ password: String, - _ server: String) -> Void - @SwiftUI.State private var username: String = "" - @SwiftUI.State private var password: String = "" - @SwiftUI.State private var server: String = "" - @SwiftUI.State private var isTextFieldFocused = true + @ObservedObject var viewModel: ConnectToManagerVM + + init(injectionBag: InjectionBag, + connectAction: @escaping (_ username: String, _ password: String, _ server: String) -> Void) { + _viewModel = ObservedObject(wrappedValue: + ConnectToManagerVM(with: injectionBag)) + viewModel.connectAction = connectAction + } var body: some View { VStack { header @@ -64,7 +64,7 @@ struct JamsConnectView: View { private var cancelButton: some View { Button(action: { - dismissAction() + viewModel.dismissView() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color(UIColor.label)) @@ -73,35 +73,29 @@ struct JamsConnectView: View { private var signinButton: some View { Button(action: { - connectAction(username, password, server) + viewModel.connect() }, label: { Text(L10n.LinkToAccountManager.signIn) - .foregroundColor(signInDisabled ? - Color(UIColor.secondaryLabel) : - .jamiColor) + .foregroundColor(viewModel.signInButtonColor) }) - .disabled(signInDisabled) + .disabled(viewModel.isSignInDisabled) } private var usernameView: some View { - WalkthroughTextEditView(text: $username, + WalkthroughTextEditView(text: $viewModel.username, placeholder: L10n.Global.username) } private var serverView: some View { let placeholder = L10n.LinkToAccountManager.accountManagerPlaceholder - return WalkthroughFocusableTextView(text: $server, - isTextFieldFocused: $isTextFieldFocused, + return WalkthroughFocusableTextView(text: $viewModel.server, + isTextFieldFocused: $viewModel.isTextFieldFocused, placeholder: placeholder) } private var passwordView: some View { - WalkthroughPasswordView(text: $password, + WalkthroughPasswordView(text: $viewModel.password, placeholder: L10n.Global.password) .padding(.bottom) } - - private var signInDisabled: Bool { - username.isEmpty || password.isEmpty || server.isEmpty - } } diff --git a/Ring/Ring/Features/Walkthrough/Views/LinkToAccountView.swift b/Ring/Ring/Features/Walkthrough/Views/LinkToAccountView.swift index 52fd0ed28022caa2f994d9fefcd22a94aea5fb45..4ccdb6e525d7b825a081bcda4e23fc8b088b0c88 100644 --- a/Ring/Ring/Features/Walkthrough/Views/LinkToAccountView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/LinkToAccountView.swift @@ -19,16 +19,16 @@ import SwiftUI struct LinkToAccountView: View { - let dismissAction: () -> Void - let linkAction: (_ pin: String, _ password: String) -> Void - @SwiftUI.State private var password: String = "" - @SwiftUI.State private var pin: String = "" - @SwiftUI.State private var scannedCode: String? - @SwiftUI.State private var animatableScanSwitch: Bool = true - @SwiftUI.State private var notAnimatableScanSwitch: Bool = true - + @ObservedObject var viewModel: LinkToAccountVM @Environment(\.verticalSizeClass) var verticalSizeClass + init(injectionBag: InjectionBag, + linkAction: @escaping (_ pin: String, _ password: String) -> Void) { + _viewModel = ObservedObject(wrappedValue: + LinkToAccountVM(with: injectionBag)) + viewModel.linkAction = linkAction + } + var body: some View { VStack { header @@ -85,7 +85,7 @@ struct LinkToAccountView: View { .padding(.horizontal) HStack { Text(L10n.LinkToAccount.pinPlaceholder + ":") - Text(pin) + Text(viewModel.pin) .foregroundColor(Color(UIColor.jamiSuccess)) } .padding() @@ -94,16 +94,15 @@ struct LinkToAccountView: View { private var scanQRCodeView: some View { ScanQRCodeView(width: 350, height: 280) { pin in - self.pin = pin - self.scannedCode = pin + viewModel.didScanQRCode(pin) } } private var pinSection: some View { VStack(spacing: 15) { - if scannedCode == nil { + if viewModel.scannedCode == nil { pinSwitchButtons - if animatableScanSwitch { + if viewModel.animatableScanSwitch { scanQRCodeView } else { manualEntryPinView @@ -118,27 +117,21 @@ struct LinkToAccountView: View { private var pinSwitchButtons: some View { HStack { switchButton(text: L10n.LinkToAccount.scanQRCode, - isHeadline: notAnimatableScanSwitch, - isHighlighted: animatableScanSwitch, + isHeadline: viewModel.notAnimatableScanSwitch, + isHighlighted: viewModel.animatableScanSwitch, transitionEdge: .trailing, action: { - notAnimatableScanSwitch = true - withAnimation { - animatableScanSwitch = true - } + viewModel.switchToQRCode() }) Spacer() switchButton(text: L10n.LinkToAccount.pinLabel, - isHeadline: !notAnimatableScanSwitch, - isHighlighted: !animatableScanSwitch, + isHeadline: !viewModel.notAnimatableScanSwitch, + isHighlighted: !viewModel.animatableScanSwitch, transitionEdge: .leading, action: { - notAnimatableScanSwitch = false - withAnimation { - animatableScanSwitch = false - } + viewModel.switchToManualEntry() }) } } @@ -171,7 +164,7 @@ struct LinkToAccountView: View { } private var manualEntryPinView: some View { - WalkthroughTextEditView(text: $pin, + WalkthroughTextEditView(text: $viewModel.pin, placeholder: L10n.LinkToAccount.pinLabel) } @@ -179,14 +172,14 @@ struct LinkToAccountView: View { VStack { Text(L10n.ImportFromArchive.passwordExplanation) .multilineTextAlignment(.center) - WalkthroughPasswordView(text: $password, placeholder: L10n.Global.password) + WalkthroughPasswordView(text: $viewModel.password, placeholder: L10n.Global.password) } .padding(.vertical) } private var cancelButton: some View { Button(action: { - dismissAction() + viewModel.dismissView() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color(UIColor.label)) @@ -195,13 +188,11 @@ struct LinkToAccountView: View { private var linkButton: some View { Button(action: { - linkAction(pin, password) + viewModel.link() }, label: { Text(L10n.LinkToAccount.linkButtonTitle) - .foregroundColor(pin.isEmpty ? - Color(UIColor.secondaryLabel) : - .jamiColor) + .foregroundColor(viewModel.linkButtonColor) }) - .disabled(pin.isEmpty ) + .disabled(!viewModel.isLinkButtonEnabled) } } diff --git a/Ring/Ring/Features/Walkthrough/Views/SIPConfigurationView.swift b/Ring/Ring/Features/Walkthrough/Views/SIPConfigurationView.swift index cc1cecedbd4e19ed9f3f2f513b10094b72c560a2..42aba19e5e8c1c15e9ecb96ca06c75b27e099598 100644 --- a/Ring/Ring/Features/Walkthrough/Views/SIPConfigurationView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/SIPConfigurationView.swift @@ -19,14 +19,15 @@ import SwiftUI struct SIPConfigurationView: View { - let dismissAction: () -> Void - let connectAction: (_ username: String, - _ password: String, - _ server: String) -> Void - @SwiftUI.State private var username: String = "" - @SwiftUI.State private var password: String = "" - @SwiftUI.State private var server: String = "" - @SwiftUI.State private var isTextFieldFocused = true + @ObservedObject var viewModel: ConnectSipVM + + init(injectionBag: InjectionBag, + connectAction: @escaping (_ username: String, _ password: String, _ server: String) -> Void) { + _viewModel = ObservedObject(wrappedValue: + ConnectSipVM(with: injectionBag)) + viewModel.connectAction = connectAction + } + var body: some View { VStack { header @@ -61,7 +62,7 @@ struct SIPConfigurationView: View { private var cancelButton: some View { Button(action: { - dismissAction() + viewModel.dismissView() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color(UIColor.label)) @@ -70,7 +71,7 @@ struct SIPConfigurationView: View { private var configureButton: some View { Button(action: { - connectAction(username, password, server) + viewModel.connect() }, label: { Text(L10n.Account.configure) .foregroundColor(.jamiColor) @@ -78,18 +79,18 @@ struct SIPConfigurationView: View { } private var usernameView: some View { - WalkthroughTextEditView(text: $username, + WalkthroughTextEditView(text: $viewModel.username, placeholder: L10n.Global.username) } private var serverView: some View { - WalkthroughFocusableTextView(text: $server, - isTextFieldFocused: $isTextFieldFocused, + WalkthroughFocusableTextView(text: $viewModel.server, + isTextFieldFocused: $viewModel.isTextFieldFocused, placeholder: L10n.Account.sipServer) } private var passwordView: some View { - WalkthroughPasswordView(text: $password, + WalkthroughPasswordView(text: $viewModel.password, placeholder: L10n.Global.password) .padding(.bottom) } diff --git a/Ring/Ring/Features/Walkthrough/Views/Views.swift b/Ring/Ring/Features/Walkthrough/Views/Views.swift index 930eff6d9f0498be5f7a366e81850a4eddaa21cf..a4cfd135a669cfa58ba6785e03970d567e6aca74 100644 --- a/Ring/Ring/Features/Walkthrough/Views/Views.swift +++ b/Ring/Ring/Features/Walkthrough/Views/Views.swift @@ -40,16 +40,14 @@ struct WalkthroughTextEditView: View { var identifier: String = "" var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(UIColor.secondarySystemGroupedBackground)) - TextField(placeholder, text: $text) - .padding(.horizontal, 10) - .padding(.vertical, 12) - .autocorrectionDisabled(true) - .autocapitalization(.none) - .accessibilityIdentifier(identifier) - } + TextField(placeholder, text: $text) + .padding(.horizontal, 10) + .padding(.vertical, 12) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .accessibilityIdentifier(identifier) + .background(Color(UIColor.secondarySystemGroupedBackground)) + .cornerRadius(10) } } diff --git a/Ring/Ring/Features/Walkthrough/Views/WelcomeView.swift b/Ring/Ring/Features/Walkthrough/Views/WelcomeView.swift index e56490b5951b516fa588b3991d6604c660bb1e51..5cbeac93896b3bbeb090790bda97a669d4460966 100644 --- a/Ring/Ring/Features/Walkthrough/Views/WelcomeView.swift +++ b/Ring/Ring/Features/Walkthrough/Views/WelcomeView.swift @@ -18,24 +18,15 @@ import SwiftUI -enum ActiveView: Identifiable { - case jamiAccount - case linkDevice - case fromArchive - case jamsAccount - case sipAccount - case aboutJami - - var id: Int { - hashValue - } -} - struct WelcomeView: View { - @ObservedObject var model: WelcomeViewModel + @ObservedObject var viewModel: WelcomeVM @SwiftUI.State var showImportOptions = false @SwiftUI.State var showAdvancedOptions = false - @SwiftUI.State var activeView: ActiveView? + + init(injectionBag: InjectionBag) { + _viewModel = ObservedObject(wrappedValue: + WelcomeVM(with: injectionBag)) + } @Environment(\.verticalSizeClass) var verticalSizeClass @@ -47,11 +38,11 @@ struct WelcomeView: View { if verticalSizeClass == .compact { HorizontalView(showImportOptions: $showImportOptions, showAdvancedOptions: $showAdvancedOptions, - activeView: $activeView) + model: viewModel) } else { PortraitView(showImportOptions: $showImportOptions, showAdvancedOptions: $showAdvancedOptions, - activeView: $activeView) + model: viewModel) } } .padding() @@ -66,66 +57,11 @@ struct WelcomeView: View { .padding() } .applyJamiBackground() - .sheet(item: $activeView) { item in - switch item { - case .jamiAccount: - CreateAccountView(injectionBag: model.injectionBag, - dismissAction: { - activeView = nil - }, createAction: { [weak model] (name, password, profileName, profileImage) in - activeView = nil - guard let model = model else { return } - model.setProfileInfo(profileName: profileName, profileImage: profileImage) - model.createAccount(name: name, password: password) - }) - case .linkDevice: - LinkToAccountView(dismissAction: { - activeView = nil - }, linkAction: {[weak model](pin, password) in - activeView = nil - guard let model = model else { return } - model.linkDevice(pin: pin, password: password) - - }) - case .jamsAccount: - JamsConnectView(dismissAction: { - activeView = nil - }, connectAction: { [weak model] username, password, server in - activeView = nil - guard let model = model else { return } - model.connectToAccountManager(userName: username, - password: password, - server: server) - }) - case .sipAccount: - SIPConfigurationView(dismissAction: { - activeView = nil - }, connectAction: {[weak model] username, password, server in - activeView = nil - guard let model = model else { return } - model.createSipAccount(userName: username, - password: password, - server: server) - }) - case .aboutJami: - AboutSwiftUIView() - case .fromArchive: - ImportFromArchiveView(injectionBag: model.injectionBag, - dismissAction: { - activeView = nil - }, createAction: {[weak model] url, password in - activeView = nil - guard let model = model else { return } - model.importFromArchive(path: url, password: password) - }) - - } - } } @ViewBuilder func alertView() -> some View { - switch model.creationState { + switch viewModel.creationState { case .initial, .unknown, .success: EmptyView() case .started: @@ -144,9 +80,9 @@ struct WelcomeView: View { CustomAlert(content: { AlertFactory .alertWithOkButton(title: error.title, message: error.message, - action: { [weak model] in - guard let model = model else { return } - model.creationState = .initial + action: { [weak viewModel] in + guard let viewModel = viewModel else { return } + viewModel.creationState = .initial }) }) } @@ -158,9 +94,9 @@ struct WelcomeView: View { CustomAlert(content: { AlertFactory .alertWithOkButton(title: title, message: message, - action: {[weak model] in - guard let model = model else { return } - model.finish() + action: {[weak viewModel] in + guard let viewModel = viewModel else { return } + viewModel.finish() }) }) } @@ -172,9 +108,9 @@ struct WelcomeView: View { CustomAlert(content: { AlertFactory .alertWithOkButton(title: title, message: message, - action: {[weak model] in - guard let model = model else { return } - model.finish() + action: {[weak viewModel] in + guard let viewModel = viewModel else { return } + viewModel.finish() }) }) } @@ -186,12 +122,12 @@ struct WelcomeView: View { @ViewBuilder func cancelButton() -> some View { - if model.notCancelable { + if viewModel.notCancelable { EmptyView() } else { - Button(action: { [weak model] in - guard let model = model else { return } - model.finish() + Button(action: { [weak viewModel] in + guard let viewModel = viewModel else { return } + viewModel.finish() }, label: { Text(L10n.Global.cancel) .foregroundColor(Color.jamiColor) @@ -202,14 +138,14 @@ struct WelcomeView: View { struct HorizontalView: View { @Binding var showImportOptions: Bool @Binding var showAdvancedOptions: Bool - @Binding var activeView: ActiveView? + var model: WelcomeVM @SwiftUI.State private var height: CGFloat = 1 var body: some View { HStack(spacing: 30) { VStack { Spacer() HeaderView() - AboutButton(activeView: $activeView) + AboutButton(model: model) Spacer() } VStack { @@ -217,7 +153,7 @@ struct HorizontalView: View { ScrollView(showsIndicators: false) { ButtonsView(showImportOptions: $showImportOptions, showAdvancedOptions: $showAdvancedOptions, - activeView: $activeView) + model: model) .background( GeometryReader { proxy in Color.clear @@ -240,7 +176,7 @@ struct HorizontalView: View { struct PortraitView: View { @Binding var showImportOptions: Bool @Binding var showAdvancedOptions: Bool - @Binding var activeView: ActiveView? + var model: WelcomeVM var body: some View { VStack { Spacer(minLength: 80) @@ -248,9 +184,9 @@ struct PortraitView: View { ScrollView(showsIndicators: false) { ButtonsView(showImportOptions: $showImportOptions, showAdvancedOptions: $showAdvancedOptions, - activeView: $activeView) + model: model) } - AboutButton(activeView: $activeView) + AboutButton(model: model) } } } @@ -275,15 +211,13 @@ struct HeaderView: View { struct ButtonsView: View { @Binding var showImportOptions: Bool @Binding var showAdvancedOptions: Bool - @Binding var activeView: ActiveView? + var model: WelcomeVM var body: some View { VStack(spacing: 12) { button(L10n.CreateAccount.createAccountFormTitle, action: { - withAnimation { - activeView = .jamiAccount - } + model.openAccountCreation() }) .accessibilityIdentifier(AccessibilityIdentifiers.joinJamiButton) @@ -295,14 +229,10 @@ struct ButtonsView: View { if showImportOptions { expandedbutton(L10n.Welcome.linkDevice, action: { - withAnimation { - activeView = .linkDevice - } + model.openLinkDevice() }) expandedbutton(L10n.Welcome.linkBackup, action: { - withAnimation { - activeView = .fromArchive - } + model.openImportArchive() }) } @@ -314,14 +244,10 @@ struct ButtonsView: View { if showAdvancedOptions { expandedbutton(L10n.Welcome.connectToJAMS, action: { - withAnimation { - activeView = .jamsAccount - } + model.openJAMS() }) expandedbutton(L10n.Account.createSipAccount, action: { - withAnimation { - activeView = .sipAccount - } + model.openSIP() }) } } @@ -370,12 +296,10 @@ struct ButtonsView: View { } struct AboutButton: View { - @Binding var activeView: ActiveView? + var model: WelcomeVM var body: some View { Button(action: { - withAnimation { - activeView = .aboutJami - } + model.openAboutJami() }, label: { Text(L10n.Smartlist.aboutJami) .padding(12) diff --git a/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift b/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift index f0609fa99a1e76a2a7186bef24a5673fa4d48e72..a730f5996b420b1dc628292cc4b0d430535986c0 100644 --- a/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift +++ b/Ring/Ring/Features/Walkthrough/WalkthroughCoordinator.swift @@ -21,25 +21,22 @@ import Foundation import RxSwift - -/// Represents the choice made by the user in the Walkthrough for the creation account type -/// -/// - createAccount: create an account from scratch (profile / username / password) -/// - linkDevice: link the device to an existing account (profile / pin / password) -public enum WalkthroughType { - case createAccount - case linkDevice - case createSipAccount - case linkToAccountManager -} +import SwiftUI /// Represents walkthrough navigation state public enum WalkthroughState: State { + case accountCreation(createAction: (String, String, String, UIImage?) -> Void) + case linkDevice(linkAction: (_ pin: String, _ password: String) -> Void) + case importArchive(importAction: (_ url: URL, _ password: String) -> Void) + case connectJAMS(connectAction: (_ username: String, _ password: String, _ server: String) -> Void) + case connectSIP(connectAction: (_ username: String, _ password: String, _ server: String) -> Void) + case aboutJami case completed } /// This Coordinator drives the walkthrough navigation (welcome / profile / creation or link) class WalkthroughCoordinator: Coordinator, StateableResponsive { + var presentingVC = [String: Bool]() var rootViewController: UIViewController { return self.navigationViewController @@ -52,7 +49,7 @@ class WalkthroughCoordinator: Coordinator, StateableResponsive { private let navigationViewController = UINavigationController() private let injectionBag: InjectionBag - let disposeBag = DisposeBag() + var disposeBag = DisposeBag() let stateSubject = PublishSubject<State>() @@ -64,17 +61,69 @@ class WalkthroughCoordinator: Coordinator, StateableResponsive { guard let self = self, let state = state as? WalkthroughState else { return } switch state { case .completed: - self.navigationViewController.setViewControllers([], animated: false) - self.rootViewController.dismiss(animated: true) + finish() + case .accountCreation(let createAction): + showAccountCreation(createAction: createAction) + case .linkDevice(linkAction: let linkAction): + showLinkDevice(linkAction: linkAction) + case .importArchive(importAction: let importAction): + showImportArchive(importAction: importAction) + case .connectJAMS(connectAction: let connectAction): + showConnectJAMS(connectAction: connectAction) + case .connectSIP(connectAction: let connectAction): + showConnectSIP(connectAction: connectAction) + case .aboutJami: + showAboutJami() } }) .disposed(by: self.disposeBag) + } + + func showAccountCreation(createAction: @escaping (String, String, String, UIImage?) -> Void) { + let accountView = CreateAccountView(injectionBag: self.injectionBag, createAction: createAction) + let viewController = createDismissableVC(accountView, dismissible: accountView.viewModel) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + + func showLinkDevice(linkAction: @escaping (_ pin: String, _ password: String) -> Void) { + let accountView = LinkToAccountView(injectionBag: self.injectionBag, linkAction: linkAction) + let viewController = createDismissableVC(accountView, dismissible: accountView.viewModel) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + + func showImportArchive(importAction: @escaping (_ url: URL, _ password: String) -> Void) { + let accountView = ImportFromArchiveView(injectionBag: self.injectionBag, importAction: importAction) + let viewController = createDismissableVC(accountView, dismissible: accountView.viewModel) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + + func showConnectJAMS(connectAction: @escaping (_ username: String, _ password: String, _ server: String) -> Void) { + let accountView = JamsConnectView(injectionBag: self.injectionBag, connectAction: connectAction) + let viewController = createDismissableVC(accountView, dismissible: accountView.viewModel) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + + func showConnectSIP(connectAction: @escaping (_ username: String, _ password: String, _ server: String) -> Void) { + let accountView = SIPConfigurationView(injectionBag: self.injectionBag, connectAction: connectAction) + let viewController = createDismissableVC(accountView, dismissible: accountView.viewModel) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + + func showAboutJami() { + let aboutView = AboutSwiftUIView() + let viewController = createDismissableVC(aboutView, dismissible: aboutView.model) + self.present(viewController: viewController, withStyle: .formModal, withAnimation: true, disposeBag: self.disposeBag) + } + func start() { + let welcomeView = WelcomeView(injectionBag: self.injectionBag) + let viewController = createHostingVC(welcomeView) + welcomeView.viewModel.notCancelable = isAccountFirst + self.present(viewController: viewController, withStyle: .show, withAnimation: true, withStateable: welcomeView.viewModel) } - func start () { - let presentedController = WelcomeViewController.instantiate(with: self.injectionBag) - presentedController.viewModel.notCancelable = isAccountFirst - self.present(viewController: presentedController, withStyle: .show, withAnimation: false, withStateable: presentedController.viewModel) + func finish() { + self.navigationViewController.setViewControllers([], animated: false) + self.rootViewController.dismiss(animated: true) } } diff --git a/Ring/Ring/Features/Walkthrough/WelcomeViewController.storyboard b/Ring/Ring/Features/Walkthrough/WelcomeViewController.storyboard deleted file mode 100644 index 803269616dbfd375da957fb4d7de032d964f2a04..0000000000000000000000000000000000000000 --- a/Ring/Ring/Features/Walkthrough/WelcomeViewController.storyboard +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="a7M-11-Me2"> - <device id="retina5_9" orientation="portrait" appearance="light"/> - <dependencies> - <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/> - </dependencies> - <scenes> - <!--Welcome View Controller--> - <scene sceneID="Rk0-Pb-xfs"> - <objects> - <viewController extendedLayoutIncludesOpaqueBars="YES" id="a7M-11-Me2" customClass="WelcomeViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController"/> - <placeholder placeholderIdentifier="IBFirstResponder" id="c0E-9z-Cxj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> - </objects> - <point key="canvasLocation" x="536.79999999999995" y="28.817733990147783"/> - </scene> - </scenes> -</document> diff --git a/Ring/Ring/Features/Walkthrough/WelcomeViewController.swift b/Ring/Ring/Features/Walkthrough/WelcomeViewController.swift deleted file mode 100644 index 04945b384074cfd9d8f1bcb5f594709852503a54..0000000000000000000000000000000000000000 --- a/Ring/Ring/Features/Walkthrough/WelcomeViewController.swift +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2017-2023 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Quentin Muret <quentin.muret@savoirfairelinux.com> - * Author: Alireza Toghiani Khorasgani alireza.toghiani@savoirfairelinux.com * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -import UIKit -import Reusable -import SwiftUI - -class WelcomeViewController: UIViewController, StoryboardBased, ViewModelBased { - var viewModel: WelcomeViewModel! - - override func viewDidLoad() { - super.viewDidLoad() - self.navigationController?.setNavigationBarHidden(true, animated: false) - addSwiftUI() - } - - func addSwiftUI() { - let welcomeView = WelcomeView(model: self.viewModel) - let contentView = UIHostingController(rootView: welcomeView) - addChild(contentView) - view.addSubview(contentView.view) - contentView.view.frame = self.view.bounds - contentView.view.translatesAutoresizingMaskIntoConstraints = false - contentView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - contentView.didMove(toParent: self) - } -} diff --git a/Ring/Ring/Protocols/Coordinator.swift b/Ring/Ring/Protocols/Coordinator.swift index b6ce473d8c9f0449e9027f952219597f93b2e6ff..08cec08fec14647933a46becd0b4e5c8dabcccfa 100644 --- a/Ring/Ring/Protocols/Coordinator.swift +++ b/Ring/Ring/Protocols/Coordinator.swift @@ -21,6 +21,7 @@ import Foundation import UIKit import RxSwift +import SwiftUI /** Represents how a UIViewController should be displayed @@ -54,6 +55,8 @@ protocol Coordinator: AnyObject { /// this property is added to prevent controller to be presenting multiple times, caused by UI lag var presentingVC: [String: Bool] { get set } + var disposeBag: DisposeBag { get set } + /// Initializes a new Coordinator with a dependancy injection bag /// /// - Parameter injectionBag: The injection Bag that will be passed to every sub components that need it @@ -139,4 +142,37 @@ extension Coordinator { .disposed(by: disposeBag) } } + + func createHostingVC<Content: View>( + _ view: Content) -> UIViewController { + let viewController = UIHostingController(rootView: view) + return viewController + } + + func createDismissableVC<Content: View>( + _ view: Content, + dismissible: Dismissable + ) -> UIViewController { + let viewController = UIHostingController(rootView: view) + + dismissible + .dismiss + .take(1) + .subscribe(onNext: { [weak self] shouldDismiss in + if shouldDismiss { + self?.dismiss(viewController: viewController, animated: true) + } + }) + .disposed(by: self.disposeBag) + return viewController + } + + private func dismiss(viewController: UIViewController, animated: Bool) { + if let navigationController = viewController.navigationController { + navigationController.popViewController(animated: animated) + } else { + viewController.dismiss(animated: animated, completion: nil) + } + } + } diff --git a/Ring/Ring/Protocols/ViewModelBased.swift b/Ring/Ring/Protocols/ViewModelBased.swift index aa5d7b6ea166777d400c05ec79588a17f3744091..f8930a5fdfb24e34e650c906f632f81dcde01cdc 100644 --- a/Ring/Ring/Protocols/ViewModelBased.swift +++ b/Ring/Ring/Protocols/ViewModelBased.swift @@ -21,6 +21,7 @@ import Foundation import Reusable import UIKit +import RxSwift /// We assume that every application ViewModel should be aware of the injection bag /// it allows the factorize a ViewModelBased UIViewController instantiation @@ -32,6 +33,19 @@ protocol ViewModel: AnyObject { init(with injectionBag: InjectionBag) } +protocol Dismissable: AnyObject { + + var dismiss: PublishSubject<Bool> { get set } + + func dismissView() +} + +extension Dismissable { + func dismissView() { + dismiss.onNext(true) + } +} + protocol ViewModelBased: AnyObject { associatedtype VMType: ViewModel