ios: create address during onboarding (#2362)

* ios: create address during onboarding

* contact picker

* email wip

* send email w/t leaving app

* fomatting

* layout, texts

* remove contact picker, add email button to address page

* refactor

* refactor

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
spaced4ndy
2023-05-01 20:36:52 +04:00
committed by GitHub
parent 6f11913359
commit 551ed202be
9 changed files with 329 additions and 10 deletions

View File

@@ -109,7 +109,7 @@ struct MigrateToAppGroupView: View {
do {
resetChatCtrl()
try initializeChat(start: true)
chatModel.onboardingStage = .step3_SetNotificationsMode
chatModel.onboardingStage = .step4_SetNotificationsMode
setV3DBMigration(.ready)
} catch let error {
dbContainerGroupDefault.set(.documents)

View File

@@ -0,0 +1,61 @@
//
// MailView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 01.05.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
import UIKit
import MessageUI
struct MailView: UIViewControllerRepresentable {
@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?
var subject = ""
var messageBody = ""
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?
init(isShowing: Binding<Bool>,
result: Binding<Result<MFMailComposeResult, Error>?>) {
_isShowing = isShowing
_result = result
}
func mailComposeController(
_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?
) {
defer {
isShowing = false
}
if let error = error {
self.result = .failure(error)
return
}
self.result = .success(result)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing, result: $result)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.setSubject(subject)
vc.setMessageBody(messageBody, isHTML: true)
vc.mailComposeDelegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>) {
}
}

View File

@@ -34,7 +34,9 @@ struct CreateProfile: View {
VStack(alignment: .leading) {
Text("Create your profile")
.font(.largeTitle)
.bold()
.padding(.bottom, 4)
.frame(maxWidth: .infinity)
Text("Your profile, contacts and delivered messages are stored on your device.")
.padding(.bottom, 4)
Text("The profile is only shared with your contacts.")
@@ -122,7 +124,7 @@ struct CreateProfile: View {
m.currentUser = try apiCreateActiveUser(profile)
if m.users.isEmpty {
try startChat()
withAnimation { m.onboardingStage = .step3_SetNotificationsMode }
withAnimation { m.onboardingStage = .step3_CreateSimpleXAddress }
} else {
dismiss()
m.users = try listUsers()

View File

@@ -0,0 +1,208 @@
//
// CreateSimpleXAddress.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 28.04.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
import Contacts
import ContactsUI
import MessageUI
import SimpleXChat
struct CreateSimpleXAddress: View {
@EnvironmentObject var m: ChatModel
@State private var progressIndicator = false
@State private var showMailView = false
@State private var mailViewResult: Result<MFMailComposeResult, Error>? = nil
var body: some View {
GeometryReader { g in
ScrollView {
ZStack {
VStack(alignment: .leading) {
Text("SimpleX Address")
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity)
Spacer()
if let userAddress = m.userAddress {
QRCode(uri: userAddress.connReqContact)
.frame(maxHeight: g.size.width)
shareQRCodeButton(userAddress)
.frame(maxWidth: .infinity)
Spacer()
shareViaEmailButton(userAddress)
.frame(maxWidth: .infinity)
Spacer()
continueButton()
.padding(.bottom, 8)
.frame(maxWidth: .infinity)
} else {
createAddressButton()
.frame(maxWidth: .infinity)
Spacer()
skipButton()
.padding(.bottom, 56)
.frame(maxWidth: .infinity)
}
}
.frame(minHeight: g.size.height)
if progressIndicator {
ProgressView().scaleEffect(2)
}
}
}
}
.frame(maxHeight: .infinity)
.padding()
}
private func createAddressButton() -> some View {
VStack(spacing: 8) {
Button {
progressIndicator = true
Task {
do {
let connReqContact = try await apiCreateUserAddress()
DispatchQueue.main.async {
m.userAddress = UserContactLink(connReqContact: connReqContact)
}
if let u = try await apiSetProfileAddress(on: true) {
DispatchQueue.main.async {
m.updateUser(u)
}
}
await MainActor.run { progressIndicator = false }
} catch let error {
logger.error("CreateSimpleXAddress create address: \(responseError(error))")
await MainActor.run { progressIndicator = false }
let a = getErrorAlert(error, "Error creating address")
AlertManager.shared.showAlertMsg(
title: a.title,
message: a.message
)
}
}
} label: {
Text("Create SimpleX address").font(.title)
}
Group {
Text("Your contacts in SimpleX will see it.\nYou can change it in Settings.")
}
.multilineTextAlignment(.center)
.font(.footnote)
.padding(.horizontal, 32)
}
}
private func skipButton() -> some View {
VStack(spacing: 8) {
Button {
withAnimation {
m.onboardingStage = .step4_SetNotificationsMode
}
} label: {
HStack {
Text("Don't create address")
Image(systemName: "chevron.right")
}
}
Text("You can create it later").font(.footnote)
}
}
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
showShareSheet(items: [userAddress.connReqContact])
} label: {
Label("Share", systemImage: "square.and.arrow.up")
}
}
private func shareViaEmailButton(_ userAddress: UserContactLink) -> some View {
Button {
showMailView = true
} label: {
Label("Invite friends", systemImage: "envelope")
.font(.title2)
}
.sheet(isPresented: $showMailView) {
SendAddressMailView(
showMailView: $showMailView,
mailViewResult: $mailViewResult,
userAddress: userAddress
)
.edgesIgnoringSafeArea(.bottom)
}
.onChange(of: mailViewResult == nil) { _ in
if let r = mailViewResult {
switch r {
case let .success(composeResult):
switch composeResult {
case .sent:
m.onboardingStage = .step4_SetNotificationsMode
default: ()
}
case let .failure(error):
logger.error("CreateSimpleXAddress share via email: \(responseError(error))")
let a = getErrorAlert(error, "Error sending email")
AlertManager.shared.showAlertMsg(
title: a.title,
message: a.message
)
}
mailViewResult = nil
}
}
}
private func continueButton() -> some View {
Button {
withAnimation {
m.onboardingStage = .step4_SetNotificationsMode
}
} label: {
HStack {
Text("Continue")
Image(systemName: "greaterthan")
}
}
}
}
struct SendAddressMailView: View {
@Binding var showMailView: Bool
@Binding var mailViewResult: Result<MFMailComposeResult, Error>?
var userAddress: UserContactLink
var body: some View {
let messageBody = """
<p>Hi!</p>
<p><a href="\(userAddress.connReqContact)">Connect to me via SimpleX Chat</a></p>
"""
MailView(
isShowing: self.$showMailView,
result: $mailViewResult,
subject: "Let's talk in SimpleX Chat",
messageBody: messageBody
)
}
}
struct CreateSimpleXAddress_Previews: PreviewProvider {
static var previews: some View {
CreateSimpleXAddress()
}
}

View File

@@ -15,7 +15,8 @@ struct OnboardingView: View {
switch onboarding {
case .step1_SimpleXInfo: SimpleXInfo(onboarding: true)
case .step2_CreateProfile: CreateProfile()
case .step3_SetNotificationsMode: SetNotificationsMode()
case .step3_CreateSimpleXAddress: CreateSimpleXAddress()
case .step4_SetNotificationsMode: SetNotificationsMode()
case .onboardingComplete: EmptyView()
}
}
@@ -24,7 +25,8 @@ struct OnboardingView: View {
enum OnboardingStage {
case step1_SimpleXInfo
case step2_CreateProfile
case step3_SetNotificationsMode
case step3_CreateSimpleXAddress
case step4_SetNotificationsMode
case onboardingComplete
}

View File

@@ -17,7 +17,10 @@ struct SetNotificationsMode: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Push notifications").font(.largeTitle)
Text("Push notifications")
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity)
Text("Send notifications:")
ForEach(NotificationsMode.values) { mode in
@@ -62,9 +65,10 @@ struct SetNotificationsMode: View {
m.notificationMode = mode
}
} catch let error {
let a = getErrorAlert(error, "Error enabling notifications")
AlertManager.shared.showAlertMsg(
title: "Error enabling notifications",
message: "\(responseError(error))"
title: a.title,
message: a.message
)
}
}

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import MessageUI
import SimpleXChat
struct UserAddressView: View {
@@ -17,6 +18,8 @@ struct UserAddressView: View {
@State private var aas = AutoAcceptState()
@State private var savedAAS = AutoAcceptState()
@State private var ignoreShareViaProfileChange = false
@State private var showMailView = false
@State private var mailViewResult: Result<MFMailComposeResult, Error>? = nil
@State private var alert: UserAddressAlert?
@State private var showSaveDialogue = false
@State private var progressIndicator = false
@@ -189,6 +192,7 @@ struct UserAddressView: View {
Section {
QRCode(uri: userAddress.connReqContact)
shareQRCodeButton(userAddress)
shareViaEmailButton(userAddress)
shareWithContactsButton()
autoAcceptToggle()
learnMoreButton()
@@ -240,9 +244,9 @@ struct UserAddressView: View {
}
}
private func shareQRCodeButton(_ userAdress: UserContactLink) -> some View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
showShareSheet(items: [userAdress.connReqContact])
showShareSheet(items: [userAddress.connReqContact])
} label: {
settingsRow("square.and.arrow.up") {
Text("Share address")
@@ -250,6 +254,36 @@ struct UserAddressView: View {
}
}
private func shareViaEmailButton(_ userAddress: UserContactLink) -> some View {
Button {
showMailView = true
} label: {
settingsRow("envelope") {
Text("Invite friends")
}
}
.sheet(isPresented: $showMailView) {
SendAddressMailView(
showMailView: $showMailView,
mailViewResult: $mailViewResult,
userAddress: userAddress
)
.edgesIgnoringSafeArea(.bottom)
}
.onChange(of: mailViewResult == nil) { _ in
if let r = mailViewResult {
switch r {
case .success: ()
case let .failure(error):
logger.error("UserAddressView share via email: \(responseError(error))")
let a = getErrorAlert(error, "Error sending email")
alert = .error(title: a.title, error: a.message)
}
mailViewResult = nil
}
}
}
private func autoAcceptToggle() -> some View {
settingsRow("checkmark") {
Toggle("Auto-accept", isOn: $aas.enable)