Enable support for multiple account

This commit is contained in:
Benoit Martins 2025-07-08 16:41:24 +02:00
parent c97e0045c8
commit 60352dbc23
10 changed files with 185 additions and 110 deletions

View file

@ -371,6 +371,19 @@ class CoreContext: ObservableObject {
}
}
}, onDefaultAccountChanged: { (_: Core, account: Account?) in
Log.info("[CoreContext][onDefaultAccountChanged] Default account set to: \(account?.displayName() ?? "none")")
if let account = account {
DispatchQueue.main.async {
for accountModel in self.accounts {
accountModel.isDefaultAccount = accountModel.account == account
}
}
}
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name("DefaultAccountChanged"), object: nil)
}
ContactsManager.shared.fetchContacts()
}, onAccountAdded: { (_: Core, acc: Account) in
self.forceRemotePushToMatchVoipPushSettings(account: acc)
@ -390,6 +403,7 @@ class CoreContext: ObservableObject {
self.accounts = accountModels
}
})
self.mCore.addDelegate(delegate: self.mCoreDelegate)
self.mCore.autoIterateEnabled = true

View file

@ -147,7 +147,6 @@ struct LoginFragment: View {
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
@ -191,7 +190,6 @@ struct LoginFragment: View {
.frame(width: 20, height: 20)
})
}
.disabled(coreContext.loggedIn)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
@ -207,7 +205,7 @@ struct LoginFragment: View {
self.accountLoginViewModel.login()
coreContext.loggingInProgress = true
}, label: {
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
Text("assistant_account_login")
.default_text_style_white_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)

View file

@ -88,7 +88,6 @@ struct ThirdPartySipAccountLoginFragment: View {
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
@ -131,7 +130,6 @@ struct ThirdPartySipAccountLoginFragment: View {
.frame(width: 20, height: 20)
})
}
.disabled(coreContext.loggedIn)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
@ -150,7 +148,6 @@ struct ThirdPartySipAccountLoginFragment: View {
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
@ -171,7 +168,6 @@ struct ThirdPartySipAccountLoginFragment: View {
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
@ -221,7 +217,7 @@ struct ThirdPartySipAccountLoginFragment: View {
Button(action: {
self.accountLoginViewModel.login()
}, label: {
Text(coreContext.loggedIn ? "manage_account_delete" : "assistant_account_login")
Text("assistant_account_login")
.default_text_style_white_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)

View file

@ -355,8 +355,8 @@ struct ContentView: View {
VStack(spacing: 0) {
if searchIsActive == false {
HStack {
if let accountModelIndex = accountProfileViewModel.accountModelIndex,
accountModelIndex < coreContext.accounts.count {
if let defaultAccountModelIndex = accountProfileViewModel.defaultAccountModelIndex,
defaultAccountModelIndex < coreContext.accounts.count {
AsyncImage(url: imagePath) { image in
switch image {
case .empty:
@ -372,9 +372,9 @@ struct ContentView: View {
imageTmp = image
}
case .failure:
if coreContext.accounts[accountModelIndex].avatarModel != nil {
if coreContext.accounts[defaultAccountModelIndex].avatarModel != nil {
let tmpImage = contactsManager.textToImage(
firstName: coreContext.accounts[accountModelIndex].avatarModel!.name,
firstName: coreContext.accounts[defaultAccountModelIndex].avatarModel!.name,
lastName: "")
Image(uiImage: tmpImage)
.resizable()
@ -398,14 +398,14 @@ struct ContentView: View {
openMenu()
}
.onAppear {
let imagePathTmp = coreContext.accounts[accountModelIndex].getImagePath()
let imagePathTmp = coreContext.accounts[defaultAccountModelIndex].getImagePath()
if !(imagePathTmp.lastPathComponent.isEmpty || imagePathTmp.lastPathComponent == "Error" || imagePathTmp.lastPathComponent == "ImageError.png") {
imagePath = imagePathTmp
}
}
.onChange(of: coreContext.accounts[accountModelIndex].usernaneAvatar) { username in
.onChange(of: coreContext.accounts[defaultAccountModelIndex].usernaneAvatar) { username in
if !username.isEmpty {
let imagePathTmp = coreContext.accounts[accountModelIndex].getImagePath()
let imagePathTmp = coreContext.accounts[defaultAccountModelIndex].getImagePath()
if !(imagePathTmp.lastPathComponent.isEmpty || imagePathTmp.lastPathComponent == "Error" || imagePathTmp.lastPathComponent == "ImageError.png") {
sharedMainViewModel.changeDefaultAvatar(defaultAvatarURL: imagePathTmp)
imagePath = imagePathTmp
@ -413,8 +413,8 @@ struct ContentView: View {
}
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ImageChanged"))) { _ in
if !coreContext.accounts[accountModelIndex].usernaneAvatar.isEmpty {
let imagePathTmp = coreContext.accounts[accountModelIndex].getImagePath()
if !coreContext.accounts[defaultAccountModelIndex].usernaneAvatar.isEmpty {
let imagePathTmp = coreContext.accounts[defaultAccountModelIndex].getImagePath()
sharedMainViewModel.changeDefaultAvatar(defaultAvatarURL: imagePathTmp)
imagePath = imagePathTmp
}
@ -1368,6 +1368,31 @@ struct ContentView: View {
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CoreStarted"))) { _ in
accountProfileViewModel.setAvatarModel()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("DefaultAccountChanged"))) { _ in
accountProfileViewModel.defaultAccountModelIndex = CoreContext.shared.accounts.firstIndex(where: {$0.isDefaultAccount})
withAnimation {
if self.sideMenuIsOpen {
self.sideMenuIsOpen = false
}
}
if self.isShowLoginFragment {
self.isShowLoginFragment = false
}
if conversationsListViewModel != nil {
conversationsListViewModel = ConversationsListViewModel()
}
if historyListViewModel != nil {
historyListViewModel = HistoryListViewModel()
}
if meetingsListViewModel != nil {
meetingsListViewModel = MeetingsListViewModel()
}
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("PasswordUpdate")).compactMap { $0.userInfo?["address"] as? String }) { address in
passwordUpdateAddress = address
isShowUpdatePasswordPopup = true

View file

@ -202,56 +202,72 @@ class ConversationsListViewModel: ObservableObject {
func addConversationDelegate() {
coreContext.doOnCoreQueue { core in
self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (_: Core, chatRoom: ChatRoom, _: [ChatMessage]) in
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
}
self.conversationsList.insert(model, at: 0)
}
self.updateUnreadMessagesCount()
self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (core: Core, chatRoom: ChatRoom, _: [ChatMessage]) in
if let defaultAddress = core.defaultAccount?.contactAddress,
let localAddress = chatRoom.localAddress,
defaultAddress.weakEqual(address2: localAddress) {
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
}
self.conversationsList.insert(model, at: 0)
}
self.updateUnreadMessagesCount()
}
}, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
if index != nil {
self.conversationsList[index!].chatMessageRemoveDelegate()
}
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
}
self.conversationsList.insert(model, at: 0)
}
self.updateUnreadMessagesCount()
if let defaultAddress = core.defaultAccount?.contactAddress,
let localAddress = chatRoom.localAddress,
defaultAddress.weakEqual(address2: localAddress) {
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
if index != nil {
self.conversationsList[index!].chatMessageRemoveDelegate()
}
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
}
self.conversationsList.insert(model, at: 0)
}
self.updateUnreadMessagesCount()
}
}, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
self.conversationsList.insert(model, at: index!)
} else {
self.conversationsList.insert(model, at: 0)
}
}
self.updateUnreadMessagesCount()
if let defaultAddress = core.defaultAccount?.contactAddress,
let localAddress = chatRoom.localAddress,
defaultAddress.weakEqual(address2: localAddress) {
let model = ConversationModel(chatRoom: chatRoom)
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
DispatchQueue.main.async {
if index != nil {
self.conversationsList.remove(at: index!)
self.conversationsList.insert(model, at: index!)
} else {
self.conversationsList.insert(model, at: 0)
}
}
self.updateUnreadMessagesCount()
}
}, onChatRoomStateChanged: { (core: Core, chatroom: ChatRoom, state: ChatRoom.State) in
// Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]")
if core.globalState == .On {
switch state {
case .Created:
self.addChatRoom(chatRoom: chatroom)
case .Deleted:
self.removeChatRoom(chatRoom: chatroom)
default:
break
}
}
if let defaultAddress = core.defaultAccount?.contactAddress,
let localAddress = chatroom.localAddress,
defaultAddress.weakEqual(address2: localAddress) {
if core.globalState == .On {
switch state {
case .Created:
self.addChatRoom(chatRoom: chatroom)
case .Deleted:
self.removeChatRoom(chatRoom: chatroom)
default:
break
}
}
}
})
core.addDelegate(delegate: self.coreConversationDelegate!)
}

View file

@ -73,7 +73,7 @@ struct SideMenu: View {
ForEach(0..<CoreContext.shared.accounts.count, id: \.self) { index in
SideMenuAccountRow(model: CoreContext.shared.accounts[index], isOpen: $isOpen, isShowAccountProfileFragment: $isShowAccountProfileFragment)
.background()
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.listRowSeparator(.hidden)
}
}

View file

@ -97,10 +97,11 @@ struct SideMenuAccountRow: View {
Menu {
Button {
withAnimation {
isOpen = false
isShowAccountProfileFragment = true
}
accountProfileViewModel.accountModelIndex = CoreContext.shared.accounts.firstIndex(where: {$0.displayName == model.displayName})
withAnimation {
isOpen = false
isShowAccountProfileFragment = true
}
} label: {
Label("drawer_menu_manage_account", systemImage: "arrow.right.circle")
}
@ -118,6 +119,10 @@ struct SideMenuAccountRow: View {
.padding(.bottom, 12)
}
.frame(height: 61)
.background(model.isDefaultAccount ? Color.grayMain2c100 : .clear)
.padding(.horizontal, 16)
.background(model.isDefaultAccount ? Color.grayMain2c100 : .white)
.onTapGesture {
model.setAsDefault()
}
}
}

View file

@ -54,9 +54,6 @@ class MeetingsListViewModel: ObservableObject {
if let account = core.defaultAccount {
confInfoList = account.conferenceInformationList
}
if confInfoList.isEmpty {
confInfoList = core.conferenceInformationList
}
var meetingsListTmp: [MeetingsListItemModel] = []
var meetingForTodayFound = false

View file

@ -28,7 +28,8 @@ class AccountProfileViewModel: ObservableObject {
var dialPlanSelected: DialPlan?
var dialPlansList: [DialPlan] = []
var accountModelIndex: Int? = 0
@Published var accountModelIndex: Int? = 0
@Published var defaultAccountModelIndex: Int? = 0
init() {
SharedMainViewModel.shared.getDialPlansList()
@ -73,43 +74,43 @@ class AccountProfileViewModel: ObservableObject {
}
}
func setAvatarModel() {
if accountModelIndex != nil {
CoreContext.shared.doOnCoreQueue { _ in
let displayNameTmp = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.identityAddress?.displayName ?? ""
let contactAddressTmp = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.identityAddress?.asStringUriOnly() ?? ""
let prefix = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.internationalPrefix ?? ""
let isoCountryCode = CoreContext.shared.accounts[self.accountModelIndex!].account.params?.internationalPrefixIsoCountryCode ?? ""
var dialPlanValueSelectedTmp = ""
if !prefix.isEmpty || !isoCountryCode.isEmpty {
Log.info(
"\(AccountProfileViewModel.TAG) Account \(CoreContext.shared.accounts[self.accountModelIndex!].account.params?.identityAddress?.asStringUriOnly() ?? "") prefix is \(prefix) \(isoCountryCode)"
)
self.dialPlansList = Factory.Instance.dialPlans
if let dialPlan = self.dialPlansList.first(where: { $0.isoCountryCode == isoCountryCode }) ??
self.dialPlansList.first(where: { $0.countryCallingCode == prefix }) {
dialPlanValueSelectedTmp = "\(dialPlan.flag) \(dialPlan.country) | +\(dialPlan.countryCallingCode)"
}
}
let accountDisplayName = CoreContext.shared.accounts[self.accountModelIndex!].account.displayName()
DispatchQueue.main.async {
CoreContext.shared.accounts[self.accountModelIndex!].avatarModel = ContactAvatarModel(
friend: nil,
name: displayNameTmp.isEmpty ? accountDisplayName : displayNameTmp,
address: contactAddressTmp,
withPresence: false
)
self.dialPlanValueSelected = dialPlanValueSelectedTmp
}
}
}
}
func setAvatarModel() {
CoreContext.shared.doOnCoreQueue { _ in
CoreContext.shared.accounts.forEach { accountTmp in
let displayNameTmp = accountTmp.account.params?.identityAddress?.displayName ?? ""
let contactAddressTmp = accountTmp.account.params?.identityAddress?.asStringUriOnly() ?? ""
let prefix = accountTmp.account.params?.internationalPrefix ?? ""
let isoCountryCode = accountTmp.account.params?.internationalPrefixIsoCountryCode ?? ""
var dialPlanValueSelectedTmp = ""
if !prefix.isEmpty || !isoCountryCode.isEmpty {
Log.info(
"\(AccountProfileViewModel.TAG) Account \(accountTmp.account.params?.identityAddress?.asStringUriOnly() ?? "") prefix is \(prefix) \(isoCountryCode)"
)
self.dialPlansList = Factory.Instance.dialPlans
if let dialPlan = self.dialPlansList.first(where: { $0.isoCountryCode == isoCountryCode }) ??
self.dialPlansList.first(where: { $0.countryCallingCode == prefix }) {
dialPlanValueSelectedTmp = "\(dialPlan.flag) \(dialPlan.country) | +\(dialPlan.countryCallingCode)"
}
}
let accountDisplayName = accountTmp.account.displayName()
DispatchQueue.main.async {
accountTmp.avatarModel = ContactAvatarModel(
friend: nil,
name: displayNameTmp.isEmpty ? accountDisplayName : displayNameTmp,
address: contactAddressTmp,
withPresence: false
)
self.dialPlanValueSelected = dialPlanValueSelectedTmp
}
}
}
}
func updateDialPlan(newDialPlan: String) {
if let dialPlan = self.dialPlansList.first(where: { newDialPlan.contains($0.isoCountryCode) }) ??

View file

@ -50,6 +50,8 @@ class AccountModel: ObservableObject {
init(account: Account, core: Core) {
self.account = account
self.computeNotificationsCount()
accountDelegate = AccountDelegateStub(onRegistrationStateChanged: { (_: Account, _: RegistrationState, _: String) in
self.update()
@ -278,6 +280,27 @@ class AccountModel: ObservableObject {
}
}
}
func setAsDefault() {
CoreContext.shared.doOnCoreQueue { core in
if core.defaultAccount?.displayName() != self.account.displayName() {
core.defaultAccount = self.account
for friendList in core.friendsLists {
if (friendList.subscriptionsEnabled) {
Log.info(
"\(AccountModel.TAG) Default account has changed, refreshing friend list \(friendList.displayName ?? "") subscriptions"
)
// friendList.updateSubscriptions() won't trigger a refresh unless a friend has changed
friendList.subscriptionsEnabled = false
friendList.subscriptionsEnabled = true
}
}
}
}
self.isDefaultAccount = true
}
}
class AccountDeviceModel: ObservableObject {