mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
12 KiB
Swift
320 lines
12 KiB
Swift
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import SwiftUI
|
|
import Combine
|
|
import SessionUIKit
|
|
import SignalUtilitiesKit
|
|
import SessionUtilitiesKit
|
|
|
|
struct LandingScreen: View {
|
|
public class ViewModel {
|
|
fileprivate let dependencies: Dependencies
|
|
private let onOnboardingComplete: () -> ()
|
|
private var disposables: Set<AnyCancellable> = Set()
|
|
|
|
init(onOnboardingComplete: @escaping () -> Void, using dependencies: Dependencies) {
|
|
self.dependencies = dependencies
|
|
self.onOnboardingComplete = onOnboardingComplete
|
|
}
|
|
|
|
fileprivate func register(setupComplete: () -> ()) {
|
|
// Reset the Onboarding cache to create a new user (in case the user previously went back)
|
|
dependencies.set(cache: .onboarding, to: Onboarding.Cache(flow: .register, using: dependencies))
|
|
|
|
/// Once the onboarding process is complete we need to call `onOnboardingComplete`
|
|
dependencies[cache: .onboarding].onboardingCompletePublisher
|
|
.subscribe(on: DispatchQueue.main, using: dependencies)
|
|
.receive(on: DispatchQueue.main, using: dependencies)
|
|
.sink(receiveValue: { [weak self] _ in self?.onOnboardingComplete() })
|
|
.store(in: &disposables)
|
|
|
|
setupComplete()
|
|
}
|
|
|
|
fileprivate func restore(setupComplete: () -> ()) {
|
|
// Reset the Onboarding cache to create a new user (in case the user previously went back)
|
|
dependencies.set(cache: .onboarding, to: Onboarding.Cache(flow: .restore, using: dependencies))
|
|
|
|
/// Once the onboarding process is complete we need to call `onOnboardingComplete`
|
|
dependencies[cache: .onboarding].onboardingCompletePublisher
|
|
.subscribe(on: DispatchQueue.main, using: dependencies)
|
|
.receive(on: DispatchQueue.main, using: dependencies)
|
|
.sink(receiveValue: { [weak self] _ in self?.onOnboardingComplete() })
|
|
.store(in: &disposables)
|
|
|
|
setupComplete()
|
|
}
|
|
}
|
|
|
|
@EnvironmentObject var host: HostWrapper
|
|
private let viewModel: ViewModel
|
|
|
|
public init(using dependencies: Dependencies, onOnboardingComplete: @escaping () -> ()) {
|
|
self.viewModel = ViewModel(
|
|
onOnboardingComplete: onOnboardingComplete,
|
|
using: dependencies
|
|
)
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack(alignment: .center) {
|
|
ThemeManager.currentTheme.colorSwiftUI(for: .backgroundPrimary).ignoresSafeArea()
|
|
|
|
VStack(
|
|
alignment: .center,
|
|
spacing: 0
|
|
) {
|
|
Spacer(minLength: 0)
|
|
|
|
Text("onboardingBubblePrivacyInYourPocket".localized())
|
|
.bold()
|
|
.font(.system(size: Values.veryLargeFontSize))
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
Spacer(minLength: 0)
|
|
.frame(maxHeight: 2 * Values.mediumSpacing)
|
|
|
|
FakeChat(using: viewModel.dependencies)
|
|
|
|
Spacer(minLength: 0)
|
|
.frame(maxHeight: Values.massiveSpacing)
|
|
|
|
Spacer(minLength: 0)
|
|
}
|
|
.padding(.vertical, Values.mediumSpacing)
|
|
.padding(.bottom, 3 * Values.largeButtonHeight)
|
|
|
|
VStack(
|
|
alignment: .center,
|
|
spacing: Values.mediumSpacing
|
|
) {
|
|
Spacer()
|
|
|
|
Button {
|
|
register()
|
|
} label: {
|
|
Text("onboardingAccountCreate".localized())
|
|
.bold()
|
|
.font(.system(size: Values.smallFontSize))
|
|
.foregroundColor(themeColor: .sessionButton_primaryFilledText)
|
|
.frame(
|
|
maxWidth: .infinity,
|
|
maxHeight: Values.largeButtonHeight,
|
|
alignment: .center
|
|
)
|
|
.backgroundColor(themeColor: .sessionButton_primaryFilledBackground)
|
|
.cornerRadius(Values.largeButtonHeight / 2)
|
|
}
|
|
.accessibility(
|
|
Accessibility(
|
|
identifier: "Create account button",
|
|
label: "Create account button"
|
|
)
|
|
)
|
|
.padding(.horizontal, Values.massiveSpacing)
|
|
|
|
Button {
|
|
restore()
|
|
} label: {
|
|
Text("onboardingAccountExists".localized())
|
|
.bold()
|
|
.font(.system(size: Values.smallFontSize))
|
|
.foregroundColor(themeColor: .sessionButton_text)
|
|
.frame(
|
|
maxWidth: .infinity,
|
|
maxHeight: Values.largeButtonHeight,
|
|
alignment: .center
|
|
)
|
|
.overlay(
|
|
Capsule()
|
|
.stroke(themeColor: .sessionButton_border)
|
|
)
|
|
}
|
|
.accessibility(
|
|
Accessibility(
|
|
identifier: "Restore your session button",
|
|
label: "Restore your session button"
|
|
)
|
|
)
|
|
.padding(.horizontal, Values.massiveSpacing)
|
|
|
|
Button {
|
|
openLegalUrl()
|
|
} label: {
|
|
let attributedText: NSAttributedString = "onboardingTosPrivacy"
|
|
.localized()
|
|
.formatted(baseFont: .systemFont(ofSize: Values.verySmallFontSize))
|
|
AttributedText(attributedText)
|
|
.font(.system(size: Values.verySmallFontSize))
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
}
|
|
.accessibility(
|
|
Accessibility(
|
|
identifier: "Open URL"
|
|
)
|
|
)
|
|
.padding(.horizontal, Values.massiveSpacing)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func register() {
|
|
viewModel.register {
|
|
let viewController: SessionHostingViewController = SessionHostingViewController(
|
|
rootView: DisplayNameScreen(using: viewModel.dependencies)
|
|
)
|
|
viewController.setUpNavBarSessionIcon(using: viewModel.dependencies)
|
|
self.host.controller?.navigationController?.pushViewController(viewController, animated: true)
|
|
}
|
|
}
|
|
|
|
private func restore() {
|
|
viewModel.restore {
|
|
let viewController: SessionHostingViewController = SessionHostingViewController(
|
|
rootView: LoadAccountScreen(using: viewModel.dependencies)
|
|
)
|
|
viewController.setNavBarTitle("loadAccount".localized())
|
|
self.host.controller?.navigationController?.pushViewController(viewController, animated: true)
|
|
}
|
|
}
|
|
|
|
private func openLegalUrl() {
|
|
let modal: ConfirmationModal = ConfirmationModal(
|
|
info: ConfirmationModal.Info(
|
|
title: "urlOpen".localized(),
|
|
body: .text("urlOpenBrowser".localized()),
|
|
confirmTitle: "onboardingTos".localized(),
|
|
confirmStyle: .textPrimary,
|
|
cancelTitle: "onboardingPrivacy".localized(),
|
|
cancelStyle: .textPrimary,
|
|
hasCloseButton: true,
|
|
onConfirm: { _ in
|
|
if let url: URL = URL(string: "https://getsession.org/terms-of-service") {
|
|
UIApplication.shared.open(url)
|
|
}
|
|
},
|
|
onCancel: { modal in
|
|
if let url: URL = URL(string: "https://getsession.org/privacy-policy") {
|
|
UIApplication.shared.open(url)
|
|
}
|
|
modal.close()
|
|
}
|
|
)
|
|
)
|
|
self.host.controller?.present(modal, animated: true)
|
|
}
|
|
}
|
|
|
|
struct ChatBubble: View {
|
|
let text: String
|
|
let outgoing: Bool
|
|
|
|
var body: some View {
|
|
let backgroundColor: Color? = ThemeManager.currentTheme.colorSwiftUI(for: (outgoing ? .messageBubble_outgoingBackground : .messageBubble_incomingBackground))
|
|
Text(text)
|
|
.foregroundColor(themeColor: (outgoing ? .messageBubble_outgoingText : .messageBubble_incomingText))
|
|
.font(.system(size: 16))
|
|
.padding(.all, 12)
|
|
.background(backgroundColor)
|
|
.cornerRadius(13)
|
|
.frame(
|
|
maxWidth: 230,
|
|
alignment: (outgoing ? .trailing : .leading)
|
|
)
|
|
}
|
|
}
|
|
|
|
struct FakeChat: View {
|
|
@State var numberOfBubblesShown: Int = 0
|
|
private let dependencies: Dependencies
|
|
|
|
public init(using dependencies: Dependencies) {
|
|
self.dependencies = dependencies
|
|
}
|
|
|
|
let chatBubbles: [ChatBubble] = [
|
|
ChatBubble(
|
|
text: "onboardingBubbleWelcomeToSession"
|
|
.put(key: "app_name", value: Constants.app_name)
|
|
.put(key: "emoji", value: "👋")
|
|
.localized(),
|
|
outgoing: false
|
|
),
|
|
ChatBubble(
|
|
text: "onboardingBubbleSessionIsEngineered"
|
|
.put(key: "app_name", value: Constants.app_name)
|
|
.localized(),
|
|
outgoing: true
|
|
),
|
|
ChatBubble(
|
|
text: "onboardingBubbleNoPhoneNumber".localized(),
|
|
outgoing: false
|
|
),
|
|
ChatBubble(
|
|
text: "onboardingBubbleCreatingAnAccountIsEasy"
|
|
.put(key: "emoji", value: "👇")
|
|
.localized(),
|
|
outgoing: true
|
|
)
|
|
]
|
|
|
|
var body: some View {
|
|
ScrollView(showsIndicators: false) {
|
|
VStack(
|
|
alignment: .leading,
|
|
spacing: 18
|
|
) {
|
|
ForEach(
|
|
0...(chatBubbles.count - 1),
|
|
id: \.self
|
|
) { index in
|
|
let chatBubble: ChatBubble = chatBubbles[index]
|
|
let bubble = chatBubble
|
|
.frame(
|
|
maxWidth: .infinity,
|
|
alignment: chatBubble.outgoing ? .trailing : .leading
|
|
)
|
|
if index < numberOfBubblesShown {
|
|
bubble
|
|
.transition(
|
|
AnyTransition
|
|
.move(edge: .bottom)
|
|
.combined(with:.opacity.animation(.easeIn(duration: 0.68)))
|
|
)
|
|
}
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.frame(
|
|
height: 320,
|
|
alignment: .bottom
|
|
)
|
|
.padding(.horizontal, 36)
|
|
}
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.onAppear {
|
|
guard numberOfBubblesShown < 4 else { return }
|
|
|
|
Timer.scheduledTimerOnMainThread(withTimeInterval: 0.2, repeats: false, using: dependencies) { [dependencies] _ in
|
|
withAnimation(.spring().speed(0.68)) {
|
|
numberOfBubblesShown = 1
|
|
}
|
|
Timer.scheduledTimerOnMainThread(withTimeInterval: 1.5, repeats: true, using: dependencies) { timer in
|
|
withAnimation(.spring().speed(0.68)) {
|
|
numberOfBubblesShown += 1
|
|
if numberOfBubblesShown >= 4 {
|
|
timer.invalidate()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LandingView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
LandingScreen(using: Dependencies.createEmpty(), onOnboardingComplete: {})
|
|
}
|
|
}
|