Add advanced settings to third-party SIP account login view

This commit is contained in:
Benoit Martins 2025-09-24 19:17:29 +02:00
parent 1d0df11c61
commit 4cf1dbd8b5
8 changed files with 163 additions and 22 deletions

View file

@ -93,6 +93,7 @@
"assistant_third_party_sip_account_warning_explanation" = "Some features require a %@ account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us.";
"assistant_third_party_sip_account_warning_ok" = "I understand";
"assistant_web_platform_link" = "subscribe.linphone.org";
"authentication_id" = "Authentication ID (if different)";
"bottom_navigation_calls_label" = "Calls";
"bottom_navigation_contacts_label" = "Contacts";
"bottom_navigation_conversations_label" = "Conversations";

View file

@ -93,6 +93,7 @@
"assistant_third_party_sip_account_warning_explanation" = "Certaines fonctionnalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte %@.\n\nCes fonctionnalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter.";
"assistant_third_party_sip_account_warning_ok" = "Jai compris";
"assistant_web_platform_link" = "subscribe.linphone.org";
"authentication_id" = "Identifiant de connexion (si différent)";
"bottom_navigation_calls_label" = "Appels";
"bottom_navigation_contacts_label" = "Contacts";
"bottom_navigation_conversations_label" = "Conversations";

View file

@ -18,12 +18,14 @@
*/
import SwiftUI
import Combine
struct LoginFragment: View {
@ObservedObject private var coreContext = CoreContext.shared
@StateObject private var accountLoginViewModel = AccountLoginViewModel()
@StateObject private var keyboard = KeyboardResponder()
@State private var isSecured: Bool = true
@ -377,6 +379,7 @@ struct LoginFragment: View {
.clipped()
}
.frame(minHeight: geometry.size.height)
.padding(.bottom, keyboard.currentHeight)
}
func acceptGeneralTerms() {

View file

@ -26,6 +26,8 @@ struct RegisterFragment: View {
@ObservedObject var registerViewModel: RegisterViewModel
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
@StateObject private var keyboard = KeyboardResponder()
@Environment(\.dismiss) var dismiss
@State private var isSecured: Bool = true
@ -181,14 +183,6 @@ struct RegisterFragment: View {
.autocapitalization(.none)
.padding(.leading, 5)
.keyboardType(.numberPad)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
.onChange(of: registerViewModel.phoneNumber) { _ in
if !registerViewModel.phoneNumberError.isEmpty {
registerViewModel.phoneNumberError = ""
@ -355,11 +349,8 @@ struct RegisterFragment: View {
.clipped()
}
.frame(minHeight: geometry.size.height)
.padding(.bottom, keyboard.currentHeight)
}
}
#Preview {
RegisterFragment(registerViewModel: RegisterViewModel())
}
// swiftlint:enable line_length

View file

@ -24,25 +24,61 @@ struct ThirdPartySipAccountLoginFragment: View {
@ObservedObject private var coreContext = CoreContext.shared
@ObservedObject var accountLoginViewModel: AccountLoginViewModel
@StateObject private var keyboard = KeyboardResponder()
@Environment(\.dismiss) var dismiss
@State private var isSecured: Bool = true
@State private var advancedSettingsIsOpen: Bool = false
@FocusState var isNameFocused: Bool
@FocusState var isPasswordFocused: Bool
@FocusState var isDomainFocused: Bool
@FocusState var isDisplayNameFocused: Bool
@FocusState var isSipProxyUrlFocused: Bool
@FocusState var isAuthIdFocused: Bool
@FocusState var isOutboundProxyFocused: Bool
var body: some View {
GeometryReader { geometry in
if #available(iOS 16.4, *) {
ScrollView(.vertical) {
innerScrollView(geometry: geometry)
}
.scrollBounceBehavior(.basedOnSize)
} else {
ScrollView(.vertical) {
innerScrollView(geometry: geometry)
ScrollViewReader { proxy in
if #available(iOS 16.4, *) {
ScrollView(.vertical) {
innerScrollView(geometry: geometry)
}
.scrollBounceBehavior(.basedOnSize)
.onChange(of: isAuthIdFocused) { field in
if field {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
proxy.scrollTo(2, anchor: .top)
}
}
}
.onChange(of: isOutboundProxyFocused) { field in
if field {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
proxy.scrollTo(2, anchor: .top)
}
}
}
} else {
ScrollView(.vertical) {
innerScrollView(geometry: geometry)
}
.onChange(of: isAuthIdFocused) { field in
if field {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
proxy.scrollTo(2, anchor: .top)
}
}
}
.onChange(of: isOutboundProxyFocused) { field in
if field {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
proxy.scrollTo(2, anchor: .top)
}
}
}
}
}
}
@ -208,6 +244,74 @@ struct ThirdPartySipAccountLoginFragment: View {
.stroke(Color.gray200, lineWidth: 1)
)
.padding(.bottom)
HStack(alignment: .center) {
Text("settings_advanced_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Image(advancedSettingsIsOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.top, 10)
.padding(.bottom, 10)
.background(.white)
.onTapGesture {
withAnimation {
advancedSettingsIsOpen.toggle()
}
}
if advancedSettingsIsOpen {
VStack(alignment: .leading) {
Text("authentication_id")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("authentication_id", text: $accountLoginViewModel.authId)
.id(1)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(.white)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isAuthIdFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isAuthIdFocused)
}
VStack(alignment: .leading) {
Text("account_settings_sip_proxy_url_title")
.default_text_style_700(styleSize: 15)
.padding(.bottom, -5)
TextField("account_settings_sip_proxy_url_title", text: $accountLoginViewModel.outboundProxy)
.id(2)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(.white)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isOutboundProxyFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.focused($isOutboundProxyFocused)
}
.padding(.bottom)
}
}
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
.padding(.horizontal, 20)
@ -241,6 +345,7 @@ struct ThirdPartySipAccountLoginFragment: View {
.clipped()
}
.frame(minHeight: geometry.size.height)
.padding(.bottom, keyboard.currentHeight)
}
}

View file

@ -29,6 +29,8 @@ class AccountLoginViewModel: ObservableObject {
@Published var domain: String = "sip.linphone.org"
@Published var displayName: String = ""
@Published var transportType: String = "TLS"
@Published var authId: String = ""
@Published var outboundProxy: String = ""
private var mCoreDelegate: CoreDelegate!
@ -83,7 +85,7 @@ class AccountLoginViewModel: ObservableObject {
// The realm will be determined automatically from the first register, as well as the algorithm
let authInfo = try Factory.Instance.createAuthInfo(
username: self.username,
userid: "",
userid: self.authId,
passwd: self.passwd,
ha1: "",
realm: "",
@ -100,7 +102,15 @@ class AccountLoginViewModel: ObservableObject {
try accountParams.setIdentityaddress(newValue: identity)
// We also need to configure where the proxy server is located
let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
var serverAddress: Address
if (!self.outboundProxy.isEmpty) {
let server = self.outboundProxy.starts(with: "sip:") ? self.outboundProxy : String("sip:" + self.outboundProxy)
serverAddress = try Factory.Instance.createAddress(addr: server)
} else {
serverAddress = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
}
let address = serverAddress
// We use the Address object to easily set the transport protocol
try address.setTransport(newValue: transport)
@ -156,6 +166,8 @@ class AccountLoginViewModel: ObservableObject {
DispatchQueue.main.async {
self.domain = "sip.linphone.org"
self.transportType = "TLS"
self.authId = ""
self.outboundProxy = ""
}
} catch { NSLog(error.localizedDescription) }

View file

@ -0,0 +1,24 @@
import Foundation
import UIKit
import Combine
final class KeyboardResponder: ObservableObject {
@Published var currentHeight: CGFloat = 0
private var cancellables: Set<AnyCancellable> = []
init() {
let willShow = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
.map { notification -> CGFloat in
(notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
}
let willHide = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
Publishers.Merge(willShow, willHide)
.receive(on: RunLoop.main)
.assign(to: \.currentHeight, on: self)
.store(in: &cancellables)
}
}

View file

@ -108,6 +108,7 @@
D73449992BC6932A00778C56 /* MeetingWaitingRoomFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73449982BC6932A00778C56 /* MeetingWaitingRoomFragment.swift */; };
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */; };
D737AEEF2DA011F2005C1280 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D737AEED2DA011F2005C1280 /* Localizable.strings */; };
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */; };
D7458F392E0BDCF4000C957A /* linphoneExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; };
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; };
@ -334,6 +335,7 @@
D734499A2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingWaitingRoomViewModel.swift; sourceTree = "<group>"; };
D737AEEE2DA011F2005C1280 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable/en.lproj/Localizable.strings; sourceTree = "<group>"; };
D737AEF02DA01203005C1280 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable/fr.lproj/Localizable.strings; sourceTree = "<group>"; };
D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardResponder.swift; sourceTree = "<group>"; };
D7458F2F2E0BDCF4000C957A /* linphoneExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = linphoneExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = "<group>"; };
D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = "<group>"; };
@ -583,6 +585,7 @@
D717071C2AC591EF0037746F /* Utils */ = {
isa = PBXGroup;
children = (
D738ACED2E857BEF0039F7D1 /* KeyboardResponder.swift */,
D7DF8BE82E2104E5003A3BC7 /* EmojiPickerView.swift */,
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */,
D717A10D2CEB770D00849D92 /* ShareSheetController.swift */,
@ -1279,6 +1282,7 @@
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */,
D717A10E2CEB772300849D92 /* ShareSheetController.swift in Sources */,
66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */,
D738ACEE2E857BF10039F7D1 /* KeyboardResponder.swift in Sources */,
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */,
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */,
D7343FEF2D3FE16C0059D784 /* HelpViewModel.swift in Sources */,