From 58c92ed004b01937ef6c233a6243c1db57669320 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:48:51 +0400 Subject: [PATCH] ios: rework existing users notice, condition views (#5214) --- apps/ios/Shared/ContentView.swift | 26 +++-- .../Onboarding/ChooseServerOperators.swift | 68 ++++++++++++-- .../Shared/Views/Onboarding/HowItWorks.swift | 1 + .../Views/Onboarding/WhatsNewView.swift | 94 +++++++++++-------- .../NetworkAndServers/NetworkAndServers.swift | 35 ++++--- .../Views/UserSettings/SettingsView.swift | 2 +- 6 files changed, 159 insertions(+), 67 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 62de4bc1c6..ac699d4a2c 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -10,11 +10,13 @@ import Intents import SimpleXChat private enum NoticesSheet: Identifiable { - case notices(showWhatsNew: Bool, showOperatorsNotice: Bool) + case whatsNew(updatedConditions: Bool) + case updatedConditions var id: String { switch self { - case .notices: return "notices" + case .whatsNew: return "whatsNew" + case .updatedConditions: return "updatedConditions" } } } @@ -274,10 +276,12 @@ struct ContentView: View { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if !noticesShown { let showWhatsNew = shouldShowWhatsNew() - let showOperatorsNotice = chatModel.conditions.conditionsAction?.showNotice ?? false - noticesShown = showWhatsNew || showOperatorsNotice - if noticesShown { - noticesSheetItem = .notices(showWhatsNew: showWhatsNew, showOperatorsNotice: showOperatorsNotice) + let showUpdatedConditions = chatModel.conditions.conditionsAction?.showNotice ?? false + noticesShown = showWhatsNew || showUpdatedConditions + if showWhatsNew { + noticesSheetItem = .whatsNew(updatedConditions: showUpdatedConditions) + } else if showUpdatedConditions { + noticesSheetItem = .updatedConditions } } } @@ -288,8 +292,14 @@ struct ContentView: View { .onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() } .sheet(item: $noticesSheetItem) { item in switch item { - case let .notices(showWhatsNew, showOperatorsNotice): - WhatsNewView(showWhatsNew: showWhatsNew, showOperatorsNotice: showOperatorsNotice) + case let .whatsNew(updatedConditions): + WhatsNewView(updatedConditions: updatedConditions) + case .updatedConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) } } if chatModel.setDeliveryReceipts { diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 4b886ad9be..09e0060c22 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -41,15 +41,27 @@ struct OnboardingButtonStyle: ButtonStyle { } } +private enum ChooseServerOperatorsSheet: Identifiable { + case showInfo + case showConditions + + var id: String { + switch self { + case .showInfo: return "showInfo" + case .showConditions: return "showConditions" + } + } +} + struct ChooseServerOperators: View { @Environment(\.dismiss) var dismiss: DismissAction @Environment(\.colorScheme) var colorScheme: ColorScheme @EnvironmentObject var theme: AppTheme var onboarding: Bool - @State private var showInfoSheet = false @State private var serverOperators: [ServerOperator] = [] @State private var selectedOperatorIds = Set() @State private var reviewConditionsNavLinkActive = false + @State private var sheetItem: ChooseServerOperatorsSheet? = nil @State private var justOpened = true var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } } @@ -74,25 +86,34 @@ struct ChooseServerOperators: View { .font(.footnote) .multilineTextAlignment(.center) .frame(maxWidth: .infinity, alignment: .center) -// .padding(.horizontal, 32) + .padding(.horizontal, 32) Spacer() let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } + let currEnabledOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) VStack(spacing: 8) { if !reviewForOperators.isEmpty { reviewConditionsButton() + } else if selectedOperatorIds != currEnabledOperatorIds && !selectedOperatorIds.isEmpty { + setOperatorsButton() } else { continueButton() } if onboarding { - Button("Conditions of use") { - // TODO open accepted conditions + Group { + if reviewForOperators.isEmpty { + Button("Conditions of use") { + sheetItem = .showConditions + } + } else { + Text("Conditions of use") + .foregroundColor(.clear) + } } .font(.callout) - .foregroundColor(reviewForOperators.isEmpty ? .accentColor : .clear) .padding(.top) } } @@ -123,8 +144,17 @@ struct ChooseServerOperators: View { justOpened = false } } - .sheet(isPresented: $showInfoSheet) { - ChooseServerOperatorsInfoView() + .sheet(item: $sheetItem) { item in + switch item { + case .showInfo: + ChooseServerOperatorsInfoView() + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) + } } } .frame(maxHeight: .infinity) @@ -140,7 +170,7 @@ struct ChooseServerOperators: View { .frame(width: 20, height: 20) .foregroundColor(theme.colors.primary) .onTapGesture { - showInfoSheet = true + sheetItem = .showInfo } Text("Select network operators to use.") @@ -200,6 +230,28 @@ struct ChooseServerOperators: View { } } + private func setOperatorsButton() -> some View { + Button { + Task { + if let enabledOperators = enabledOperators(serverOperators) { + let r = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } + } + } + } label: { + Text("Update") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + private func continueButton() -> some View { Button { continueToNextStep() diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index f11dbbe7a8..9a0ee4ddeb 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -18,6 +18,7 @@ struct HowItWorks: View { VStack(alignment: .leading) { Text("How SimpleX works") .font(.largeTitle) + .bold() .padding(.vertical) ScrollView { VStack(alignment: .leading) { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 1d1ec5b64c..c078fb23b1 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -526,7 +526,7 @@ private let versionDescriptions: [VersionDescription] = [ .view(FeatureView( icon: nil, title: "Network decentralization", - view: newOperatorsView + view: { NewOperatorsView() } )), .feature(Description( icon: "text.quote", @@ -549,20 +549,37 @@ func shouldShowWhatsNew() -> Bool { return v != lastVersion } -fileprivate func newOperatorsView() -> some View { - VStack(alignment: .leading) { - Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) - .resizable() - .scaledToFit() - .frame(height: 48) - Text("The second preset operator in the app!") - .multilineTextAlignment(.leading) - .lineLimit(10) - HStack { - Button("Enable Flux") { - +fileprivate struct NewOperatorsView: View { + @State private var showOperatorsSheet = false + + var body: some View { + VStack(alignment: .leading) { + Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) + .resizable() + .scaledToFit() + .frame(height: 48) + Text("The second preset operator in the app!") + .multilineTextAlignment(.leading) + .lineLimit(10) + HStack { + Button("Enable Flux") { + showOperatorsSheet = true + } + Text("for better metadata privacy.") } - Text("for better metadata privacy.") + } + .sheet(isPresented: $showOperatorsSheet) { + ChooseServerOperators(onboarding: false) + } + } +} + +private enum WhatsNewViewSheet: Identifiable { + case showConditions + + var id: String { + switch self { + case .showConditions: return "showConditions" } } } @@ -573,13 +590,13 @@ struct WhatsNewView: View { @State var currentVersion = versionDescriptions.count - 1 @State var currentVersionNav = versionDescriptions.count - 1 var viaSettings = false - @State var showWhatsNew: Bool - var showOperatorsNotice: Bool + var updatedConditions: Bool + @State private var sheetItem: WhatsNewViewSheet? = nil var body: some View { - viewBody() + whatsNewView() .task { - if showOperatorsNotice { + if updatedConditions { do { let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId try await setConditionsNotified(conditionsId: conditionsId) @@ -588,14 +605,16 @@ struct WhatsNewView: View { } } } - } - - @ViewBuilder private func viewBody() -> some View { - if showWhatsNew { - whatsNewView() - } else if showOperatorsNotice { - ChooseServerOperators(onboarding: false) - } + .sheet(item: $sheetItem) { item in + switch item { + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) + } + } } private func whatsNewView() -> some View { @@ -623,22 +642,19 @@ struct WhatsNewView: View { } } } + if updatedConditions { + Button("View updated conditions") { + sheetItem = .showConditions + } + } if !viaSettings { Spacer() - if showOperatorsNotice { - Button("View updated conditions") { - showWhatsNew = false - } - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) - } else { - Button("Ok") { - dismiss() - } - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) + Button("Ok") { + dismiss() } + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) Spacer() } @@ -729,6 +745,6 @@ struct WhatsNewView: View { struct NewFeaturesView_Previews: PreviewProvider { static var previews: some View { - WhatsNewView(showWhatsNew: true, showOperatorsNotice: false) + WhatsNewView(updatedConditions: false) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 2247e3d8d5..c668ad3858 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -20,7 +20,7 @@ private enum NetworkAlert: Identifiable { } private enum NetworkAndServersSheet: Identifiable { - case showConditions(conditionsAction: UsageConditionsAction) + case showConditions var id: String { switch self { @@ -65,7 +65,7 @@ struct NetworkAndServers: View { switch conditionsAction { case let .review(_, deadline, _): if let deadline = deadline, anyOperatorEnabled { - Text("Conditions will be considered accepted on: \(conditionsTimestamp(deadline)).") + Text("Conditions will be accepted on: \(conditionsTimestamp(deadline)).") .foregroundColor(theme.colors.secondary) } default: @@ -171,9 +171,8 @@ struct NetworkAndServers: View { } .sheet(item: $sheetItem) { item in switch item { - case let .showConditions(conditionsAction): + case .showConditions: UsageConditionsView( - conditionsAction: conditionsAction, currUserServers: $currUserServers, userServers: $userServers ) @@ -221,7 +220,7 @@ struct NetworkAndServers: View { private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View { Button { - sheetItem = .showConditions(conditionsAction: conditionsAction) + sheetItem = .showConditions } label: { switch conditionsAction { case .review: @@ -236,7 +235,6 @@ struct NetworkAndServers: View { struct UsageConditionsView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme - var conditionsAction: UsageConditionsAction @Binding var currUserServers: [UserOperatorServers] @Binding var userServers: [UserOperatorServers] @@ -248,14 +246,29 @@ struct UsageConditionsView: View { .padding(.top) .padding(.top) - switch conditionsAction { + switch ChatModel.shared.conditions.conditionsAction { - case let .review(operators, _, _): + case .none: + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + + case let .review(operators, deadline, _): Text("Conditions will be accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") ConditionsTextView() - acceptConditionsButton(operators.map { $0.operatorId }) - .padding(.bottom) - .padding(.bottom) + VStack(spacing: 8) { + acceptConditionsButton(operators.map { $0.operatorId }) + if let deadline = deadline { + Text("Conditions will be automatically accepted for enabled operators on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 32) + } + } + .padding(.bottom) + .padding(.bottom) case let .accepted(operators): Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index e73697e42a..f2a1a56d01 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -367,7 +367,7 @@ struct SettingsView: View { } } NavigationLink { - WhatsNewView(viaSettings: true, showWhatsNew: true, showOperatorsNotice: false) + WhatsNewView(viaSettings: true, updatedConditions: false) .modifier(ThemedBackground()) .navigationBarTitleDisplayMode(.inline) } label: {