mv-experiment #1
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
),
|
),
|
|
@ -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 {
|
113
ConversationsClassic/View/Main/MainTabScreen.swift
Normal file
113
ConversationsClassic/View/Main/MainTabScreen.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ConversationsClassic/View/RootView.swift
Normal file
27
ConversationsClassic/View/RootView.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue