This commit is contained in:
fmodf 2024-08-11 16:02:18 +02:00
parent b98bcbcfad
commit d0c45cd41d
8 changed files with 218 additions and 39 deletions

View file

@ -4,14 +4,19 @@ import Foundation
@MainActor
final class NavigationStore: ObservableObject {
enum Flow: Equatable {
enum Entering {
enum Entering: Equatable {
case welcome
case login
case registration
}
enum Main {
case contacts
enum Main: Equatable {
enum Contacts: Equatable {
case list
case add
}
case contacts(Contacts)
case conversations
case settings
}

View file

@ -7,10 +7,15 @@ 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
// https://stackoverflow.com/questions/77253122/swiftui-navigationstack-title-loads-inline-instead-of-large-when-sheet-is-pres
UINavigationBar.appearance().prefersLargeTitles = true
}
var body: some Scene {
WindowGroup {
AppRootView()
.environmentObject(navigationStore)
.environmentObject(clientsStore)
}
}

View file

@ -9,8 +9,8 @@ struct AppRootView: View {
case .start:
StartScreen()
case .entering(let entering):
switch entering {
case .entering(let kind):
switch kind {
case .welcome:
WelcomeScreen()
@ -23,9 +23,18 @@ struct AppRootView: View {
case .main(let main):
switch main {
case .contacts:
case .contacts(let kind):
switch kind {
case .list:
ContactsScreen()
case .add:
ContactsScreen()
.fullScreenCover(isPresented: .constant(true)) {
AddContactOrChannelScreen()
}
}
case .conversations:
EmptyView()

View file

@ -0,0 +1,153 @@
import SwiftUI
struct AddContactOrChannelScreen: View {
@EnvironmentObject var navigation: NavigationStore
// @EnvironmentObject var store: AppStore
// enum Field {
// case account
// case contact
// }
//
// @FocusState private var focus: Field?
//
// @Binding var isPresented: Bool
// @State private var contactJID: String = ""
// // @State private var ownerAccount: Account?
//
// @State private var isShowingLoader = false
// @State private var isShowingAlert = false
// @State private var errorMsg = ""
var body: some View {
ZStack {
// Background color
Color.Material.Background.light
.ignoresSafeArea()
// Content
VStack(spacing: 0) {
// Header
SharedNavigationBar(
leftButton: .init(
image: Image(systemName: "xmark"),
action: {
withAnimation {
navigation.flow = .main(.contacts(.list))
}
// isPresented = false
}
),
centerText: .init(text: L10n.Contacts.Add.title),
rightButton: .init(
image: Image(systemName: "plus.viewfinder"),
action: {
print("Scan QR-code")
}
)
)
// VStack(spacing: 16) {
// // Explanation text
//
// Text(L10n.Contacts.Add.explanation)
// .font(.body3)
// .foregroundColor(.Material.Shape.separator)
// .multilineTextAlignment(.center)
// .padding(.top, 16)
//
// // Account selector
// HStack(spacing: 0) {
// Text("Use account:")
// .font(.body2)
// .foregroundColor(.Material.Text.main)
// .frame(alignment: .leading)
// Spacer()
// }
// // UniversalInputCollection.DropDownMenu(
// // prompt: "Use account",
// // elements: store.state.accountsState.accounts,
// // selected: $ownerAccount,
// // focus: $focus,
// // fieldType: .account
// // )
//
// // Contact text input
// HStack(spacing: 0) {
// Text("Contact JID:")
// .font(.body2)
// .foregroundColor(.Material.Text.main)
// .frame(alignment: .leading)
// Spacer()
// }
// UniversalInputCollection.TextField(
// prompt: "Contact or channel JID",
// text: $contactJID,
// focus: $focus,
// fieldType: .contact,
// contentType: .emailAddress,
// keyboardType: .emailAddress,
// submitLabel: .done,
// action: {
// focus = .account
// }
// )
//
// // Save button
// Button {
// navigation.flow = .main(.contacts(.list))
// } label: {
// Text(L10n.Global.save)
// }
// .buttonStyle(PrimaryButtonStyle())
// .disabled(!inputValid)
// .padding(.top)
// Spacer()
// }
.padding(.horizontal, 32)
}
}
// .onAppear {
// if let exists = store.state.accountsState.accounts.first, exists.isActive {
// ownerAccount = exists
// }
// }
// .loadingIndicator(isShowingLoader)
// .alert(isPresented: $isShowingAlert) {
// Alert(
// title: Text(L10n.Global.Error.title),
// message: Text(errorMsg),
// dismissButton: .default(Text(L10n.Global.ok))
// )
// }
// .onChange(of: store.state.rostersState.newAddedRosterJid) { jid in
// if jid != nil, isShowingLoader {
// isShowingLoader = false
// isPresented = false
// }
// }
// .onChange(of: store.state.rostersState.newAddedRosterError) { error in
// if let error = error, isShowingLoader {
// isShowingLoader = false
// errorMsg = error
// isShowingAlert = true
// }
// }
}
private var inputValid: Bool {
true
// ownerAccount != nil && !contactJID.isEmpty && UniversalInputCollection.Validators.isEmail(contactJID)
}
// private func save() {
// guard let ownerAccount else { return }
// if let exists = store.state.rostersState.rosters.first(where: { $0.bareJid == ownerAccount.bareJid && $0.contactBareJid == contactJID }), exists.locallyDeleted {
// store.dispatch(.rostersAction(.unmarkRosterAsLocallyDeleted(ownerJID: ownerAccount.bareJid, contactJID: contactJID)))
// isPresented = false
// } else {
// isShowingLoader = true
// store.dispatch(.rostersAction(.addRoster(ownerJID: ownerAccount.bareJid, contactJID: contactJID, name: nil, groups: [])))
// }
// }
}

View file

@ -1,6 +1,7 @@
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
@ -8,8 +9,6 @@ struct ContactsScreen: View {
// @State private var errorAlertMessage = ""
// @State private var isShowingLoader = false
@State private var rosters: [Roster] = []
var body: some View {
ZStack {
// Background color
@ -24,15 +23,18 @@ struct ContactsScreen: View {
rightButton: .init(
image: Image(systemName: "plus"),
action: {
withAnimation {
navigation.flow = .main(.contacts(.add))
}
// addPanelPresented = true
}
)
)
// Contacts list
if !rosters.isEmpty {
if !rostersStore.rosters.isEmpty {
List {
ForEach(rosters) { roster in
ForEach(rostersStore.rosters) { roster in
ContactsScreenRow(
roster: roster
// isErrorAlertPresented: $isErrorAlertPresented,
@ -51,9 +53,9 @@ struct ContactsScreen: View {
SharedTabBar()
}
}
.task {
await fetchRosters()
}
// .task {
// await fetchRosters()
// }
// .loadingIndicator(isShowingLoader)
// .fullScreenCover(isPresented: $addPanelPresented) {
// AddContactOrChannelScreen(isPresented: $addPanelPresented)
@ -67,27 +69,27 @@ struct ContactsScreen: View {
// }
}
private func fetchRosters() async {
let jids = clientsStore.clients
.filter { $0.state != .disabled }
.map { $0.credentials.bareJid }
do {
try await withThrowingTaskGroup(of: [Roster].self) { group in
for jid in jids {
group.addTask {
try await Roster.fetchAll(for: jid)
}
}
var allRosters: [Roster] = []
for try await rosters in group {
allRosters.append(contentsOf: rosters)
}
self.rosters = allRosters.sorted { $0.contactBareJid < $1.contactBareJid }
}
} catch {}
}
// private func fetchRosters() async {
// let jids = clientsStore.clients
// .filter { $0.state != .disabled }
// .map { $0.credentials.bareJid }
//
// do {
// try await withThrowingTaskGroup(of: [Roster].self) { group in
// for jid in jids {
// group.addTask {
// try await Roster.fetchAll(for: jid)
// }
// }
//
// var allRosters: [Roster] = []
// for try await rosters in group {
// allRosters.append(contentsOf: rosters)
// }
// self.rosters = allRosters.sorted { $0.contactBareJid < $1.contactBareJid }
// }
// } catch {}
// }
}
private struct ContactsScreenRow: View {

View file

@ -130,7 +130,7 @@ struct LoginScreen: View {
isLoading = false
isError = false
if navigation.flow == .entering(.login) {
navigation.flow = .main(.contacts)
navigation.flow = .main(.contacts(.list))
}
case .failure:

View file

@ -8,7 +8,7 @@ struct SharedTabBar: View {
.frame(height: 0.2)
.foregroundColor(.Material.Shape.separator)
HStack(spacing: 0) {
SharedTabBarButton(buttonFlow: .main(.contacts))
SharedTabBarButton(buttonFlow: .main(.contacts(.list)))
SharedTabBarButton(buttonFlow: .main(.conversations))
SharedTabBarButton(buttonFlow: .main(.settings))
}
@ -62,7 +62,7 @@ private struct SharedTabBarButton: View {
var buttonTitle: String {
switch buttonFlow {
case .main(.contacts):
case .main(.contacts(.list)):
return "Contacts"
case .main(.conversations):

View file

@ -5,6 +5,9 @@ options:
postGenCommand: swiftgen
packages:
SwiftfulRouting:
url: https://github.com/SwiftfulThinking/SwiftfulRouting
majorVersion: 5.3.5
MartinOMEMO:
url: https://github.com/tigase/MartinOMEMO
majorVersion: 2.2.3
@ -74,6 +77,8 @@ targets:
- sdk: Security.framework
# - framework: Lib/WebRTC.xcframework
# - target: Engine
- package: SwiftfulRouting
link: true
- package: MartinOMEMO
link: true
- package: KeychainAccess