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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 329 additions and 10 deletions

View file

@ -1057,7 +1057,7 @@ func startChat(refreshInvitations: Bool = true) throws {
}
withAnimation {
m.onboardingStage = m.onboardingStage == .step2_CreateProfile && m.users.count == 1
? .step3_SetNotificationsMode
? .step3_CreateSimpleXAddress
: .onboardingComplete
}
}

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)

View file

@ -151,6 +151,8 @@
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; };
6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; };
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
644E72A629F18C00003534BE /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644E72A129F18C00003534BE /* libgmpxx.a */; };
644E72A729F18C00003534BE /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644E72A229F18C00003534BE /* libffi.a */; };
@ -418,6 +420,8 @@
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = "<group>"; };
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
644E72A129F18C00003534BE /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
644E72A229F18C00003534BE /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
@ -604,6 +608,7 @@
18415A7F0F189D87DEFEABCA /* PressedButtonStyle.swift */,
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */,
18415DAAAD1ADBEDB0EDA852 /* VideoPlayerView.swift */,
64466DCB29FFE3E800E3D48D /* MailView.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -668,6 +673,7 @@
5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */,
5CB0BA992827FD8800B3292C /* HowItWorks.swift */,
5CB0BA91282713FD00B3292C /* CreateProfile.swift */,
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */,
5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */,
5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */,
);
@ -1085,6 +1091,7 @@
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */,
5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */,
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */,
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */,
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */,
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */,
5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */,
@ -1129,6 +1136,7 @@
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */,
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */,
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */,
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */,
5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */,
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */,
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */,