diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index 1de5ad3..13398c5 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -100,12 +100,6 @@ extension Client { _ = try await connection.module(.roster).removeItem(jid: JID(roster.contactBareJid)) } - func setActive(_ active: Bool) { - Task { - try? await credentials.setActive(flag: active) - } - } - func connect() async { guard credentials.isActive, state == .enabled(.disconnected) else { return diff --git a/ConversationsClassic/AppData/Store/SettingsStore.swift b/ConversationsClassic/AppData/Store/ChatSettingsStore.swift similarity index 89% rename from ConversationsClassic/AppData/Store/SettingsStore.swift rename to ConversationsClassic/AppData/Store/ChatSettingsStore.swift index 5755d68..11cee97 100644 --- a/ConversationsClassic/AppData/Store/SettingsStore.swift +++ b/ConversationsClassic/AppData/Store/ChatSettingsStore.swift @@ -5,7 +5,7 @@ import Photos import SwiftUI @MainActor -final class SettingsStore: ObservableObject { +final class ChatSettingsStore: ObservableObject { @Published var chat: Chat? private let client: Client @@ -21,7 +21,7 @@ final class SettingsStore: ObservableObject { } } -extension SettingsStore { +extension ChatSettingsStore { func setSecured(_ secured: Bool) { Task { try? await chat?.setEncrypted(secured) @@ -30,7 +30,7 @@ extension SettingsStore { } // MARK: - Processing attachments -private extension SettingsStore { +private extension ChatSettingsStore { func subscribe() { chatCancellable = ValueObservation.tracking(Chat .filter(Column("account") == roster.bareJid && Column("participant") == roster.contactBareJid) diff --git a/ConversationsClassic/AppData/Store/ClientsStore.swift b/ConversationsClassic/AppData/Store/ClientsStore.swift index 2d65d4a..fce8ec0 100644 --- a/ConversationsClassic/AppData/Store/ClientsStore.swift +++ b/ConversationsClassic/AppData/Store/ClientsStore.swift @@ -131,7 +131,7 @@ extension ClientsStore { // MARK: - Produce stores for conversation extension ClientsStore { // swiftlint:disable:next large_tuple - func conversationStores(for roster: Roster) async throws -> (MessagesStore, AttachmentsStore, SettingsStore) { + func conversationStores(for roster: Roster) async throws -> (MessagesStore, AttachmentsStore, ChatSettingsStore) { while !ready { await Task.yield() } @@ -142,12 +142,12 @@ extension ClientsStore { let conversationStore = MessagesStore(roster: roster, client: client) let attachmentsStore = AttachmentsStore(roster: roster, client: client) - let settingsStore = SettingsStore(roster: roster, client: client) + let settingsStore = ChatSettingsStore(roster: roster, client: client) return (conversationStore, attachmentsStore, settingsStore) } // swiftlint:disable:next large_tuple - func conversationStores(for chat: Chat) async throws -> (MessagesStore, AttachmentsStore, SettingsStore) { + func conversationStores(for chat: Chat) async throws -> (MessagesStore, AttachmentsStore, ChatSettingsStore) { while !ready { await Task.yield() } @@ -159,7 +159,7 @@ extension ClientsStore { let roster = try await chat.fetchRoster() let conversationStore = MessagesStore(roster: roster, client: client) let attachmentsStore = AttachmentsStore(roster: roster, client: client) - let settingsStore = SettingsStore(roster: roster, client: client) + let settingsStore = ChatSettingsStore(roster: roster, client: client) return (conversationStore, attachmentsStore, settingsStore) } } diff --git a/ConversationsClassic/AppData/Store/GlobalSettingsStore.swift b/ConversationsClassic/AppData/Store/GlobalSettingsStore.swift new file mode 100644 index 0000000..e969c79 --- /dev/null +++ b/ConversationsClassic/AppData/Store/GlobalSettingsStore.swift @@ -0,0 +1,32 @@ +import Combine +import Foundation +import GRDB +import Photos +import SwiftUI + +@MainActor +final class GlobalSettingsStore: ObservableObject { + static let shared = GlobalSettingsStore() + + @Published var credentials: [Credentials] = [] + + private var credentialsCancellable: AnyCancellable? + + init() { + subscribe() + } +} + +private extension GlobalSettingsStore { + func subscribe() { + credentialsCancellable = ValueObservation.tracking(Credentials + .fetchAll + ) + .publisher(in: Database.shared.dbQueue, scheduling: .immediate) + .receive(on: DispatchQueue.main) + .sink { _ in + } receiveValue: { [weak self] credentials in + self?.credentials = credentials + } + } +} diff --git a/ConversationsClassic/ConversationsClassicApp.swift b/ConversationsClassic/ConversationsClassicApp.swift index bf24165..4816a3f 100644 --- a/ConversationsClassic/ConversationsClassicApp.swift +++ b/ConversationsClassic/ConversationsClassicApp.swift @@ -5,6 +5,7 @@ import SwiftUI @MainActor struct ConversationsClassic: App { private let clientsStore = ClientsStore.shared + private let globalSettingsStore = GlobalSettingsStore.shared 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 @@ -16,6 +17,7 @@ struct ConversationsClassic: App { WindowGroup { RootView() .environmentObject(clientsStore) + .environmentObject(globalSettingsStore) } } } diff --git a/ConversationsClassic/View/Entering/WelcomeScreen.swift b/ConversationsClassic/View/Entering/WelcomeScreen.swift index cbe008f..31ab60a 100644 --- a/ConversationsClassic/View/Entering/WelcomeScreen.swift +++ b/ConversationsClassic/View/Entering/WelcomeScreen.swift @@ -1,6 +1,7 @@ import SwiftUI struct WelcomeScreen: View { + @EnvironmentObject var globalSettingsStore: GlobalSettingsStore @Environment(\.router) var router var body: some View { @@ -9,6 +10,23 @@ struct WelcomeScreen: View { Color.Material.Background.light .ignoresSafeArea() + if !globalSettingsStore.credentials.isEmpty && globalSettingsStore.credentials.allSatisfy({ !$0.isActive }) { + VStack { + HStack { + Spacer() + Image(systemName: "gear") + .foregroundColor(.Material.Elements.active) + .tappablePadding(.symmetric(10)) { + router.showScreen(.push) { _ in + SettingsScreen() + } + } + } + .padding() + Spacer() + } + } + // content VStack(spacing: 32) { // icon diff --git a/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift index 5ec1cd6..1b14f55 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift @@ -7,7 +7,7 @@ struct ConversationScreen: View { @Environment(\.router) var router @StateObject var messagesStore: MessagesStore @StateObject var attachments: AttachmentsStore - @StateObject var settings: SettingsStore + @StateObject var settings: ChatSettingsStore @State private var autoScroll = true @State private var firstIsVisible = true diff --git a/ConversationsClassic/View/Main/Conversation/ConversationSettingsScreen.swift b/ConversationsClassic/View/Main/Conversation/ConversationSettingsScreen.swift index 92afb15..17160de 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationSettingsScreen.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationSettingsScreen.swift @@ -5,7 +5,7 @@ import SwiftUI struct ConversationSettingsScreen: View { @Environment(\.router) var router - @EnvironmentObject var settingsStore: SettingsStore + @EnvironmentObject var settingsStore: ChatSettingsStore var body: some View { ZStack { diff --git a/ConversationsClassic/View/Main/Settings/SettingsScreen.swift b/ConversationsClassic/View/Main/Settings/SettingsScreen.swift index f83bb9c..10c02c3 100644 --- a/ConversationsClassic/View/Main/Settings/SettingsScreen.swift +++ b/ConversationsClassic/View/Main/Settings/SettingsScreen.swift @@ -2,6 +2,7 @@ import SwiftUI struct SettingsScreen: View { @EnvironmentObject var clientsStore: ClientsStore + @EnvironmentObject var settingsStore: GlobalSettingsStore @Environment(\.router) var router var body: some View { @@ -22,19 +23,21 @@ struct SettingsScreen: View { // Accounts section SharedSectionTitle(text: L10n.Settings.Section.Accounts.title) - ForEach(clientsStore.clients) { client in + ForEach(settingsStore.credentials) { creds in SharedListRow( - iconType: .charCircle(client.credentials.bareJid), - text: client.credentials.bareJid, + iconType: .charCircle(creds.bareJid), + text: creds.bareJid, controlType: .switcher(isOn: Binding( - get: { client.credentials.isActive }, + get: { creds.isActive }, set: { new in - client.setActive(new) + Task { + try? await creds.setActive(flag: new) + } } )) ) .onTapGesture { - print("Tapped account \(client.credentials.bareJid)") + print("Tapped account \(creds.bareJid)") } } @@ -83,6 +86,11 @@ struct SettingsScreen: View { Spacer() } } + // .onDisappear { + // if settingsStore.credentials.isEmpty || settingsStore.credentials.allSatisfy({ !$0.isActive }) { + // router.dismissScreenStack() + // } + // } } @ViewBuilder private var addAccountSelector: some View { diff --git a/ConversationsClassic/View/RootView.swift b/ConversationsClassic/View/RootView.swift index 3205073..2460c56 100644 --- a/ConversationsClassic/View/RootView.swift +++ b/ConversationsClassic/View/RootView.swift @@ -3,11 +3,12 @@ import SwiftUI struct RootView: View { @EnvironmentObject var clientsStore: ClientsStore + @EnvironmentObject var globalSettingsStore: GlobalSettingsStore var body: some View { Group { if clientsStore.ready { - if clientsStore.clients.isEmpty || clientsStore.clients.allSatisfy({ !$0.credentials.isActive }) { + if clientsStore.clients.isEmpty || globalSettingsStore.credentials.allSatisfy({ !$0.isActive }) { RouterView { _ in WelcomeScreen() }