Fix multiple initializations of LinphoneApp and ContentView

This commit is contained in:
Benoit Martins 2025-06-19 15:05:55 +02:00 committed by MARTINS Benoît
parent 8b53f55e7a
commit 7f16eb7ec2
2 changed files with 336 additions and 210 deletions

View file

@ -129,85 +129,125 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
@main
struct LinphoneApp: App {
@Environment(\.scenePhase) var scenePhase
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject private var coreContext = CoreContext.shared
@StateObject private var coreContext = CoreContext.shared
@StateObject private var navigationManager = NavigationManager()
@ObservedObject private var telecomManager = TelecomManager.shared
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
@State private var pendingURL: URL?
@StateObject private var telecomManager = TelecomManager.shared
@StateObject private var sharedMainViewModel = SharedMainViewModel.shared
var body: some Scene {
WindowGroup {
if coreContext.coreHasStartedOnce {
ZStack {
if !sharedMainViewModel.welcomeViewDisplayed {
ZStack {
WelcomeView()
ToastView()
.zIndex(3)
}
} else if (coreContext.coreIsStarted && coreContext.accounts.isEmpty) || sharedMainViewModel.displayProfileMode {
ZStack {
AssistantView()
ToastView()
.zIndex(3)
}
} else {
ContentView()
.environmentObject(navigationManager)
.onAppear {
// Link the navigation manager to the AppDelegate
delegate.coreContext = coreContext
delegate.navigationManager = navigationManager
// Check if the app was launched with a notification payload
if let callId = delegate.launchNotificationCallId, let peerAddr = delegate.launchNotificationPeerAddr, let localAddr = delegate.launchNotificationLocalAddr {
// Notify the app to navigate to the chat room
navigationManager.openChatRoom(callId: callId, peerAddr: peerAddr, localAddr: localAddr)
}
}
}
if coreContext.coreIsStarted {
VStack {
}
.onAppear {
if let url = pendingURL {
URIHandler.handleURL(url: url)
pendingURL = nil
}
}
}
}
.onOpenURL { url in
if coreContext.coreIsStarted {
URIHandler.handleURL(url: url)
} else {
pendingURL = url
}
}
} else {
SplashScreen()
}
}.onChange(of: scenePhase) { newPhase in
RootView(
coreContext: coreContext,
telecomManager: telecomManager,
sharedMainViewModel: sharedMainViewModel,
navigationManager: navigationManager,
appDelegate: delegate
)
.environmentObject(coreContext)
.environmentObject(navigationManager)
.environmentObject(telecomManager)
.environmentObject(sharedMainViewModel)
}
.onChange(of: scenePhase) { newPhase in
if !telecomManager.callInProgress {
if newPhase == .active {
switch newPhase {
case .active:
Log.info("Entering foreground")
coreContext.onEnterForeground()
} else if newPhase == .inactive {
} else if newPhase == .background {
case .background:
Log.info("Entering background")
coreContext.onEnterBackground()
default:
break
}
}
}
}
}
struct RootView: View {
@ObservedObject var coreContext: CoreContext
@ObservedObject var telecomManager: TelecomManager
@ObservedObject var sharedMainViewModel: SharedMainViewModel
@ObservedObject var navigationManager: NavigationManager
@State private var pendingURL: URL?
let appDelegate: AppDelegate
var body: some View {
Group {
if coreContext.coreHasStartedOnce {
MainViewSwitcher(
coreContext: coreContext,
navigationManager: navigationManager,
sharedMainViewModel: sharedMainViewModel,
pendingURL: $pendingURL,
appDelegate: appDelegate
)
} else {
SplashScreen()
}
}
.onOpenURL { url in
if coreContext.coreIsStarted {
URIHandler.handleURL(url: url)
} else {
pendingURL = url
}
}
}
}
struct MainViewSwitcher: View {
let coreContext: CoreContext
let navigationManager: NavigationManager
let sharedMainViewModel: SharedMainViewModel
@Binding var pendingURL: URL?
let appDelegate: AppDelegate
var body: some View {
ZStack {
selectedMainView()
if coreContext.coreIsStarted {
VStack {} // Force trigger .onAppear
.onAppear {
if let url = pendingURL {
URIHandler.handleURL(url: url)
pendingURL = nil
}
}
}
}
}
@ViewBuilder
func selectedMainView() -> some View {
if !sharedMainViewModel.welcomeViewDisplayed {
ZStack {
WelcomeView()
ToastView().zIndex(3)
}
} else if (coreContext.coreIsStarted && coreContext.accounts.isEmpty)
|| sharedMainViewModel.displayProfileMode {
ZStack {
AssistantView()
ToastView().zIndex(3)
}
} else {
ContentView()
.onAppear {
appDelegate.coreContext = coreContext
appDelegate.navigationManager = navigationManager
if let callId = appDelegate.launchNotificationCallId,
let peerAddr = appDelegate.launchNotificationPeerAddr,
let localAddr = appDelegate.launchNotificationLocalAddr {
navigationManager.openChatRoom(callId: callId, peerAddr: peerAddr, localAddr: localAddr)
}
}
}
}
}

View file

@ -27,12 +27,12 @@ struct ContentView: View {
@Environment(\.scenePhase) var scenePhase
@EnvironmentObject var navigationManager: NavigationManager
@EnvironmentObject var coreContext: CoreContext
@EnvironmentObject var telecomManager: TelecomManager
@EnvironmentObject var sharedMainViewModel: SharedMainViewModel
@ObservedObject private var coreContext = CoreContext.shared
@ObservedObject private var telecomManager = TelecomManager.shared
@ObservedObject private var contactsManager = ContactsManager.shared
@ObservedObject private var magicSearch = MagicSearchSingleton.shared
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
@StateObject private var callViewModel = CallViewModel()
@StateObject private var accountProfileViewModel = AccountProfileViewModel()
@ -83,15 +83,6 @@ struct ContentView: View {
@State var conversationInfoPopupText: String = ""
var body: some View {
let contactLoaded = NotificationCenter.default
.publisher(for: NSNotification.Name("ContactLoaded"))
let contactAdded = NotificationCenter.default
.publisher(for: NSNotification.Name("ContactAdded"))
.compactMap { $0.userInfo?["address"] as? String }
let imageChanged = NotificationCenter.default
.publisher(for: NSNotification.Name("ImageChanged"))
let coreStarted = NotificationCenter.default
.publisher(for: NSNotification.Name("CoreStarted"))
GeometryReader { geometry in
VStack(spacing: 0) {
if (telecomManager.callInProgress && !fullscreenVideo && ((!telecomManager.callDisplayed && callViewModel.callsCounter == 1) || callViewModel.callsCounter > 1)) || isShowConversationFragment {
@ -342,9 +333,6 @@ struct ContentView: View {
.resizable()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
.onAppear {
accountProfileViewModel.saveImage(image: tmpImage, name: coreContext.accounts[accountModelIndex].avatarModel!.name, prefix: "-default")
}
} else if let cachedImage = imageTmp {
cachedImage
.resizable()
@ -377,13 +365,22 @@ struct ContentView: View {
}
}
}
.onReceive(imageChanged) { _ in
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ImageChanged"))) { _ in
if !coreContext.accounts[accountModelIndex].usernaneAvatar.isEmpty {
let imagePathTmp = coreContext.accounts[accountModelIndex].getImagePath()
sharedMainViewModel.changeDefaultAvatar(defaultAvatarURL: imagePathTmp)
imagePath = imagePathTmp
}
}
} else if let cachedImage = imageTmp {
cachedImage
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
} else {
ProgressView()
.frame(width: avatarSize, height: avatarSize)
}
Text(String(localized: sharedMainViewModel.indexView == 0 ? "bottom_navigation_contacts_label" : (sharedMainViewModel.indexView == 1 ? "bottom_navigation_calls_label" : (sharedMainViewModel.indexView == 2 ? "bottom_navigation_conversations_label" : "bottom_navigation_meetings_label"))))
@ -629,133 +626,37 @@ struct ContentView: View {
}
if sharedMainViewModel.indexView == 0 {
if let contactsListVM = contactsListViewModel {
ContactsView(
isShowEditContactFragment: $isShowEditContactFragment,
isShowDeletePopup: $isShowDeleteContactPopup,
text: $text
)
.environmentObject(contactsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
contactsListViewModel = ContactsListViewModel()
}
}
}
ContactsContainer(
contactsListViewModel: $contactsListViewModel,
isShowEditContactFragment: $isShowEditContactFragment,
isShowDeleteContactPopup: $isShowDeleteContactPopup,
text: $text,
orientation: orientation
)
} else if sharedMainViewModel.indexView == 1 {
if let historyListVM = historyListViewModel {
HistoryView(
isShowStartCallFragment: $isShowStartCallFragment,
isShowEditContactFragment: $isShowEditContactFragment,
text: $text,
isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress
)
.environmentObject(historyListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
historyListViewModel = HistoryListViewModel()
}
}
}
HistoryContainer(
historyListViewModel: $historyListViewModel,
isShowStartCallFragment: $isShowStartCallFragment,
isShowEditContactFragment: $isShowEditContactFragment,
text: $text,
isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress,
orientation: orientation
)
} else if sharedMainViewModel.indexView == 2 {
if let conversationsListVM = conversationsListViewModel {
ConversationsView(
text: $text,
isShowStartConversationFragment: $isShowStartConversationFragment
)
.environmentObject(conversationsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
conversationsListViewModel = ConversationsListViewModel()
}
}
}
ConversationsContainer(
conversationsListViewModel: $conversationsListViewModel,
isShowStartConversationFragment: $isShowStartConversationFragment,
text: $text,
orientation: orientation
)
} else if sharedMainViewModel.indexView == 3 {
if let meetingsListVM = meetingsListViewModel {
MeetingsView(
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup,
text: $text
)
.environmentObject(meetingsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
meetingsListViewModel = MeetingsListViewModel()
}
}
}
MeetingsContainer(
meetingsListViewModel: $meetingsListViewModel,
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup,
text: $text,
orientation: orientation
)
}
}
}
@ -1381,7 +1282,7 @@ struct ContentView: View {
sharedMainViewModel.changeIndexView(indexViewInt: 2)
}
}
.onReceive(contactLoaded) { _ in
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ContactLoaded"))) { _ in
if let conversationsListVM = conversationsListViewModel {
conversationsListVM.updateChatRoomsList()
}
@ -1390,12 +1291,12 @@ struct ContentView: View {
historyListVM.refreshHistoryAvatarModel()
}
}
.onReceive(contactAdded) { address in
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ContactAdded")).compactMap { $0.userInfo?["address"] as? String }) { address in
if let conversationsListVM = conversationsListViewModel {
conversationsListVM.updateChatRoom(address: address)
}
}
.onReceive(coreStarted) { _ in
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CoreStarted"))) { _ in
accountProfileViewModel.setAvatarModel()
}
}
@ -1434,6 +1335,191 @@ struct ContentView: View {
}
}
struct ContactsContainer: View {
@Binding var contactsListViewModel: ContactsListViewModel?
@Binding var isShowEditContactFragment: Bool
@Binding var isShowDeleteContactPopup: Bool
@Binding var text: String
var orientation: UIDeviceOrientation
var body: some View {
Group {
if let contactsListVM = contactsListViewModel {
ContactsView(
isShowEditContactFragment: $isShowEditContactFragment,
isShowDeletePopup: $isShowDeleteContactPopup,
text: $text
)
.environmentObject(contactsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
if contactsListViewModel == nil {
contactsListViewModel = ContactsListViewModel()
}
}
}
}
}
}
}
struct HistoryContainer: View {
@Binding var historyListViewModel: HistoryListViewModel?
@Binding var isShowStartCallFragment: Bool
@Binding var isShowEditContactFragment: Bool
@Binding var text: String
@Binding var isShowEditContactFragmentAddress: String
var orientation: UIDeviceOrientation
var body: some View {
Group {
if let historyListVM = historyListViewModel {
HistoryView(
isShowStartCallFragment: $isShowStartCallFragment,
isShowEditContactFragment: $isShowEditContactFragment,
text: $text,
isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress
)
.environmentObject(historyListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
if historyListViewModel == nil {
historyListViewModel = HistoryListViewModel()
}
}
}
}
}
}
}
struct ConversationsContainer: View {
@Binding var conversationsListViewModel: ConversationsListViewModel?
@Binding var isShowStartConversationFragment: Bool
@Binding var text: String
var orientation: UIDeviceOrientation
var body: some View {
Group {
if let conversationsListVM = conversationsListViewModel {
ConversationsView(
text: $text,
isShowStartConversationFragment: $isShowStartConversationFragment
)
.environmentObject(conversationsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
if conversationsListViewModel == nil {
conversationsListViewModel = ConversationsListViewModel()
}
}
}
}
}
}
}
struct MeetingsContainer: View {
@Binding var meetingsListViewModel: MeetingsListViewModel?
@Binding var isShowScheduleMeetingFragment: Bool
@Binding var isShowSendCancelMeetingNotificationPopup: Bool
@Binding var text: String
var orientation: UIDeviceOrientation
var body: some View {
Group {
if let meetingsListVM = meetingsListViewModel {
MeetingsView(
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup,
text: $text
)
.environmentObject(meetingsListVM)
.roundedCorner(25, corners: [.topRight, .topLeft])
.shadow(
color: (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
? .white.opacity(0.0)
: .black.opacity(0.2),
radius: 25
)
} else {
NavigationView {
VStack {
Spacer()
ProgressView()
.controlSize(.large)
Spacer()
}
.onAppear {
if meetingsListViewModel == nil {
meetingsListViewModel = MeetingsListViewModel()
}
}
}
}
}
}
}
class NavigationManager: ObservableObject {
@Published var selectedCallId: String?
@Published var peerAddr: String?