forked from mirrors/linphone-iphone
Add Account creation feature (Register)
Fix unreadMessagesCount when displayedConversation is null Change user agent
This commit is contained in:
parent
2c28474ca5
commit
02a89a08c3
9 changed files with 569 additions and 174 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" : {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue