diff --git a/ConversationsClassic/AppData/Stores/NavigationStore.swift b/ConversationsClassic/AppData/Stores/NavigationStore.swift deleted file mode 100644 index 9f022ae..0000000 --- a/ConversationsClassic/AppData/Stores/NavigationStore.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Combine -import Foundation - -@MainActor -final class NavigationStore: ObservableObject { - enum Flow: Equatable { - enum Entering: Equatable { - case welcome - case login - case registration - } - - enum Main: Equatable { - enum Contacts: Equatable { - case list - case add - } - - case contacts(Contacts) - case conversations - case settings - } - - case start - case entering(Entering) - case main(Main) - } - - @Published var flow: Flow = .start -} diff --git a/ConversationsClassic/ConversationsClassicApp.swift b/ConversationsClassic/ConversationsClassicApp.swift index 63993d7..698b045 100644 --- a/ConversationsClassic/ConversationsClassicApp.swift +++ b/ConversationsClassic/ConversationsClassicApp.swift @@ -5,7 +5,6 @@ import SwiftUI @MainActor struct ConversationsClassic: App { private var clientsStore = ClientsStore() - private var navigationStore = NavigationStore() init() { // There's a bug on iOS 17 where sheet may not load with large title, even if modifiers are set, which causes some tests to fail @@ -15,7 +14,7 @@ struct ConversationsClassic: App { var body: some Scene { WindowGroup { - AppRootView() + RootView() .environmentObject(clientsStore) } } diff --git a/ConversationsClassic/Resources/Strings/Localizable.strings b/ConversationsClassic/Resources/Strings/Localizable.strings index 99cc122..954cbcc 100644 --- a/ConversationsClassic/Resources/Strings/Localizable.strings +++ b/ConversationsClassic/Resources/Strings/Localizable.strings @@ -19,6 +19,10 @@ "Login.btn" = "Continue"; "Login.error" = "Check internet connection, and make sure that JID and password are correct"; +// MARK: Tabs +"Tabs.Name.contacts" = "Contacts"; +"Tabs.Name.conversations" = "Chats"; +"Tabs.Name.settings" = "Settings"; diff --git a/ConversationsClassic/View/AppRootView.swift b/ConversationsClassic/View/AppRootView.swift deleted file mode 100644 index f74fdc4..0000000 --- a/ConversationsClassic/View/AppRootView.swift +++ /dev/null @@ -1,47 +0,0 @@ -import SwiftUI - -struct AppRootView: View { - @EnvironmentObject var navigation: NavigationStore - - var body: some View { - Group { - switch navigation.flow { - case .start: - StartScreen() - - case .entering(let kind): - switch kind { - case .welcome: - WelcomeScreen() - - case .login: - LoginScreen() - - case .registration: - RegistrationScreen() - } - - case .main(let main): - switch main { - case .contacts(let kind): - switch kind { - case .list: - ContactsScreen() - - case .add: - ContactsScreen() - .fullScreenCover(isPresented: .constant(true)) { - AddContactOrChannelScreen() - } - } - - case .conversations: - EmptyView() - - case .settings: - EmptyView() - } - } - } - } -} diff --git a/ConversationsClassic/View/Entering/LoginScreen.swift b/ConversationsClassic/View/Entering/LoginScreen.swift index b47a77a..62e0cfe 100644 --- a/ConversationsClassic/View/Entering/LoginScreen.swift +++ b/ConversationsClassic/View/Entering/LoginScreen.swift @@ -3,7 +3,7 @@ import Martin import SwiftUI struct LoginScreen: View { - @EnvironmentObject var navigation: NavigationStore + @Environment(\.router) var router @EnvironmentObject var clientsStore: ClientsStore enum Field { @@ -88,9 +88,7 @@ struct LoginScreen: View { .disabled(!loginInputValid) Button { - withAnimation { - navigation.flow = .entering(.welcome) - } + router.dismissScreen() } label: { Text("\(Image(systemName: "chevron.left")) \(L10n.Global.back)") .foregroundColor(.Material.Elements.active) @@ -129,9 +127,6 @@ struct LoginScreen: View { clientsStore.addNewClient(client) isLoading = false isError = false - if navigation.flow == .entering(.login) { - navigation.flow = .main(.contacts(.list)) - } case .failure: isLoading = false diff --git a/ConversationsClassic/View/Entering/RegistrationScreen.swift b/ConversationsClassic/View/Entering/RegistrationScreen.swift index b2366af..16b095d 100644 --- a/ConversationsClassic/View/Entering/RegistrationScreen.swift +++ b/ConversationsClassic/View/Entering/RegistrationScreen.swift @@ -1,15 +1,13 @@ import SwiftUI struct RegistrationScreen: View { - @EnvironmentObject var navigation: NavigationStore + @Environment(\.router) var router public var body: some View { ZStack { Color.Material.Background.light Button { - withAnimation { - navigation.flow = .entering(.welcome) - } + router.dismissScreen() } label: { VStack { Text("Not yet implemented") diff --git a/ConversationsClassic/View/Entering/WelcomeScreen.swift b/ConversationsClassic/View/Entering/WelcomeScreen.swift index 404d102..cbe008f 100644 --- a/ConversationsClassic/View/Entering/WelcomeScreen.swift +++ b/ConversationsClassic/View/Entering/WelcomeScreen.swift @@ -1,9 +1,9 @@ import SwiftUI struct WelcomeScreen: View { - @EnvironmentObject var navigation: NavigationStore + @Environment(\.router) var router - public var body: some View { + var body: some View { ZStack { // background Color.Material.Background.light @@ -33,16 +33,18 @@ struct WelcomeScreen: View { // buttons VStack(spacing: 16) { Button { - withAnimation { - navigation.flow = .entering(.login) + router.showScreen(.push) { _ in + LoginScreen() + .navigationBarBackButtonHidden(true) } } label: { Text(L10n.Start.Btn.login) } .buttonStyle(SecondaryButtonStyle()) Button { - withAnimation { - navigation.flow = .entering(.registration) + router.showScreen(.push) { _ in + RegistrationScreen() + .navigationBarBackButtonHidden(true) } } label: { Text(L10n.Start.Btn.register) diff --git a/ConversationsClassic/View/Contacts/AddContactOrChannelScreen.swift b/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift similarity index 96% rename from ConversationsClassic/View/Contacts/AddContactOrChannelScreen.swift rename to ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift index 0ba62ef..afd9ff8 100644 --- a/ConversationsClassic/View/Contacts/AddContactOrChannelScreen.swift +++ b/ConversationsClassic/View/Main/Contacts/AddContactOrChannelScreen.swift @@ -1,7 +1,6 @@ import SwiftUI struct AddContactOrChannelScreen: View { - @EnvironmentObject var navigation: NavigationStore // @EnvironmentObject var store: AppStore // enum Field { @@ -32,9 +31,9 @@ struct AddContactOrChannelScreen: View { leftButton: .init( image: Image(systemName: "xmark"), action: { - withAnimation { - navigation.flow = .main(.contacts(.list)) - } + // withAnimation { + // navigation.flow = .main(.contacts(.list)) + // } // isPresented = false } ), diff --git a/ConversationsClassic/View/Contacts/ContactsScreen.swift b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift similarity index 96% rename from ConversationsClassic/View/Contacts/ContactsScreen.swift rename to ConversationsClassic/View/Main/Contacts/ContactsScreen.swift index d259725..a70950b 100644 --- a/ConversationsClassic/View/Contacts/ContactsScreen.swift +++ b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift @@ -1,7 +1,6 @@ import SwiftUI struct ContactsScreen: View { - @EnvironmentObject var navigation: NavigationStore @EnvironmentObject var clientsStore: ClientsStore @StateObject var rostersStore = RostersStore(clientsPublisher: ClientsStore.shared.$clients) // @State private var addPanelPresented = false @@ -23,9 +22,9 @@ struct ContactsScreen: View { rightButton: .init( image: Image(systemName: "plus"), action: { - withAnimation { - navigation.flow = .main(.contacts(.add)) - } + // withAnimation { + // navigation.flow = .main(.contacts(.add)) + // } // addPanelPresented = true } ) @@ -48,9 +47,6 @@ struct ContactsScreen: View { } else { Spacer() } - - // Tab bar - SharedTabBar() } } // .task { diff --git a/ConversationsClassic/View/Main/MainTabScreen.swift b/ConversationsClassic/View/Main/MainTabScreen.swift new file mode 100644 index 0000000..2e986f0 --- /dev/null +++ b/ConversationsClassic/View/Main/MainTabScreen.swift @@ -0,0 +1,113 @@ +import Foundation +import SwiftfulRouting +import SwiftUI + +private enum Tab { + case conversations + case contacts + case settings +} + +struct MainTabScreen: View { + @State private var selectedTab: Tab = .conversations + + var body: some View { + ZStack { + // Background color + Color.Material.Background.light + .ignoresSafeArea() + + // Content + VStack(spacing: 0) { + switch selectedTab { + case .conversations: + Color.red + // ConversationsScreen() + + case .contacts: + RouterView { _ in + ContactsScreen() + } + + case .settings: + Color.green + // SettingsScreen() + } + + // Tab bar + TabBar(selectedTab: $selectedTab) + } + } + } +} + +private struct TabBar: View { + @Binding var selectedTab: Tab + + var body: some View { + VStack(spacing: 0) { + Rectangle() + .frame(maxWidth: .infinity) + .frame(height: 0.2) + .foregroundColor(.Material.Shape.separator) + HStack(spacing: 0) { + TabBarButton(buttonType: .contacts, selectedTab: $selectedTab) + TabBarButton(buttonType: .conversations, selectedTab: $selectedTab) + TabBarButton(buttonType: .settings, selectedTab: $selectedTab) + } + .background(Color.Material.Background.dark) + } + .frame(height: 50) + } +} + +private struct TabBarButton: View { + let buttonType: Tab + + @Binding var selectedTab: Tab + + var body: some View { + ZStack { + VStack(spacing: 2) { + buttonImg + .foregroundColor(buttonType == selectedTab ? .Material.Elements.active : .Material.Elements.inactive) + .font(.system(size: 24, weight: .light)) + .symbolRenderingMode(.hierarchical) + Text(buttonTitle) + .font(.sub1) + .foregroundColor(buttonType == selectedTab ? .Material.Text.main : .Material.Elements.inactive) + } + Rectangle() + .foregroundColor(.white.opacity(0.01)) + .onTapGesture { + selectedTab = buttonType + } + } + } + + var buttonImg: Image { + switch buttonType { + case .contacts: + return Image(systemName: "person.2.fill") + + case .conversations: + return Image(systemName: "bubble.left.fill") + + case .settings: + return Image(systemName: "gearshape.fill") + } + } + + var buttonTitle: String { + switch buttonType { + case .contacts: + return L10n.Tabs.Name.contacts + + case .conversations: + return L10n.Tabs.Name.conversations + + case .settings: + return L10n.Tabs.Name.settings + } + } +} diff --git a/ConversationsClassic/View/RootView.swift b/ConversationsClassic/View/RootView.swift new file mode 100644 index 0000000..8215a79 --- /dev/null +++ b/ConversationsClassic/View/RootView.swift @@ -0,0 +1,27 @@ +import SwiftfulRouting +import SwiftUI + +struct RootView: View { + @EnvironmentObject var clientsStore: ClientsStore + + var body: some View { + Group { + if clientsStore.ready { + if clientsStore.clients.isEmpty { + RouterView { _ in + WelcomeScreen() + } + } else { + RouterView { _ in + MainTabScreen() + } + } + } else { + StartScreen() + } + } + .task { + clientsStore.startFetching() + } + } +} diff --git a/ConversationsClassic/View/SharedComponents/SharedTabBar.swift b/ConversationsClassic/View/SharedComponents/SharedTabBar.swift index ff1949b..0cece8d 100644 --- a/ConversationsClassic/View/SharedComponents/SharedTabBar.swift +++ b/ConversationsClassic/View/SharedComponents/SharedTabBar.swift @@ -7,72 +7,72 @@ struct SharedTabBar: View { .frame(maxWidth: .infinity) .frame(height: 0.2) .foregroundColor(.Material.Shape.separator) - HStack(spacing: 0) { - SharedTabBarButton(buttonFlow: .main(.contacts(.list))) - SharedTabBarButton(buttonFlow: .main(.conversations)) - SharedTabBarButton(buttonFlow: .main(.settings)) - } - .background(Color.Material.Background.dark) + // HStack(spacing: 0) { + // SharedTabBarButton(buttonFlow: .main(.contacts(.list))) + // SharedTabBarButton(buttonFlow: .main(.conversations)) + // SharedTabBarButton(buttonFlow: .main(.settings)) + // } + // .background(Color.Material.Background.dark) } .frame(height: 50) } } private struct SharedTabBarButton: View { - @EnvironmentObject var navigation: NavigationStore - - let buttonFlow: NavigationStore.Flow + // let buttonFlow: NavigationStore.Flow var body: some View { ZStack { VStack(spacing: 2) { buttonImg - .foregroundColor(buttonFlow == navigation.flow ? .Material.Elements.active : .Material.Elements.inactive) + // .foregroundColor(buttonFlow == navigation.flow ? .Material.Elements.active : .Material.Elements.inactive) .font(.system(size: 24, weight: .light)) .symbolRenderingMode(.hierarchical) Text(buttonTitle) .font(.sub1) - .foregroundColor(buttonFlow == navigation.flow ? .Material.Text.main : .Material.Elements.inactive) + // .foregroundColor(buttonFlow == navigation.flow ? .Material.Text.main : .Material.Elements.inactive) } Rectangle() .foregroundColor(.white.opacity(0.01)) .onTapGesture { - withAnimation { - navigation.flow = buttonFlow - } + // withAnimation { + // navigation.flow = buttonFlow + // } } } } var buttonImg: Image { - switch buttonFlow { - case .main(.contacts): - return Image(systemName: "person.2.fill") + // switch buttonFlow { + // case .main(.contacts): + // return Image(systemName: "person.2.fill") + // + // case .main(.conversations): + // return Image(systemName: "bubble.left.fill") + // + // case .main(.settings): + // return Image(systemName: "gearshape.fill") - case .main(.conversations): - return Image(systemName: "bubble.left.fill") - - case .main(.settings): - return Image(systemName: "gearshape.fill") - - default: - return Image(systemName: "questionmark.circle") - } + // default: + // return Image(systemName: "questionmark.circle") + // } + Image(systemName: "questionmark.circle") } var buttonTitle: String { - switch buttonFlow { - case .main(.contacts(.list)): - return "Contacts" - - case .main(.conversations): - return "Chats" - - case .main(.settings): - return "Settings" - - default: - return "Unknown" - } + "" + // switch buttonFlow { + // case .main(.contacts(.list)): + // return "Contacts" + // + // case .main(.conversations): + // return "Chats" + // + // case .main(.settings): + // return "Settings" + // + // default: + // return "Unknown" + // } } } diff --git a/ConversationsClassic/View/StartScreen.swift b/ConversationsClassic/View/StartScreen.swift index 0999ebd..71564bd 100644 --- a/ConversationsClassic/View/StartScreen.swift +++ b/ConversationsClassic/View/StartScreen.swift @@ -2,7 +2,6 @@ import SwiftUI struct StartScreen: View { @EnvironmentObject var clientsStore: ClientsStore - @EnvironmentObject var navigation: NavigationStore var body: some View { ZStack { @@ -13,16 +12,5 @@ struct StartScreen: View { .frame(width: 200, height: 200) } .ignoresSafeArea() - .onAppear { - clientsStore.startFetching() - } - .onChange(of: clientsStore.ready) { ready in - if ready { - let flow: NavigationStore.Flow = clientsStore.clients.isEmpty ? .entering(.welcome) : .main(.conversations) - withAnimation { - navigation.flow = flow - } - } - } } }