From a02cfb4f41488eba245acd434aea551a62078d73 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 26 Dec 2022 14:08:01 +0000 Subject: [PATCH] ios: show what is new in the latest version (#1644) * ios: show what is new in the latest version * add OK button to WhatsNew * separate state for nav buttons --- apps/ios/Shared/ContentView.swift | 8 + apps/ios/Shared/Views/ChatList/ChatHelp.swift | 13 ++ .../Views/Onboarding/CreateProfile.swift | 1 + .../Views/Onboarding/WhatsNewView.swift | 194 ++++++++++++++++++ .../Views/UserSettings/MarkdownHelp.swift | 1 - .../Views/UserSettings/SettingsView.swift | 22 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + 7 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 apps/ios/Shared/Views/Onboarding/WhatsNewView.swift diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 26e9e953c6..aec5b96f90 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -19,6 +19,7 @@ struct ContentView: View { @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = true @AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false + @State private var showWhatsNew = false var body: some View { ZStack { @@ -61,9 +62,16 @@ struct ContentView: View { if (!prefLANoticeShown && prefShowLANotice) { prefLANoticeShown = true alertManager.showAlert(laNoticeAlert()) + } else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + showWhatsNew = shouldShowWhatsNew() + } } prefShowLANotice = true } + .sheet(isPresented: $showWhatsNew) { + WhatsNewView() + } if chatModel.showCallView, let call = chatModel.activeCall { ActiveCallView(call: call) } diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 2aa6b0e7d7..7741512432 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -14,6 +14,10 @@ struct ChatHelp: View { @State private var showAddChat = false var body: some View { + ScrollView { chatHelp() } + } + + func chatHelp() -> some View { VStack(alignment: .leading, spacing: 10) { Text("Thank you for installing SimpleX Chat!") @@ -44,6 +48,15 @@ struct ChatHelp: View { Text("**Scan QR code**: to connect to your contact in person or via video call.") } .padding(.top, 24) + + VStack(alignment: .leading, spacing: 10) { + Text("Markdown in messages") + .font(.title2) + .fontWeight(.bold) + + MarkdownHelp() + } + .padding(.top, 24) } .padding() } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 9810c5d3b1..c6ecb21eb9 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -75,6 +75,7 @@ struct CreateProfile: View { } .onAppear() { focusDisplayName = true + setLastVersionDefault() } .padding() } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift new file mode 100644 index 0000000000..122c05443c --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -0,0 +1,194 @@ +// +// WhatsNewView.swift +// SimpleX (iOS) +// +// Created by Evgeny on 24/12/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +private struct VersionDescription { + var version: String + var features: [FeatureDescription] +} + +private struct FeatureDescription { + var icon: String + var title: LocalizedStringKey + var description: LocalizedStringKey +} + +private let versionDescriptions: [VersionDescription] = [ + VersionDescription( + version: "v4.2", + features: [ + FeatureDescription( + icon: "checkmark.shield", + title: "Security assessment", + description: "SimpleX Chat security was [audited by Trail of Bits](https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)." + ), + FeatureDescription( + icon: "person.2", + title: "Group links", + description: "Admins can create the links to join groups." + ), + FeatureDescription( + icon: "checkmark", + title: "Auto-accept contact requests", + description: "With optional welcome message." + ), + ] + ), + VersionDescription( + version: "v4.3", + features: [ + FeatureDescription( + icon: "mic", + title: "Voice messages", + description: "Max 30 seconds, received instantly." + ), + FeatureDescription( + icon: "trash.slash", + title: "Irreversible message deletion", + description: "Your contacts can allow full message deletion." + ), + FeatureDescription( + icon: "externaldrive.connected.to.line.below", + title: "Improved server configuration", + description: "Add servers by scanning QR codes." + ), + FeatureDescription( + icon: "eye.slash", + title: "Improved privacy and security", + description: "Hide app screen in the recent apps." + ), + ] + ), + VersionDescription( + version: "v4.4", + features: [ + FeatureDescription( + icon: "stopwatch", + title: "Disappearing messages", + description: "Sent messages will be deleted after set time." + ), + FeatureDescription( + icon: "ellipsis.circle", + title: "Live messages", + description: "Recipients see updates as you type them." + ), + FeatureDescription( + icon: "checkmark.shield", + title: "Verify connection security", + description: "Compare security codes with your contacts." + ), + FeatureDescription( + icon: "camera", + title: "GIFs and stickers", + description: "Send them from gallery or custom keyboards." + ) + ] + ) +] + +private let lastVersion = versionDescriptions.last!.version + +func setLastVersionDefault() { + UserDefaults.standard.set(lastVersion, forKey: DEFAULT_WHATS_NEW_VERSION) +} + +func shouldShowWhatsNew() -> Bool { + let v = UserDefaults.standard.string(forKey: DEFAULT_WHATS_NEW_VERSION) + setLastVersionDefault() + return v != lastVersion +} + +struct WhatsNewView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @State var currentVersion = versionDescriptions.count - 1 + @State var currentVersionNav = versionDescriptions.count - 1 + var viaSettings = false + + var body: some View { + VStack { + TabView(selection: $currentVersion) { + ForEach(0..<3) { i in + let v = versionDescriptions[i] + VStack(alignment: .leading, spacing: 16) { + Text("New in \(v.version)") + .font(.title) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity) + .padding(.vertical) + ForEach(v.features, id: \.icon) { f in + featureDescription(f.icon, f.title, f.description) + } + if !viaSettings { + Spacer() + Button("Ok") { + dismiss() + } + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) + Spacer() + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .tag(i) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + Spacer() + pagination() + } + .padding() + } + + private func featureDescription(_ icon: String, _ title: LocalizedStringKey, _ description: LocalizedStringKey) -> some View { + VStack(alignment: .leading) { + HStack(alignment: .center) { + Image(systemName: icon).foregroundColor(.secondary) + Text(title).font(.title3).bold() + } + Text(description) + .multilineTextAlignment(.leading) + } + } + + private func pagination() -> some View { + HStack { + if currentVersionNav > 0 { + let prev = currentVersionNav - 1 + Button { + currentVersionNav = prev + withAnimation { currentVersion = prev } + } label: { + HStack { + Image(systemName: "chevron.left") + Text(versionDescriptions[prev].version) + } + } + } + Spacer() + if currentVersionNav < versionDescriptions.count - 1 { + let next = currentVersionNav + 1 + Button { + currentVersionNav = next + withAnimation { currentVersion = next } + } label: { + HStack { + Text(versionDescriptions[next].version) + Image(systemName: "chevron.right") + } + } + } + } + } +} + +struct NewFeaturesView_Previews: PreviewProvider { + static var previews: some View { + WhatsNewView() + } +} diff --git a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift index c5dafc8663..afb0af66c1 100644 --- a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift @@ -26,7 +26,6 @@ struct MarkdownHelp: View { .textSelection(.enabled) } .frame(maxWidth: .infinity, alignment: .leading) - .padding() } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index c04bfc2b3e..f41c022dc3 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -39,6 +39,7 @@ let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue" let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle" let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab" let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown" +let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion" let appDefaults: [String: Any] = [ DEFAULT_SHOW_LA_NOTICE: false, @@ -193,6 +194,12 @@ struct SettingsView: View { } label: { settingsRow("questionmark") { Text("How to use it") } } + NavigationLink { + WhatsNewView(viaSettings: true) + .navigationBarTitleDisplayMode(.inline) + } label: { + settingsRow("plus") { Text("What's new") } + } NavigationLink { SimpleXInfo(onboarding: false) .navigationBarTitle("", displayMode: .inline) @@ -200,13 +207,14 @@ struct SettingsView: View { } label: { settingsRow("info") { Text("About SimpleX Chat") } } - NavigationLink { - MarkdownHelp() - .navigationTitle("How to use markdown") - .frame(maxHeight: .infinity, alignment: .top) - } label: { - settingsRow("textformat") { Text("Markdown in messages") } - } +// NavigationLink { +// MarkdownHelp() +// .padding() +// .navigationTitle("How to use markdown") +// .frame(maxHeight: .infinity, alignment: .top) +// } label: { +// settingsRow("textformat") { Text("Markdown in messages") } +// } settingsRow("number") { Button("Send questions and ideas") { showSettings = false diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 68f5775c11..16bd4590bd 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; }; 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; }; 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; + 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */; }; 5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */; }; 5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C132944CC12002D9531 /* ScanCodeView.swift */; }; 5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; }; @@ -325,6 +326,7 @@ 5CBD285729565D2600EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = "fr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5CBD285829565D2600EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUtils.swift; sourceTree = ""; }; + 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = ""; }; 5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyCodeView.swift; sourceTree = ""; }; 5CBE6C132944CC12002D9531 /* ScanCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanCodeView.swift; sourceTree = ""; }; 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; @@ -592,6 +594,7 @@ 5CB0BA992827FD8800B3292C /* HowItWorks.swift */, 5CB0BA91282713FD00B3292C /* CreateProfile.swift */, 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */, + 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */, ); path = Onboarding; sourceTree = ""; @@ -1037,6 +1040,7 @@ 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, + 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */, 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */, 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,