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 @MainActor
final class NavigationStore: ObservableObject { final class NavigationStore: ObservableObject {
enum Flow: Equatable { enum Flow: Equatable {
enum Entering { enum Entering: Equatable {
case welcome case welcome
case login case login
case registration case registration
} }
enum Main { enum Main: Equatable {
case contacts enum Contacts: Equatable {
case list
case add
}
case contacts(Contacts)
case conversations case conversations
case settings case settings
} }

View file

@ -7,10 +7,15 @@ struct ConversationsClassic: App {
private var clientsStore = ClientsStore() private var clientsStore = ClientsStore()
private var navigationStore = NavigationStore() 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 { var body: some Scene {
WindowGroup { WindowGroup {
AppRootView() AppRootView()
.environmentObject(navigationStore)
.environmentObject(clientsStore) .environmentObject(clientsStore)
} }
} }

View file

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

View file

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

View file

@ -8,7 +8,7 @@ struct SharedTabBar: View {
.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)) SharedTabBarButton(buttonFlow: .main(.contacts(.list)))
SharedTabBarButton(buttonFlow: .main(.conversations)) SharedTabBarButton(buttonFlow: .main(.conversations))
SharedTabBarButton(buttonFlow: .main(.settings)) SharedTabBarButton(buttonFlow: .main(.settings))
} }
@ -62,7 +62,7 @@ private struct SharedTabBarButton: View {
var buttonTitle: String { var buttonTitle: String {
switch buttonFlow { switch buttonFlow {
case .main(.contacts): case .main(.contacts(.list)):
return "Contacts" return "Contacts"
case .main(.conversations): case .main(.conversations):

View file

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