1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

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>
This commit is contained in:
Daniel D’Aquino 2024-01-30 07:42:04 +00:00 committed by William Casarin
parent cdd5327829
commit 263389b2e6
6 changed files with 254 additions and 31 deletions

View File

@ -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 */,

View File

@ -0,0 +1,39 @@
//
// DamusPurpleNewUserOnboardingView.swift
// damus
//
// Created by Daniel DAquino 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)
}

View File

@ -0,0 +1,192 @@
//
// DamusPurpleTranslationSetupView.swift
// damus
//
// Created by Daniel DAquino 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: {})
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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: {
// 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: {})
}
}