Add Account creation feature (Register)

Fix unreadMessagesCount when displayedConversation is null
Change user agent
This commit is contained in:
Benoit Martins 2024-06-20 16:39:49 +02:00
parent 2c28474ca5
commit 02a89a08c3
9 changed files with 569 additions and 174 deletions

View file

@ -1179,7 +1179,6 @@
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
"USE_CRASHLYTICS=1",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = msgNotificationService/Info.plist;
@ -1193,7 +1192,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1219,10 +1218,7 @@
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"USE_CRASHLYTICS=1",
);
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = msgNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService;
@ -1235,7 +1231,7 @@
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1382,7 +1378,6 @@
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
"USE_CRASHLYTICS=1",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
@ -1408,8 +1403,8 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 6.0;
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
MARKETING_VERSION = 6.0.0;
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@ -1435,10 +1430,7 @@
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"USE_CRASHLYTICS=1",
);
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "Camera usage is required for video VOIP calls";
@ -1463,8 +1455,8 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 6.0;
OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS";
MARKETING_VERSION = 6.0.0;
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;

View file

@ -114,8 +114,11 @@ final class CoreContext: ObservableObject {
self.mCore.autoIterateEnabled = false
self.mCore.callkitEnabled = true
self.mCore.pushNotificationEnabled = true
self.mCore.setUserAgent(name: "Linphone iOS 6.0 Beta (\(UIDevice.current.localizedModel)) - Linphone SDK : \(self.coreVersion)", version: "6.0")
let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
self.mCore.setUserAgent(name: "\(appName ?? "Linphone")iOS/\(version ?? "6.0.0") Beta (\(UIDevice.current.localizedModel)) LinphoneSDK", version: self.coreVersion)
self.mCore.videoCaptureEnabled = true
self.mCore.videoDisplayEnabled = true

View file

@ -290,6 +290,38 @@
}
}
},
"assistant_account_register_push_notification_not_received_error" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Push notification with auth token not received in 5 seconds, please try again later"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "La notification poussée avec le jeton d\\'authentification n'a pas été reçue dans les 5 secondes, merci de réessayer plus tard"
}
}
}
},
"assistant_account_register_unexpected_error" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unexpected error occurred, please try again later"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Un erreur inattendue est survenue, merci de réessayer plus tard"
}
}
}
},
"assistant_already_have_an_account" : {
"localizations" : {
"en" : {

View file

@ -45,8 +45,7 @@ store_friends=0
record_aware=1
[account_creator]
url=https://flexisip-staging-master.linphone.org/login # For testing
# url=https://subscribe.linphone.org/api/
url=https://subscribe.linphone.org/api/
[lime]
lime_update_threshold=86400

View file

@ -25,7 +25,6 @@ struct RegisterCodeConfirmationFragment: View {
@Environment(\.dismiss) var dismiss
@StateObject var viewModel = ViewModel()
@FocusState var isFocused: Bool
let textLimit = 4
@ -40,116 +39,129 @@ struct RegisterCodeConfirmationFragment: View {
var body: some View {
NavigationView {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack {
ZStack {
Image("mountain")
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: 100)
.clipped()
VStack(alignment: .leading) {
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, -75)
.padding(.leading, -10)
.onTapGesture {
withAnimation {
dismiss()
ZStack {
ScrollView(.vertical) {
VStack {
ZStack {
Image("mountain")
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: 100)
.clipped()
VStack(alignment: .leading) {
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, -75)
.padding(.leading, -10)
.onTapGesture {
withAnimation {
dismiss()
}
}
}
Spacer()
Spacer()
}
.padding(.leading)
}
.padding(.leading)
.frame(width: geometry.size.width)
Text("assistant_account_register")
.default_text_style_white_800(styleSize: 20)
.padding(.top, 20)
}
.frame(width: geometry.size.width)
.padding(.top, 35)
.padding(.bottom, 10)
Text("assistant_account_register")
.default_text_style_white_800(styleSize: 20)
.padding(.top, 20)
}
.padding(.top, 35)
.padding(.bottom, 10)
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Image("confirm_sms_code_illu")
.padding(.bottom, -geometry.safeAreaInsets.bottom)
}
}
VStack(alignment: .center) {
Spacer()
Text(String(format: NSLocalizedString("assistant_account_creation_sms_confirmation_explanation", comment: ""), registerViewModel.phoneNumber))
.default_text_style(styleSize: 15)
.foregroundStyle(Color.grayMain2c700)
.padding(.horizontal, 10)
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
ZStack {
VStack {
ZStack {
HStack(spacing: spaceBetweenBoxes) {
otpText(text: viewModel.otp1, focused: viewModel.otpField.isEmpty)
otpText(text: viewModel.otp2, focused: viewModel.otpField.count == 1)
otpText(text: viewModel.otp3, focused: viewModel.otpField.count == 2)
otpText(text: viewModel.otp4, focused: viewModel.otpField.count == 3)
}
TextField("", text: $viewModel.otpField)
.default_text_style_600(styleSize: 80)
.frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight)
.textContentType(.oneTimeCode)
.foregroundColor(.clear)
.accentColor(.clear)
.background(.clear)
.keyboardType(.numberPad)
.focused($isFocused)
.onChange(of: viewModel.otpField) { _ in
limitText(textLimit)
}
Spacer()
HStack {
Spacer()
Image("confirm_sms_code_illu")
.padding(.bottom, -geometry.safeAreaInsets.bottom)
}
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, 20)
Button(action: {
dismiss()
}, label: {
Text("assistant_account_creation_wrong_phone_number")
.default_text_style_orange_600(styleSize: 15)
.frame(height: 35)
})
.padding(.horizontal, 15)
.padding(.vertical, 5)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.orangeMain500, lineWidth: 1)
)
.padding(.bottom)
.frame(maxWidth: .infinity)
Spacer()
Spacer()
VStack(alignment: .center) {
Spacer()
Text(String(format: NSLocalizedString("assistant_account_creation_sms_confirmation_explanation", comment: ""), registerViewModel.phoneNumber))
.default_text_style(styleSize: 15)
.foregroundStyle(Color.grayMain2c700)
.padding(.horizontal, 10)
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
VStack {
ZStack {
HStack(spacing: spaceBetweenBoxes) {
otpText(text: registerViewModel.otp1, focused: registerViewModel.otpField.isEmpty)
otpText(text: registerViewModel.otp2, focused: registerViewModel.otpField.count == 1)
otpText(text: registerViewModel.otp3, focused: registerViewModel.otpField.count == 2)
otpText(text: registerViewModel.otp4, focused: registerViewModel.otpField.count == 3)
}
TextField("", text: $registerViewModel.otpField)
.default_text_style_600(styleSize: 80)
.frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight)
.textContentType(.oneTimeCode)
.foregroundColor(.clear)
.accentColor(.clear)
.background(.clear)
.keyboardType(.numberPad)
.focused($isFocused)
.onChange(of: registerViewModel.otpField) { _ in
limitText(textLimit)
if registerViewModel.otpField.count > 3 {
registerViewModel.validateCode()
}
}
}
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, 20)
Button(action: {
dismiss()
}, label: {
Text("assistant_account_creation_wrong_phone_number")
.default_text_style_orange_600(styleSize: 15)
.frame(height: 35)
})
.padding(.horizontal, 15)
.padding(.vertical, 5)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.orangeMain500, lineWidth: 1)
)
.padding(.bottom)
.frame(maxWidth: .infinity)
Spacer()
Spacer()
}
.frame(maxWidth: sharedMainViewModel.maxWidth)
.padding(.horizontal, 20)
}
.frame(maxWidth: sharedMainViewModel.maxWidth)
.padding(.horizontal, 20)
}
.frame(minHeight: geometry.size.height)
.onAppear {
registerViewModel.otpField = ""
}
}
.frame(minHeight: geometry.size.height)
if registerViewModel.createInProgress {
PopupLoadingView()
.background(.black.opacity(0.65))
}
}
}
.navigationTitle("")
@ -175,54 +187,12 @@ struct RegisterCodeConfirmationFragment: View {
}
func limitText(_ upper: Int) {
if viewModel.otpField.count > upper {
viewModel.otpField = String(viewModel.otpField.prefix(upper))
if registerViewModel.otpField.count > upper {
registerViewModel.otpField = String(registerViewModel.otpField.prefix(upper))
}
}
}
class ViewModel: ObservableObject {
@Published var otpField = "" {
didSet {
guard otpField.count <= 5,
otpField.last?.isNumber ?? true else {
otpField = oldValue
return
}
}
}
var otp1: String {
guard otpField.count >= 1 else {
return ""
}
return String(Array(otpField)[0])
}
var otp2: String {
guard otpField.count >= 2 else {
return ""
}
return String(Array(otpField)[1])
}
var otp3: String {
guard otpField.count >= 3 else {
return ""
}
return String(Array(otpField)[2])
}
var otp4: String {
guard otpField.count >= 4 else {
return ""
}
return String(Array(otpField)[3])
}
@Published var borderColor: Color = .black
var successCompletionHandler: (() -> Void)?
@Published var showResendText = false
}
#Preview {
RegisterCodeConfirmationFragment(registerViewModel: RegisterViewModel())
}

View file

@ -31,7 +31,6 @@ struct RegisterFragment: View {
@FocusState var isPhoneNumberFocused: Bool
@FocusState var isPasswordFocused: Bool
@State private var isLinkActive = false
@State private var isShowPopup = false
var body: some View {
@ -103,14 +102,14 @@ struct RegisterFragment: View {
HStack {
Menu {
Picker("", selection: $registerViewModel.dialPlanSelected) {
Picker("", selection: $registerViewModel.dialPlanValueSelected) {
ForEach(Array(registerViewModel.dialPlansLabelList.enumerated()), id: \.offset) { index, dialPlan in
Text(dialPlan).tag(registerViewModel.dialPlansShortLabelList[index])
}
}
} label: {
HStack {
Text(registerViewModel.dialPlanSelected)
Text(registerViewModel.dialPlanValueSelected)
Image("caret-down")
.renderingMode(.template)
@ -183,7 +182,7 @@ struct RegisterFragment: View {
)
.padding(.bottom)
NavigationLink(isActive: $isLinkActive, destination: {
NavigationLink(isActive: $registerViewModel.isLinkActive, destination: {
RegisterCodeConfirmationFragment(registerViewModel: registerViewModel)
}, label: {
Text("assistant_account_create")
@ -196,7 +195,7 @@ struct RegisterFragment: View {
.padding(.vertical, 10)
.background((registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500)
.cornerRadius(60)
.disabled(!isLinkActive)
.disabled(!registerViewModel.isLinkActive)
.padding(.bottom)
.simultaneousGesture(
TapGesture().onEnded {
@ -283,7 +282,9 @@ struct RegisterFragment: View {
titleSecondButton: Text("Continue"),
actionSecondButton: {
self.isShowPopup = false
self.isLinkActive = true
registerViewModel.createInProgress = true
registerViewModel.startAccountCreation()
registerViewModel.phoneNumberConfirmedByUser()
}
)
.background(.black.opacity(0.65))
@ -292,6 +293,10 @@ struct RegisterFragment: View {
}
}
if registerViewModel.createInProgress {
PopupLoadingView()
.background(.black.opacity(0.65))
}
}
}
.navigationTitle("")

View file

@ -19,29 +19,174 @@
import Foundation
import linphonesw
import Combine
class RegisterViewModel: ObservableObject {
static let TAG = "[RegisterViewModel]"
let accountTokenNotification = Notification.Name("AccountCreationTokenReceived")
private var coreContext = CoreContext.shared
@Published var username: String = ""
@Published var usernameError: String = ""
@Published var phoneNumber: String = ""
@Published var phoneNumberError: String = ""
@Published var passwd: String = ""
@Published var passwordError: String = ""
@Published var domain: String = "sip.linphone.org"
@Published var displayName: String = ""
@Published var transportType: String = "TLS"
@Published var dialPlanSelected: String = "🇫🇷 +33"
@Published var dialPlanValueSelected: String = "🇫🇷 +33"
@Published var dialPlansList: [DialPlan] = []
@Published var dialPlansLabelList: [String] = []
@Published var dialPlansShortLabelList: [String] = []
private let HASHALGORITHM = "SHA-256"
private var accountManagerServices: AccountManagerServices?
private var accountCreationToken: String?
private var accountCreatedAuthInfo: AuthInfo?
private var accountCreated: Account?
private var normalizedPhoneNumber: String?
private var accountManagerServicesSuscriptions = Set<AnyCancellable?>()
private var mCoreSuscriptions = Set<AnyCancellable?>()
@Published var isLinkActive: Bool = false
@Published var createInProgress: Bool = false
@Published var otpField = "" {
didSet {
guard otpField.count <= 5,
otpField.last?.isNumber ?? true else {
otpField = oldValue
return
}
}
}
var otp1: String {
guard otpField.count >= 1 else {
return ""
}
return String(Array(otpField)[0])
}
var otp2: String {
guard otpField.count >= 2 else {
return ""
}
return String(Array(otpField)[1])
}
var otp3: String {
guard otpField.count >= 3 else {
return ""
}
return String(Array(otpField)[2])
}
var otp4: String {
guard otpField.count >= 4 else {
return ""
}
return String(Array(otpField)[3])
}
init() {
getDialPlansList()
getAccountCreationToken()
}
func addDelegate(core: Core) {
self.accountManagerServicesSuscriptions.insert(self.accountManagerServices!.publisher?.onRequestSuccessful?.postOnCoreQueue {
(ams: AccountManagerServices, request: AccountManagerServices.Request, data: String) in
Log.info("\(RegisterViewModel.TAG) Request \(request) was successful, data is \(data)")
switch request {
case AccountManagerServices.Request.CreateAccountUsingToken:
if !data.isEmpty {
self.storeAccountInCore(core: core, identity: data)
self.sendCodeBySms()
} else {
Log.error(
"\(RegisterViewModel.TAG) No data found for createAccountUsingToken request, can't continue!"
)
}
case AccountManagerServices.Request.SendPhoneNumberLinkingCodeBySms:
DispatchQueue.main.async {
self.createInProgress = false
self.isLinkActive = true
}
case AccountManagerServices.Request.LinkPhoneNumberUsingCode:
let account = self.accountCreated
if account != nil {
Log.info(
"\(RegisterViewModel.TAG) Account \(account?.params?.identityAddress?.asStringUriOnly()) has been created & activated, setting it as default"
)
if let assistantLinphone = Bundle.main.path(forResource: "assistant_linphone_default_values", ofType: nil) {
core.loadConfigFromXml(xmlUri: assistantLinphone)
}
DispatchQueue.main.async {
self.createInProgress = false
}
do {
try core.addAccount(account: account!)
core.defaultAccount = account
} catch {
}
}
default: break
}
})
self.accountManagerServicesSuscriptions.insert(self.accountManagerServices!.publisher?.onRequestError?.postOnCoreQueue {
(ams: AccountManagerServices, request: AccountManagerServices.Request, statusCode: Int, errorMessage: String, parameterErrors: Dictionary?) in
Log.error(
"\(RegisterViewModel.TAG) Request \(request) returned an error with status code \(statusCode) and message \(errorMessage)"
)
if !errorMessage.isEmpty {
DispatchQueue.main.async {
ToastViewModel.shared.toastMessage = "Error: \(errorMessage)"
ToastViewModel.shared.displayToast = true
}
}
switch request {
case AccountManagerServices.Request.SendAccountCreationTokenByPush:
Log.warn("\(RegisterViewModel.TAG) Cancelling job waiting for push notification")
default: break
}
DispatchQueue.main.async {
self.createInProgress = false
}
})
NotificationCenter.default.addObserver(forName: accountTokenNotification, object: nil, queue: nil) { notification in
if !(self.username.isEmpty || self.passwd.isEmpty) {
if let token = notification.userInfo?["token"] as? String {
if !token.isEmpty {
self.accountCreationToken = token
Log.info(
"\(RegisterViewModel.TAG) Extracted token \(self.accountCreationToken ?? "Error token") from push payload, creating account"
)
self.createAccount()
} else {
Log.error("\(RegisterViewModel.TAG) Push payload JSON object has an empty 'token'!")
self.onFlexiApiTokenRequestError()
}
}
}
}
}
func getDialPlansList() {
coreContext.doOnCoreQueue { core in
coreContext.doOnCoreQueue { _ in
let dialPlans = Factory.Instance.dialPlans
dialPlans.forEach { dialPlan in
self.dialPlansList.append(dialPlan)
@ -54,4 +199,232 @@ class RegisterViewModel: ObservableObject {
}
}
}
func getAccountCreationToken() {
coreContext.doOnCoreQueue { core in
do {
self.accountManagerServices = try core.createAccountManagerServices()
if self.accountManagerServices != nil {
self.accountManagerServices!.language = Locale.current.identifier
self.addDelegate(core: core)
}
} catch {
}
}
}
func startAccountCreation() {
coreContext.doOnCoreQueue { core in
if self.accountCreationToken == nil {
Log.info("\(RegisterViewModel.TAG) We don't have a creation token, let's request one")
self.requestFlexiApiToken(core: core)
} else {
let authInfo = self.accountCreatedAuthInfo
if authInfo != nil {
Log.info("\(RegisterViewModel.TAG) Account has already been created, requesting SMS to be sent")
self.sendCodeBySms()
} else {
Log.info("\(RegisterViewModel.TAG) We've already have a token \(self.accountCreationToken ?? ""), continuing")
self.createAccount()
}
}
}
}
func storeAccountInCore(core: Core, identity: String) {
do {
let passwordValue = passwd
let sipIdentity = try Factory.Instance.createAddress(addr: identity)
// We need to have an AuthInfo for newly created account to authorize phone number linking request
let authInfo = try Factory.Instance.createAuthInfo(
username: sipIdentity.username ?? "Error username",
userid: nil,
passwd: passwordValue,
ha1: nil,
realm: nil,
domain: sipIdentity.domain
)
core.addAuthInfo(info: authInfo)
Log.info("\(RegisterViewModel.TAG) Auth info for SIP identity \(sipIdentity.asStringUriOnly()) created & added")
var dialPlan: DialPlan?
dialPlansList.forEach { dial in
let countryCode = dialPlanValueSelected.components(separatedBy: "+")
if dial.countryCallingCode == countryCode[1] {
dialPlan = dial
}
}
let accountParams = try core.createAccountParams()
try accountParams.setIdentityaddress(newValue: sipIdentity)
if dialPlan != nil {
let dialPlanTmp = dialPlan?.internationalCallPrefix ?? "Error international call prefix"
let isoCountryCodeTmp = dialPlan?.isoCountryCode ?? "Error iso country code"
Log.info(
"\(RegisterViewModel.TAG) Setting international prefix \(dialPlanTmp) and country \(isoCountryCodeTmp) to account params"
)
accountParams.internationalPrefix = dialPlan!.internationalCallPrefix
accountParams.internationalPrefixIsoCountryCode = dialPlan!.isoCountryCode
}
let account = try core.createAccount(params: accountParams)
Log.info("\(RegisterViewModel.TAG) Account for SIP identity \(sipIdentity.asStringUriOnly()) created & added")
accountCreatedAuthInfo = authInfo
accountCreated = account
} catch let error {
Log.error("\(RegisterViewModel.TAG) Failed to create address from SIP Identity \(identity)!")
Log.error("\(RegisterViewModel.TAG) Error is \(error)")
}
}
func requestFlexiApiToken(core: Core) {
if !core.isPushNotificationAvailable {
Log.error(
"\(RegisterViewModel.TAG) Core says push notification aren't available, can't request a token from FlexiAPI"
)
self.onFlexiApiTokenRequestError()
return
}
let pushConfig = core.pushNotificationConfig
if pushConfig != nil && self.accountManagerServices != nil {
pushConfig!.provider = "apns.dev"
var formatedPnParam = pushConfig!.param
formatedPnParam = formatedPnParam?.replacingOccurrences(of: "voip&remote", with: "remote")
pushConfig!.param = formatedPnParam
let coreRemoteToken = pushConfig!.remoteToken
var formatedRemoteToken = ""
if coreRemoteToken != nil {
formatedRemoteToken = String(coreRemoteToken!.prefix(64))
pushConfig!.prid = formatedRemoteToken.uppercased()
self.accountManagerServices!.requestAccountCreationTokenByPush(pnProvider: pushConfig?.provider ?? "", pnParam: pushConfig?.param ?? "", pnPrid: pushConfig?.prid ?? "")
} else {
Log.warn("\(RegisterViewModel.TAG) No remote push token available in core for account creator configuration")
}
Log.info("\(RegisterViewModel.TAG) Found push notification info: provider \("apns.dev"), param \(formatedPnParam ?? "error") and prid \(formatedRemoteToken)")
} else {
Log.error("\(RegisterViewModel.TAG) No push configuration object in Core, shouldn't happen!")
self.onFlexiApiTokenRequestError()
}
}
func onFlexiApiTokenRequestError() {
Log.error("\(RegisterViewModel.TAG) Flexi API token request by push error!")
DispatchQueue.main.async {
ToastViewModel.shared.toastMessage = "Failed_push_notification_not_received_error"
ToastViewModel.shared.displayToast = true
}
}
func sendCodeBySms() {
let account = accountCreated
if accountManagerServices != nil && account != nil {
let phoneNumberValue = normalizedPhoneNumber
if phoneNumberValue == nil || phoneNumberValue!.isEmpty {
Log.error("\(RegisterViewModel.TAG) Phone number is null or empty, this shouldn't happen at this step!")
return
}
let identity = account!.params!.identityAddress
if identity != nil {
Log.info(
"\(RegisterViewModel.TAG) Account \(identity!.asStringUriOnly()) should now be created, asking account manager to send a confirmation code by SMS to \(phoneNumberValue ?? "")"
)
accountManagerServices!.requestPhoneNumberLinkingCodeBySms(
sipIdentity: identity!,
phoneNumber: phoneNumberValue!
)
}
}
}
func createAccount() {
if accountManagerServices != nil {
let token = accountCreationToken
if token == nil || (token != nil && token!.isEmpty) {
Log.error("\(RegisterViewModel.TAG) No account creation token, can't create account!")
return
}
if username.isEmpty || passwd.isEmpty {
Log.error("\(RegisterViewModel.TAG) Either username \(username) or password is null or empty!")
return
}
Log.info(
"\(RegisterViewModel.TAG) Account creation token is \(token ?? "Error token"), creating account with username \(username) and algorithm \(HASHALGORITHM)"
)
do {
try accountManagerServices!.createAccountUsingToken(
username: username,
password: passwd,
algorithm: HASHALGORITHM,
token: token!
)
} catch {
Log.info(
"\(RegisterViewModel.TAG) Can't create account using token"
)
}
}
}
func phoneNumberConfirmedByUser() {
coreContext.doOnCoreQueue { core in
if self.accountManagerServices != nil {
var dialPlan: DialPlan?
self.dialPlansList.forEach { dial in
let countryCode = self.dialPlanValueSelected.components(separatedBy: "+")
Log.info("dialPlansListdialPlansList \(dial.countryCallingCode) \(countryCode[1])")
if dial.countryCallingCode == countryCode[1] {
dialPlan = dial
}
}
if (dialPlan == nil) {
Log.error("\(RegisterViewModel.TAG) No dial plan (country) selected!")
}
let number = self.phoneNumber
let formattedPhoneNumber = dialPlan?.formatPhoneNumber(phoneNumber: number, escapePlus: false)
Log.info(
"\(RegisterViewModel.TAG) Formatted phone number \(number) using dial plan \(dialPlan?.country ?? "Error country") is \(formattedPhoneNumber ?? "Error phone number")"
)
self.normalizedPhoneNumber = formattedPhoneNumber
} else {
Log.error("\(RegisterViewModel.TAG) Account manager services hasn't been initialized!")
DispatchQueue.main.async {
ToastViewModel.shared.toastMessage = "Failed_account_register_unexpected_error"
ToastViewModel.shared.displayToast = true
}
}
}
}
func validateCode() {
createInProgress = true
let account = accountCreated
if accountManagerServices != nil && account != nil {
let code = otpField
let identity = account!.params?.identityAddress
if identity != nil {
Log.info(
"\(RegisterViewModel.TAG) Activating account using code \(code) for account \(identity!.asStringUriOnly())"
)
accountManagerServices!.linkPhoneNumberToAccountUsingCode(sipIdentity: identity!, code: code)
}
}
}
}

View file

@ -400,7 +400,7 @@ class ConversationViewModel: ObservableObject {
let isFirstMessageTmp = (eventLog.chatMessage?.isOutgoing ?? false) ? isFirstMessageOutgoingTmp : isFirstMessageIncomingTmp
let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount
let unreadMessagesCount = self.displayedConversation != nil ? self.displayedConversation!.chatRoom.unreadMessagesCount : 0
var statusTmp: Message.Status? = .sending
switch eventLog.chatMessage?.state {

View file

@ -171,6 +171,27 @@ struct ToastView: View {
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed_push_notification_not_received_error":
Text("assistant_account_register_push_notification_not_received_error")
.multilineTextAlignment(.center)
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed_account_register_unexpected_error":
Text("assistant_account_register_unexpected_error")
.multilineTextAlignment(.center)
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
case let str where str.contains("Error: "):
Text(toastViewModel.toastMessage)
.multilineTextAlignment(.center)
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
default:
Text("Error")