mv-experiment #1

Merged
fmodf merged 88 commits from mv-experiment into develop 2024-09-03 15:13:59 +00:00
13 changed files with 202 additions and 158 deletions
Showing only changes of commit 2e053afea9 - Show all commits

View file

@ -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
}

View file

@ -5,7 +5,6 @@ import SwiftUI
@MainActor @MainActor
struct ConversationsClassic: App { struct ConversationsClassic: App {
private var clientsStore = ClientsStore() private var clientsStore = ClientsStore()
private var navigationStore = NavigationStore()
init() { 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 // 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 { var body: some Scene {
WindowGroup { WindowGroup {
AppRootView() RootView()
.environmentObject(clientsStore) .environmentObject(clientsStore)
} }
} }

View file

@ -19,6 +19,10 @@
"Login.btn" = "Continue"; "Login.btn" = "Continue";
"Login.error" = "Check internet connection, and make sure that JID and password are correct"; "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";

View file

@ -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()
}
}
}
}
}

View file

@ -3,7 +3,7 @@ import Martin
import SwiftUI import SwiftUI
struct LoginScreen: View { struct LoginScreen: View {
@EnvironmentObject var navigation: NavigationStore @Environment(\.router) var router
@EnvironmentObject var clientsStore: ClientsStore @EnvironmentObject var clientsStore: ClientsStore
enum Field { enum Field {
@ -88,9 +88,7 @@ struct LoginScreen: View {
.disabled(!loginInputValid) .disabled(!loginInputValid)
Button { Button {
withAnimation { router.dismissScreen()
navigation.flow = .entering(.welcome)
}
} label: { } label: {
Text("\(Image(systemName: "chevron.left")) \(L10n.Global.back)") Text("\(Image(systemName: "chevron.left")) \(L10n.Global.back)")
.foregroundColor(.Material.Elements.active) .foregroundColor(.Material.Elements.active)
@ -129,9 +127,6 @@ struct LoginScreen: View {
clientsStore.addNewClient(client) clientsStore.addNewClient(client)
isLoading = false isLoading = false
isError = false isError = false
if navigation.flow == .entering(.login) {
navigation.flow = .main(.contacts(.list))
}
case .failure: case .failure:
isLoading = false isLoading = false

View file

@ -1,15 +1,13 @@
import SwiftUI import SwiftUI
struct RegistrationScreen: View { struct RegistrationScreen: View {
@EnvironmentObject var navigation: NavigationStore @Environment(\.router) var router
public var body: some View { public var body: some View {
ZStack { ZStack {
Color.Material.Background.light Color.Material.Background.light
Button { Button {
withAnimation { router.dismissScreen()
navigation.flow = .entering(.welcome)
}
} label: { } label: {
VStack { VStack {
Text("Not yet implemented") Text("Not yet implemented")

View file

@ -1,9 +1,9 @@
import SwiftUI import SwiftUI
struct WelcomeScreen: View { struct WelcomeScreen: View {
@EnvironmentObject var navigation: NavigationStore @Environment(\.router) var router
public var body: some View { var body: some View {
ZStack { ZStack {
// background // background
Color.Material.Background.light Color.Material.Background.light
@ -33,16 +33,18 @@ struct WelcomeScreen: View {
// buttons // buttons
VStack(spacing: 16) { VStack(spacing: 16) {
Button { Button {
withAnimation { router.showScreen(.push) { _ in
navigation.flow = .entering(.login) LoginScreen()
.navigationBarBackButtonHidden(true)
} }
} label: { } label: {
Text(L10n.Start.Btn.login) Text(L10n.Start.Btn.login)
} }
.buttonStyle(SecondaryButtonStyle()) .buttonStyle(SecondaryButtonStyle())
Button { Button {
withAnimation { router.showScreen(.push) { _ in
navigation.flow = .entering(.registration) RegistrationScreen()
.navigationBarBackButtonHidden(true)
} }
} label: { } label: {
Text(L10n.Start.Btn.register) Text(L10n.Start.Btn.register)

View file

@ -1,7 +1,6 @@
import SwiftUI import SwiftUI
struct AddContactOrChannelScreen: View { struct AddContactOrChannelScreen: View {
@EnvironmentObject var navigation: NavigationStore
// @EnvironmentObject var store: AppStore // @EnvironmentObject var store: AppStore
// enum Field { // enum Field {
@ -32,9 +31,9 @@ struct AddContactOrChannelScreen: View {
leftButton: .init( leftButton: .init(
image: Image(systemName: "xmark"), image: Image(systemName: "xmark"),
action: { action: {
withAnimation { // withAnimation {
navigation.flow = .main(.contacts(.list)) // navigation.flow = .main(.contacts(.list))
} // }
// isPresented = false // isPresented = false
} }
), ),

View file

@ -1,7 +1,6 @@
import SwiftUI import SwiftUI
struct ContactsScreen: View { struct ContactsScreen: View {
@EnvironmentObject var navigation: NavigationStore
@EnvironmentObject var clientsStore: ClientsStore @EnvironmentObject var clientsStore: ClientsStore
@StateObject var rostersStore = RostersStore(clientsPublisher: ClientsStore.shared.$clients) @StateObject var rostersStore = RostersStore(clientsPublisher: ClientsStore.shared.$clients)
// @State private var addPanelPresented = false // @State private var addPanelPresented = false
@ -23,9 +22,9 @@ struct ContactsScreen: View {
rightButton: .init( rightButton: .init(
image: Image(systemName: "plus"), image: Image(systemName: "plus"),
action: { action: {
withAnimation { // withAnimation {
navigation.flow = .main(.contacts(.add)) // navigation.flow = .main(.contacts(.add))
} // }
// addPanelPresented = true // addPanelPresented = true
} }
) )
@ -48,9 +47,6 @@ struct ContactsScreen: View {
} else { } else {
Spacer() Spacer()
} }
// Tab bar
SharedTabBar()
} }
} }
// .task { // .task {

View file

@ -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
}
}
}

View file

@ -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()
}
}
}

View file

@ -7,72 +7,72 @@ struct SharedTabBar: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 0.2) .frame(height: 0.2)
.foregroundColor(.Material.Shape.separator) .foregroundColor(.Material.Shape.separator)
HStack(spacing: 0) { // HStack(spacing: 0) {
SharedTabBarButton(buttonFlow: .main(.contacts(.list))) // SharedTabBarButton(buttonFlow: .main(.contacts(.list)))
SharedTabBarButton(buttonFlow: .main(.conversations)) // SharedTabBarButton(buttonFlow: .main(.conversations))
SharedTabBarButton(buttonFlow: .main(.settings)) // SharedTabBarButton(buttonFlow: .main(.settings))
} // }
.background(Color.Material.Background.dark) // .background(Color.Material.Background.dark)
} }
.frame(height: 50) .frame(height: 50)
} }
} }
private struct SharedTabBarButton: View { private struct SharedTabBarButton: View {
@EnvironmentObject var navigation: NavigationStore // let buttonFlow: NavigationStore.Flow
let buttonFlow: NavigationStore.Flow
var body: some View { var body: some View {
ZStack { ZStack {
VStack(spacing: 2) { VStack(spacing: 2) {
buttonImg 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)) .font(.system(size: 24, weight: .light))
.symbolRenderingMode(.hierarchical) .symbolRenderingMode(.hierarchical)
Text(buttonTitle) Text(buttonTitle)
.font(.sub1) .font(.sub1)
.foregroundColor(buttonFlow == navigation.flow ? .Material.Text.main : .Material.Elements.inactive) // .foregroundColor(buttonFlow == navigation.flow ? .Material.Text.main : .Material.Elements.inactive)
} }
Rectangle() Rectangle()
.foregroundColor(.white.opacity(0.01)) .foregroundColor(.white.opacity(0.01))
.onTapGesture { .onTapGesture {
withAnimation { // withAnimation {
navigation.flow = buttonFlow // navigation.flow = buttonFlow
} // }
} }
} }
} }
var buttonImg: Image { var buttonImg: Image {
switch buttonFlow { // switch buttonFlow {
case .main(.contacts): // case .main(.contacts):
return Image(systemName: "person.2.fill") // 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): // default:
return Image(systemName: "bubble.left.fill") // return Image(systemName: "questionmark.circle")
// }
case .main(.settings): Image(systemName: "questionmark.circle")
return Image(systemName: "gearshape.fill")
default:
return Image(systemName: "questionmark.circle")
}
} }
var buttonTitle: String { var buttonTitle: String {
switch buttonFlow { ""
case .main(.contacts(.list)): // switch buttonFlow {
return "Contacts" // case .main(.contacts(.list)):
// return "Contacts"
case .main(.conversations): //
return "Chats" // case .main(.conversations):
// return "Chats"
case .main(.settings): //
return "Settings" // case .main(.settings):
// return "Settings"
default: //
return "Unknown" // default:
} // return "Unknown"
// }
} }
} }

View file

@ -2,7 +2,6 @@ import SwiftUI
struct StartScreen: View { struct StartScreen: View {
@EnvironmentObject var clientsStore: ClientsStore @EnvironmentObject var clientsStore: ClientsStore
@EnvironmentObject var navigation: NavigationStore
var body: some View { var body: some View {
ZStack { ZStack {
@ -13,16 +12,5 @@ struct StartScreen: View {
.frame(width: 200, height: 200) .frame(width: 200, height: 200)
} }
.ignoresSafeArea() .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
}
}
}
} }
} }