damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 263389b2e669307df7b260fe3e494d856219d54e
parent cdd5327829bebdb95e9f02af6237f2bc3842416f
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Tue, 30 Jan 2024 07:42:04 +0000

purple: add translation setup view the onboarding

This commit changes the welcome view into a multi-step onboarding
process, where it makes it more clear that translations are unlocked,
and provides the user with some choices to set it up or not.

This flow also makes the translation setup possible on the LN flow

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 8++++++++
Adamus/Views/Purple/DamusPurpleNewUserOnboardingView.swift | 39+++++++++++++++++++++++++++++++++++++++
Adamus/Views/Purple/DamusPurpleTranslationSetupView.swift | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/Purple/DamusPurpleURLSheetView.swift | 5++++-
Mdamus/Views/Purple/DamusPurpleView.swift | 27+--------------------------
Mdamus/Views/Purple/DamusPurpleWelcomeView.swift | 14++++++++++----
6 files changed, 254 insertions(+), 31 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -448,6 +448,8 @@ D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; }; D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; }; D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; }; + D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; }; + D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */; }; D74AAFC22B153395006CF0F4 /* HeadlessDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */; }; D74AAFC32B153395006CF0F4 /* HeadlessDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */; }; D74AAFC52B1538DF006CF0F4 /* NotificationExtensionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */; }; @@ -1342,6 +1344,8 @@ D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; }; D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; }; D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; }; + D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; }; + D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNewUserOnboardingView.swift; sourceTree = "<group>"; }; D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessDamusState.swift; sourceTree = "<group>"; }; D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExtensionState.swift; sourceTree = "<group>"; }; D74AAFCB2B155D07006CF0F4 /* MakeZapRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeZapRequest.swift; sourceTree = "<group>"; }; @@ -2576,9 +2580,11 @@ children = ( 4CFF8F5829C9FD1E008DB934 /* DamusPurpleView.swift */, D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */, + D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */, D7ADD3DF2B538D4200F104C4 /* DamusPurpleURLSheetView.swift */, D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */, D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */, + D7373BA72B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift */, ); path = Purple; sourceTree = "<group>"; @@ -2996,6 +3002,7 @@ 4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */, 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */, 4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */, + D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */, 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */, 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */, @@ -3114,6 +3121,7 @@ 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */, D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */, + D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, D7CB5D452B116FE800AD4105 /* Contacts+.swift in Sources */, 4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */, diff --git a/damus/Views/Purple/DamusPurpleNewUserOnboardingView.swift b/damus/Views/Purple/DamusPurpleNewUserOnboardingView.swift @@ -0,0 +1,39 @@ +// +// DamusPurpleNewUserOnboardingView.swift +// damus +// +// Created by Daniel D’Aquino on 2024-01-29. +// + +import SwiftUI + +struct DamusPurpleNewUserOnboardingView: View { + var damus_state: DamusState + @State var current_page: Int = 0 + @Environment(\.dismiss) var dismiss + + func next_page() { + current_page += 1 + } + + var body: some View { + NavigationView { + TabView(selection: $current_page) { + DamusPurpleWelcomeView(next_page: { + self.next_page() + }) + .tag(0) + + DamusPurpleTranslationSetupView(damus_state: damus_state, next_page: { + dismiss() + }) + .tag(1) + } + .ignoresSafeArea() // Necessary to avoid weird white edges + } + } +} + +#Preview { + DamusPurpleNewUserOnboardingView(damus_state: test_damus_state) +} diff --git a/damus/Views/Purple/DamusPurpleTranslationSetupView.swift b/damus/Views/Purple/DamusPurpleTranslationSetupView.swift @@ -0,0 +1,192 @@ +// +// DamusPurpleTranslationSetupView.swift +// damus +// +// Created by Daniel D’Aquino on 2024-01-29. +// + +import SwiftUI + +fileprivate extension Animation { + static func content() -> Animation { + Animation.easeInOut(duration: 1.5).delay(0) + } + + static func delayed_content() -> Animation { + Animation.easeInOut(duration: 1.5).delay(1) + } +} + +struct DamusPurpleTranslationSetupView: View { + var damus_state: DamusState + var next_page: () -> Void + + @State var start = false + @State var show_settings_change_confirmation_dialog = false + + // MARK: - Helper functions + + func update_user_settings_to_purple() { + if damus_state.settings.translation_service == .none { + set_translation_settings_to_purple() + self.next_page() + } + else { + show_settings_change_confirmation_dialog = true + } + } + + func set_translation_settings_to_purple() { + damus_state.settings.translation_service = .purple + damus_state.settings.auto_translate = true + } + + // MARK: - View layout + + var body: some View { + VStack { + Image("damus-dark-logo") + .resizable() + .frame(width: 50, height: 50) + .clipShape(RoundedRectangle(cornerRadius: 10.0)) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(LinearGradient( + colors: [DamusColors.lighterPink.opacity(0.8), .white.opacity(0), DamusColors.deepPurple.opacity(0.6)], + startPoint: .topLeading, + endPoint: .bottomTrailing), lineWidth: 1) + ) + .shadow(radius: 5) + .padding(20) + .opacity(start ? 1.0 : 0.0) + .animation(.content(), value: start) + + Text(NSLocalizedString("You unlocked", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple" )) + .font(.largeTitle) + .fontWeight(.bold) + .foregroundStyle( + LinearGradient( + colors: [.black, .black, DamusColors.pink, DamusColors.lighterPink], + startPoint: start ? .init(x: -3, y: 4) : .bottomLeading, + endPoint: start ? .topTrailing : .init(x: 3, y: -4) + ) + ) + .scaleEffect(x: start ? 1 : 0.9, y: start ? 1 : 0.9) + .opacity(start ? 1.0 : 0.0) + .animation(.content(), value: start) + + Image(systemName: "globe") + .resizable() + .frame(width: 96, height: 90) + .foregroundStyle( + LinearGradient( + colors: [.black, DamusColors.purple, .white, .white], + startPoint: start ? .init(x: -1, y: 1.5) : .bottomLeading, + endPoint: start ? .topTrailing : .init(x: 10, y: -11) + ) + ) + .animation(Animation.snappy(duration: 2).delay(0), value: start) + .shadow( + color: start ? DamusColors.purple.opacity(0.2) : DamusColors.purple.opacity(0.3), + radius: start ? 30 : 10 + ) + .animation(Animation.snappy(duration: 2).delay(0), value: start) + .scaleEffect(x: start ? 1 : 0.8, y: start ? 1 : 0.8) + .opacity(start ? 1.0 : 0.0) + .animation(Animation.snappy(duration: 2).delay(0), value: start) + + Text(NSLocalizedString("Automatic translations", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple")) + .font(.headline) + .fontWeight(.bold) + .foregroundStyle( + LinearGradient( + colors: [.black, .black, DamusColors.lighterPink, DamusColors.lighterPink], + startPoint: start ? .init(x: -3, y: 4) : .bottomLeading, + endPoint: start ? .topTrailing : .init(x: 3, y: -4) + ) + ) + .scaleEffect(x: start ? 1 : 0.9, y: start ? 1 : 0.9) + .opacity(start ? 1.0 : 0.0) + .animation(.content(), value: start) + .padding(.top, 10) + + Text(NSLocalizedString("As part of your Damus Purple membership, you get complimentary and automated translations. Would you like to enable Damus Purple translations?\n\nTip: You can always change this later in Settings → Translations", comment: "Message notifying the user that they get auto-translations as part of their service")) + .lineSpacing(5) + .multilineTextAlignment(.center) + .foregroundStyle(.white.opacity(0.8)) + .padding(.horizontal, 20) + .padding(.top, 50) + .padding(.bottom, 20) + .opacity(start ? 1.0 : 0.0) + .animation(.delayed_content(), value: start) + + Button(action: { + self.update_user_settings_to_purple() + }, label: { + HStack { + Spacer() + Text(NSLocalizedString("Enable Purple auto-translations", comment: "Label for button that allows users to enable Damus Purple translations")) + Spacer() + } + }) + .padding(.horizontal, 30) + .buttonStyle(GradientButtonStyle()) + .opacity(start ? 1.0 : 0.0) + .animation(.delayed_content(), value: start) + + Button(action: { + self.next_page() + }, label: { + HStack { + Spacer() + Text(NSLocalizedString("No, thanks", comment: "Label for button that allows users to reject enabling Damus Purple translations")) + Spacer() + } + }) + .padding(.horizontal, 30) + .foregroundStyle(DamusColors.pink) + .opacity(start ? 1.0 : 0.0) + .padding() + .animation(.delayed_content(), value: start) + } + .background(content: { + ZStack { + Rectangle() + .background(.black) + Image("purple-blue-gradient-1") + .offset(CGSize(width: 300.0, height: -0.0)) + .opacity(start ? 1.0 : 0.2) + Image("stars-bg") + .resizable(resizingMode: .stretch) + .frame(width: 500, height: 500) + .offset(x: -100, y: 50) + .scaleEffect(start ? 1 : 0.9) + .animation(.content(), value: start) + Image("purple-blue-gradient-1") + .offset(CGSize(width: 300.0, height: -0.0)) + .opacity(start ? 1.0 : 0.2) + + } + }) + .onAppear(perform: { + withAnimation(.easeOut(duration: 6), { + start = true + }) + }) + .confirmationDialog( + NSLocalizedString("It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?", comment: "Confirmation dialog question asking users if they want their translation settings to be automatically switched to the Damus Purple translation service"), + isPresented: $show_settings_change_confirmation_dialog, + titleVisibility: .visible + ) { + Button(NSLocalizedString("Yes", comment: "User confirm Yes")) { + set_translation_settings_to_purple() + self.next_page() + }.keyboardShortcut(.defaultAction) + Button(NSLocalizedString("No", comment: "User confirm No"), role: .cancel) {} + } + } +} + +#Preview { + DamusPurpleTranslationSetupView(damus_state: test_damus_state, next_page: {}) +} diff --git a/damus/Views/Purple/DamusPurpleURLSheetView.swift b/damus/Views/Purple/DamusPurpleURLSheetView.swift @@ -19,7 +19,10 @@ struct DamusPurpleURLSheetView: View { case .verify_npub(let checkout_id): DamusPurpleVerifyNpubView(damus_state: damus_state, checkout_id: checkout_id) case .welcome(_): - DamusPurpleWelcomeView() + // Forcibly pass the dismiss environment object, + // because SwiftUI has a weird quirk that makes the `dismiss` Environment object unavailable in deeply nested views + // this problem only exists in real devices. + DamusPurpleNewUserOnboardingView(damus_state: damus_state, dismiss: _dismiss) case .landing: DamusPurpleView(damus_state: damus_state) } diff --git a/damus/Views/Purple/DamusPurpleView.swift b/damus/Views/Purple/DamusPurpleView.swift @@ -106,21 +106,10 @@ struct DamusPurpleView: View { } .ignoresSafeArea(.all) .sheet(isPresented: $show_welcome_sheet, onDismiss: { - update_user_settings_to_purple() shouldDismissView = true }, content: { - DamusPurpleWelcomeView() + DamusPurpleNewUserOnboardingView(damus_state: damus_state) }) - .confirmationDialog( - NSLocalizedString("It seems that you already have a translation service configured. Would you like to switch to Damus Purple as your translator?", comment: "Confirmation dialog question asking users if they want their translation settings to be automatically switched to the Damus Purple translation service"), - isPresented: $show_settings_change_confirmation_dialog, - titleVisibility: .visible - ) { - Button(NSLocalizedString("Yes", comment: "User confirm Yes")) { - set_translation_settings_to_purple() - }.keyboardShortcut(.defaultAction) - Button(NSLocalizedString("No", comment: "User confirm No"), role: .cancel) {} - } .onChange(of: shouldDismissView) { shouldDismissView in if shouldDismissView && !show_settings_change_confirmation_dialog { dismiss() @@ -148,20 +137,6 @@ struct DamusPurpleView: View { } } - func update_user_settings_to_purple() { - if damus_state.settings.translation_service == .none { - set_translation_settings_to_purple() - } - else { - show_settings_change_confirmation_dialog = true - } - } - - func set_translation_settings_to_purple() { - damus_state.settings.translation_service = .purple - damus_state.settings.auto_translate = true - } - func handle_transactions(products: [Product]) async { for await update in StoreKit.Transaction.updates { switch update { diff --git a/damus/Views/Purple/DamusPurpleWelcomeView.swift b/damus/Views/Purple/DamusPurpleWelcomeView.swift @@ -17,6 +17,7 @@ fileprivate extension Animation { struct DamusPurpleWelcomeView: View { @Environment(\.dismiss) var dismiss @State var start = false + var next_page: () -> Void var body: some View { VStack { @@ -80,7 +81,7 @@ struct DamusPurpleWelcomeView: View { .animation(.content(), value: start) Button(action: { - dismiss() + self.next_page() }, label: { HStack { Spacer() @@ -113,15 +114,20 @@ struct DamusPurpleWelcomeView: View { } }) .onAppear(perform: { - withAnimation(.easeOut(duration: 6), { - start = true + // SwiftUI quirk #98332: If I try to trigger an immediate animation, the animation does not work when this view is placed under a TabView. + // Triggering the animation only after a slight delay makes it work. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + withAnimation(.easeOut(duration: 6), { + start = true + }) }) + }) } } struct DamusPurpleWelcomeView_Previews: PreviewProvider { static var previews: some View { - DamusPurpleWelcomeView() + DamusPurpleWelcomeView(next_page: {}) } }