diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index acea38e69e..2c6cec871f 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -9,6 +9,13 @@ import SwiftUI import Intents import SimpleXChat +enum HomeTab { + case settings + case contacts + case chats + case newChat +} + struct ContentView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var alertManager = AlertManager.shared @@ -27,7 +34,7 @@ struct ContentView: View { @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false - @State private var showSettings = false + @State private var homeTab: HomeTab = .chats @State private var showWhatsNew = false @State private var showChooseLAMode = false @State private var showSetPasscode = false @@ -74,7 +81,7 @@ struct ContentView: View { callView(call) } - if !showSettings, let la = chatModel.laRequest { + if homeTab != .settings, let la = chatModel.laRequest { LocalAuthView(authRequest: la) .onDisappear { // this flag is separate from accessAuthenticated to show initializationView while we wait for authentication @@ -97,9 +104,6 @@ struct ContentView: View { } } .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } - .sheet(isPresented: $showSettings) { - SettingsView(showSettings: $showSettings) - } .confirmationDialog("SimpleX Lock mode", isPresented: $showChooseLAMode, titleVisibility: .visible) { Button("System authentication") { initialEnableLA() } Button("Passcode entry") { showSetPasscode = true } @@ -230,7 +234,7 @@ struct ContentView: View { private func mainView() -> some View { ZStack(alignment: .top) { - ChatListView(showSettings: $showSettings).privacySensitive(protectScreen) + HomeView(homeTab: $homeTab).privacySensitive(protectScreen) .onAppear { requestNtfAuthorization() // Local Authentication notice is to be shown on next start after onboarding is complete diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 2435c9a4f5..40458c6ac1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -10,7 +10,7 @@ import SwiftUI struct ChatHelp: View { @EnvironmentObject var chatModel: ChatModel - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @State private var newChatMenuOption: NewChatMenuOption? = nil var body: some View { @@ -24,7 +24,7 @@ struct ChatHelp: View { VStack(alignment: .leading, spacing: 0) { Text("To ask any questions and to receive updates:") Button("connect to SimpleX Chat developers.") { - showSettings = false + homeTab = .chats DispatchQueue.main.async { UIApplication.shared.open(simplexTeamURL) } @@ -63,7 +63,6 @@ struct ChatHelp: View { struct ChatHelp_Previews: PreviewProvider { static var previews: some View { - @State var showSettings = false - return ChatHelp(showSettings: $showSettings) + return ChatHelp(homeTab: Binding.constant(.chats)) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 6bf63bb2e3..0453f0cf6c 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -54,7 +54,8 @@ struct ChatListView: View { } } UserPicker( - showSettings: $showSettings, +// showSettings: $showSettings, + homeTab: Binding.constant(.chats), showConnectDesktop: $showConnectDesktop, userPickerVisible: $userPickerVisible ) diff --git a/apps/ios/Shared/Views/ChatList/ChatsView.swift b/apps/ios/Shared/Views/ChatList/ChatsView.swift new file mode 100644 index 0000000000..48617d5a5c --- /dev/null +++ b/apps/ios/Shared/Views/ChatList/ChatsView.swift @@ -0,0 +1,382 @@ +// +// ChatsView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 01.05.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct ChatsView: View { + @EnvironmentObject var chatModel: ChatModel + @State private var searchMode = false + @FocusState private var searchFocussed + @State private var searchText = "" + @State private var searchShowingSimplexLink = false + @State private var searchChatFilteredBySimplexLink: String? = nil + @State private var newChatMenuOption: NewChatMenuOption? = nil // TODO remove? + @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false + + var body: some View { + if #available(iOS 16.0, *) { + viewBody.scrollDismissesKeyboard(.immediately) + } else { + viewBody + } + } + + private var viewBody: some View { + ZStack { + NavStackCompat( + isActive: Binding( + get: { chatModel.chatId != nil }, + set: { _ in } + ), + destination: chatView + ) { + VStack { + if chatModel.chats.isEmpty { + onboardingButtons() + } + chatsView + } + } + } + } + + private var chatsView: some View { + VStack { + chatList + } + .refreshable { + AlertManager.shared.showAlert(Alert( + title: Text("Reconnect servers?"), + message: Text("Reconnect all connected servers to force message delivery. It uses additional traffic."), + primaryButton: .default(Text("Ok")) { + Task { + do { + try await reconnectAllServers() + } catch let error { + AlertManager.shared.showAlertMsg(title: "Error", message: "\(responseError(error))") + } + } + }, + secondaryButton: .cancel() + )) + } + .listStyle(.plain) + .navigationBarTitleDisplayMode(.inline) + .navigationBarHidden(searchMode) + .toolbar { + ToolbarItem(placement: .principal) { + HStack(spacing: 4) { + Text("Chats") + .font(.headline) + if chatModel.chats.count > 0 { + toggleFilterButton() + } + } + .frame(maxWidth: .infinity, alignment: .center) + } + } + } + + private func toggleFilterButton() -> some View { + Button { + showUnreadAndFavorites = !showUnreadAndFavorites + } label: { + Image(systemName: "line.3.horizontal.decrease.circle" + (showUnreadAndFavorites ? ".fill" : "")) + .foregroundColor(.accentColor) + } + } + + @ViewBuilder private var chatList: some View { + let cs = filteredChats() + ZStack { + VStack { + List { + if !chatModel.chats.isEmpty { + ChatsSearchBar( + searchMode: $searchMode, + searchFocussed: $searchFocussed, + searchText: $searchText, + searchShowingSimplexLink: $searchShowingSimplexLink, + searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink + ) + .listRowSeparator(.hidden) + .frame(maxWidth: .infinity) + } + ForEach(cs, id: \.viewId) { chat in + ChatListNavLink(chat: chat) + .padding(.trailing, -16) + .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) + } + .offset(x: -8) + } + } + .onChange(of: chatModel.chatId) { _ in + if chatModel.chatId == nil, let chatId = chatModel.chatToTop { + chatModel.chatToTop = nil + chatModel.popChat(chatId) + } + } + if cs.isEmpty && !chatModel.chats.isEmpty { + Text("No filtered chats").foregroundColor(.secondary) + } + } + } + + private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View { + Circle() + .frame(width: size, height: size) + .foregroundColor(.accentColor) + } + + // TODO remove? + private func onboardingButtons() -> some View { + VStack(alignment: .trailing, spacing: 0) { + Path { p in + p.move(to: CGPoint(x: 8, y: 0)) + p.addLine(to: CGPoint(x: 16, y: 10)) + p.addLine(to: CGPoint(x: 0, y: 10)) + p.addLine(to: CGPoint(x: 8, y: 0)) + } + .fill(Color.accentColor) + .frame(width: 20, height: 10) + .padding(.trailing, 12) + + connectButton("Tap to start a new chat") { + newChatMenuOption = .newContact + } + + Spacer() + Text("You have no chats") + .foregroundColor(.secondary) + .frame(maxWidth: .infinity) + } + .padding(.trailing, 6) + .frame(maxHeight: .infinity) + } + + private func connectButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View { + Button(action: action) { + Text(label) + .padding(.vertical, 10) + .padding(.horizontal, 20) + } + .background(Color.accentColor) + .foregroundColor(.white) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + + @ViewBuilder private func chatView() -> some View { + if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) { + ChatView(chat: chat).onAppear { + loadChat(chat: chat) + } + } + } + + private func filteredChats() -> [Chat] { + if let linkChatId = searchChatFilteredBySimplexLink { + return chatModel.chats.filter { $0.id == linkChatId } + } else { + let s = searchString() + return s == "" && !showUnreadAndFavorites + ? chatModel.chats + : chatModel.chats.filter { chat in + let cInfo = chat.chatInfo + switch cInfo { + case let .direct(contact): + return s == "" + ? filtered(chat) + : (viewNameContains(cInfo, s) || + contact.profile.displayName.localizedLowercase.contains(s) || + contact.fullName.localizedLowercase.contains(s)) + case let .group(gInfo): + return s == "" + ? (filtered(chat) || gInfo.membership.memberStatus == .memInvited) + : viewNameContains(cInfo, s) + case .local: + return s == "" || viewNameContains(cInfo, s) + case .contactRequest: + return s == "" || viewNameContains(cInfo, s) + case let .contactConnection(conn): + return s != "" && conn.localAlias.localizedLowercase.contains(s) + case .invalidJSON: + return false + } + } + } + + func searchString() -> String { + searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + } + + func filtered(_ chat: Chat) -> Bool { + (chat.chatInfo.chatSettings?.favorite ?? false) || + chat.chatStats.unreadChat || + (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) + } + + func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool { + cInfo.chatViewName.localizedLowercase.contains(s) + } + } +} + +struct ChatsSearchBar: View { + @EnvironmentObject var m: ChatModel + @Binding var searchMode: Bool + @FocusState.Binding var searchFocussed: Bool + @Binding var searchText: String + @Binding var searchShowingSimplexLink: Bool + @Binding var searchChatFilteredBySimplexLink: String? + @State private var ignoreSearchTextChange = false + @State private var showScanCodeSheet = false + @State private var alert: PlanAndConnectAlert? + @State private var sheet: PlanAndConnectActionSheet? + + var body: some View { + VStack(spacing: 12) { + HStack(spacing: 12) { + HStack(spacing: 4) { + Image(systemName: "magnifyingglass") + TextField("Search or paste SimpleX link", text: $searchText) + .foregroundColor(searchShowingSimplexLink ? .secondary : .primary) + .disabled(searchShowingSimplexLink) + .focused($searchFocussed) + .frame(maxWidth: .infinity) + if !searchText.isEmpty { + Image(systemName: "xmark.circle.fill") + .onTapGesture { + searchText = "" + } + } else if !searchFocussed { + HStack(spacing: 24) { + if m.pasteboardHasStrings { + Image(systemName: "doc") + .onTapGesture { + if let str = UIPasteboard.general.string { + searchText = str + } + } + } + + Image(systemName: "qrcode") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .onTapGesture { + showScanCodeSheet = true + } + } + .padding(.trailing, 2) + } + } + .padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7)) + .foregroundColor(.secondary) + .background(Color(.tertiarySystemFill)) + .cornerRadius(10.0) + + if searchFocussed { + Text("Cancel") + .foregroundColor(.accentColor) + .onTapGesture { + searchText = "" + searchFocussed = false + } + } + } + Divider() + } + .sheet(isPresented: $showScanCodeSheet) { + NewChatView(selection: .connect, showQRCodeScanner: true) + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) // fixes .refreshable in ChatsView affecting nested view + } + .onChange(of: searchFocussed) { sf in + withAnimation { searchMode = sf } + } + .onChange(of: searchText) { t in + if ignoreSearchTextChange { + ignoreSearchTextChange = false + } else { + if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue + searchFocussed = false + if case let .simplexLink(linkType, _, smpHosts) = link.format { + ignoreSearchTextChange = true + searchText = simplexLinkText(linkType, smpHosts) + } + searchShowingSimplexLink = true + searchChatFilteredBySimplexLink = nil + connect(link.text) + } else { + if t != "" { // if some other text is pasted, enter search mode + searchFocussed = true + } + searchShowingSimplexLink = false + searchChatFilteredBySimplexLink = nil + } + } + } + .alert(item: $alert) { a in + planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" }) + } + .actionSheet(item: $sheet) { s in + planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" }) + } + } + + private func connect(_ link: String) { + planAndConnect( + link, + showAlert: { alert = $0 }, + showActionSheet: { sheet = $0 }, + dismiss: false, + incognito: nil, + filterKnownContact: { searchChatFilteredBySimplexLink = $0.id }, + filterKnownGroup: { searchChatFilteredBySimplexLink = $0.id } + ) + } +} + +// TODO remove +func chatsStoppedIcon() -> some View { + Button { + AlertManager.shared.showAlertMsg( + title: "Chat is stopped", + message: "You can start chat via app Settings / Database or by restarting the app" + ) + } label: { + Image(systemName: "exclamationmark.octagon.fill").foregroundColor(.red) + } +} + +struct ChatsView_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.chats = [ + Chat( + chatInfo: ChatInfo.sampleData.direct, + chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] + ), + Chat( + chatInfo: ChatInfo.sampleData.group, + chatItems: [ChatItem.getSample(1, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")] + ), + Chat( + chatInfo: ChatInfo.sampleData.contactRequest, + chatItems: [] + ) + + ] + return Group { + ChatsView() + .environmentObject(chatModel) + ChatsView() + .environmentObject(ChatModel()) + } + } +} diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index a615f9c118..338e774569 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -13,7 +13,7 @@ struct UserPicker: View { @EnvironmentObject var m: ChatModel @Environment(\.colorScheme) var colorScheme @Environment(\.scenePhase) var scenePhase - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @Binding var showConnectDesktop: Bool @Binding var userPickerVisible: Bool @State var scrollViewContentSize: CGSize = .zero @@ -72,7 +72,7 @@ struct UserPicker: View { } Divider() menuButton("Settings", icon: "gearshape") { - showSettings = true + homeTab = .settings withAnimation { userPickerVisible.toggle() } @@ -106,7 +106,7 @@ struct UserPicker: View { let user = u.user return Button(action: { if user.activeUser { - showSettings = true + homeTab = .settings withAnimation { userPickerVisible.toggle() } @@ -181,7 +181,7 @@ struct UserPicker_Previews: PreviewProvider { let m = ChatModel() m.users = [UserInfo.sampleData, UserInfo.sampleData] return UserPicker( - showSettings: Binding.constant(false), + homeTab: Binding.constant(.chats), showConnectDesktop: Binding.constant(false), userPickerVisible: Binding.constant(true) ) diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 2e0cd7738f..05cbda784d 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -41,7 +41,7 @@ enum DatabaseAlert: Identifiable { struct DatabaseView: View { @EnvironmentObject var m: ChatModel - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @State private var runChat = false @State private var alert: DatabaseAlert? = nil @State private var showFileImporter = false @@ -409,7 +409,7 @@ struct DatabaseView: View { private func startChat() { if m.chatDbChanged { - showSettings = false + homeTab = .chats DispatchQueue.main.asyncAfter(deadline: .now() + 1) { resetChatCtrl() do { @@ -493,6 +493,6 @@ func deleteChatAsync() async throws { struct DatabaseView_Previews: PreviewProvider { static var previews: some View { - DatabaseView(showSettings: Binding.constant(false), chatItemTTL: .none) + DatabaseView(homeTab: Binding.constant(.chats), chatItemTTL: .none) } } diff --git a/apps/ios/Shared/Views/Home/HomeView.swift b/apps/ios/Shared/Views/Home/HomeView.swift new file mode 100644 index 0000000000..574c2a4298 --- /dev/null +++ b/apps/ios/Shared/Views/Home/HomeView.swift @@ -0,0 +1,194 @@ +// +// HomeView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 01.05.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct HomeView: View { + @EnvironmentObject var chatModel: ChatModel + @Binding var homeTab: HomeTab + @State private var userPickerVisible = false + @State private var showConnectDesktop = false + @State private var newChatMenuOption: NewChatMenuOption? = nil + + var body: some View { + ZStack(alignment: .bottomLeading) { + switch homeTab { + case .settings: settingsView() + case .contacts: contactsView() + case .chats: chatsView() + case .newChat: newChatView() + } + if userPickerVisible { + Rectangle().fill(.white.opacity(0.001)).onTapGesture { + withAnimation { + userPickerVisible.toggle() + } + } + } + UserPicker( + homeTab: $homeTab, + showConnectDesktop: $showConnectDesktop, + userPickerVisible: $userPickerVisible + ) + } + .toolbar { + ToolbarItemGroup(placement: .bottomBar) { + settingsButton() + Spacer() + contactsButton() + Spacer() + chatsButton() + Spacer() + newChatButton() + } + } + .sheet(isPresented: $showConnectDesktop) { + ConnectDesktopView() + } + } + + @ViewBuilder private func settingsButton() -> some View { + let user = chatModel.currentUser ?? User.sampleData + let multiUser = chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 + Button { + if multiUser { + withAnimation { + userPickerVisible.toggle() + } + } else { + homeTab = .settings + } + } label: { + if user.image != nil { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel)) + .padding(.trailing, 4) + let allRead = chatModel.users + .filter { u in !u.user.activeUser && !u.user.hidden } + .allSatisfy { u in u.unreadCount == 0 } + if !allRead { + userUnreadBadge(size: 12) + } + } + } else { + VStack(spacing: 4) { + Image(systemName: multiUser ? "person.2.fill" : "gearshape.fill") + Text("Users") + .font(.caption) + } + } + } + .foregroundColor(homeTab == .settings ? .accentColor : .secondary) + } + + private func userUnreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View { + Circle() + .frame(width: size, height: size) + .foregroundColor(.accentColor) + } + + private func contactsButton() -> some View { + Button { + homeTab = .contacts + } label: { + VStack(spacing: 4) { + Image(systemName: "person.crop.circle.fill") + Text("Contacts") + .font(.caption) + } + } + .foregroundColor(homeTab == .contacts ? .accentColor : .secondary) + } + + private func chatsButton() -> some View { + Button { + homeTab = .chats + } label: { + VStack(spacing: 4) { + Image(systemName: "message.fill") + Text("Chats") + .font(.caption) + } + } + .foregroundColor(homeTab == .chats ? .accentColor : .secondary) + } + + @ViewBuilder private func newChatButton() -> some View { + if homeTab != .newChat { + Menu { + Button { + newChatMenuOption = .newContact + homeTab = .newChat + } label: { + Text("Add contact") + } + Button { + newChatMenuOption = .newGroup + homeTab = .newChat + } label: { + Text("Create group") + } + } label: { + newChatButtonLabel() + } + .foregroundColor(.secondary) + } else { + Button {} label: { + newChatButtonLabel() + } + .foregroundColor(.accentColor) + } + } + + private func newChatButtonLabel() -> some View { + VStack(spacing: 4) { + Image(systemName: "square.and.pencil") + Text("New chat") + .font(.caption) + } + } + + private func settingsView() -> some View { + SettingsView(homeTab: $homeTab) + } + + private func contactsView() -> some View { + // TODO + VStack { + Text("Contacts") + } + } + + private func chatsView() -> some View { + // TODO remove top bar, move search to bottom + // TODO hide toolbar when in chat + // TODO onboarding buttons (remove?) + ChatsView() + } + + @ViewBuilder private func newChatView() -> some View { + // TODO doesn't fit + // TODO alerts don't work + // TODO dismiss on connect + // TODO dismiss when creating group + // TODO chat stopped (see chatsStoppedIcon in ChatsView) + switch newChatMenuOption { + case .newContact: + NewChatView(selection: .invite) + case .newGroup: + AddGroupView() + case nil: + EmptyView() + } + } +} + +#Preview { + HomeView(homeTab: Binding.constant(.chats)) +} diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 645de4c3f8..12ef0b907c 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -54,7 +54,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable { struct MigrateFromDevice: View { @EnvironmentObject var m: ChatModel @Environment(\.dismiss) var dismiss: DismissAction - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @Binding var showProgressOnSettings: Bool @State private var migrationState: MigrationFromState = .chatStopInProgress @State private var useKeychain = storeDBPassphraseGroupDefault.get() @@ -555,7 +555,7 @@ struct MigrateFromDevice: View { } catch let error { fatalError("Error starting chat \(responseError(error))") } - showSettings = false + homeTab = .chats } } catch let error { alert = .error(title: "Error deleting database", error: responseError(error)) @@ -579,7 +579,7 @@ struct MigrateFromDevice: View { // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered if dismiss || m.chatDbStatus != .ok { await MainActor.run { - showSettings = false + homeTab = .chats } } } @@ -729,6 +729,6 @@ private class MigrationChatReceiver { struct MigrateFromDevice_Previews: PreviewProvider { static var previews: some View { - MigrateFromDevice(showSettings: Binding.constant(true), showProgressOnSettings: Binding.constant(false)) + MigrateFromDevice(homeTab: Binding.constant(.chats), showProgressOnSettings: Binding.constant(false)) } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index e532448a90..4d0054e9f5 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -154,7 +154,7 @@ struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var sceneDelegate: SceneDelegate - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @State private var showProgress: Bool = false var body: some View { @@ -185,7 +185,7 @@ struct SettingsView: View { } NavigationLink { - UserProfilesView(showSettings: $showSettings) + UserProfilesView(homeTab: $homeTab) } label: { settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") } } @@ -215,7 +215,7 @@ struct SettingsView: View { } NavigationLink { - MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress) + MigrateFromDevice(homeTab: $homeTab, showProgressOnSettings: $showProgress) .navigationTitle("Migrate device") .navigationBarTitleDisplayMode(.large) } label: { @@ -276,7 +276,7 @@ struct SettingsView: View { Section("Help") { if let user = user { NavigationLink { - ChatHelp(showSettings: $showSettings) + ChatHelp(homeTab: $homeTab) .navigationTitle("Welcome \(user.displayName)!") .frame(maxHeight: .infinity, alignment: .top) } label: { @@ -298,7 +298,7 @@ struct SettingsView: View { } settingsRow("number") { Button("Send questions and ideas") { - showSettings = false + homeTab = .chats DispatchQueue.main.async { UIApplication.shared.open(simplexTeamURL) } @@ -352,7 +352,7 @@ struct SettingsView: View { private func chatDatabaseRow() -> some View { NavigationLink { - DatabaseView(showSettings: $showSettings, chatItemTTL: chatModel.chatItemTTL) + DatabaseView(homeTab: $homeTab, chatItemTTL: chatModel.chatItemTTL) .navigationTitle("Your chat database") } label: { let color: Color = chatModel.chatDbEncrypted == false ? .orange : .secondary @@ -446,9 +446,8 @@ struct SettingsView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.currentUser = User.sampleData - @State var showSettings = false - return SettingsView(showSettings: $showSettings) + return SettingsView(homeTab: Binding.constant(.chats)) .environmentObject(chatModel) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 8c1a3bf4e1..ab13c36b01 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -8,7 +8,7 @@ import SimpleXChat struct UserProfilesView: View { @EnvironmentObject private var m: ChatModel - @Binding var showSettings: Bool + @Binding var homeTab: HomeTab @Environment(\.editMode) private var editMode @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @@ -280,7 +280,7 @@ struct UserProfilesView: View { await MainActor.run { onboardingStageDefault.set(.step1_SimpleXInfo) m.onboardingStage = .step1_SimpleXInfo - showSettings = false + homeTab = .chats } } } else { @@ -403,6 +403,6 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String { struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { - UserProfilesView(showSettings: Binding.constant(true)) + UserProfilesView(homeTab: Binding.constant(.settings)) } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c5d3665824..1d3c76ee59 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; + 642CE4952BE2651E00AD7757 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642CE4942BE2651E00AD7757 /* HomeView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; @@ -168,6 +169,7 @@ 644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; }; 644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */; }; 6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; }; + 64593F122BE28E5F00CD75D2 /* ChatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64593F112BE28E5F00CD75D2 /* ChatsView.swift */; }; 646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; }; 646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; }; 647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */; }; @@ -450,6 +452,7 @@ 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; + 642CE4942BE2651E00AD7757 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; @@ -463,6 +466,7 @@ 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = ""; }; 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkedDeletedItemView.swift; sourceTree = ""; }; 6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = ""; }; + 64593F112BE28E5F00CD75D2 /* ChatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsView.swift; sourceTree = ""; }; 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; }; 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = ""; }; 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberInfoView.swift; sourceTree = ""; }; @@ -562,6 +566,7 @@ 5C2E260D27A30E2400F70299 /* Views */ = { isa = PBXGroup; children = ( + 642CE4932BE2650500AD7757 /* Home */, 5CB0BA8C282711BC00B3292C /* Onboarding */, 3C714775281C080100CB4D4B /* Call */, 5C971E1F27AEBF7000C8A3CE /* Helpers */, @@ -802,6 +807,7 @@ 5C13730A28156D2700F43030 /* ContactConnectionView.swift */, 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */, 18415835CBD939A9ABDC108A /* UserPicker.swift */, + 64593F112BE28E5F00CD75D2 /* ChatsView.swift */, ); path = ChatList; sourceTree = ""; @@ -899,6 +905,14 @@ path = Database; sourceTree = ""; }; + 642CE4932BE2650500AD7757 /* Home */ = { + isa = PBXGroup; + children = ( + 642CE4942BE2651E00AD7757 /* HomeView.swift */, + ); + path = Home; + sourceTree = ""; + }; 6440CA01288AEC770062C672 /* Group */ = { isa = PBXGroup; children = ( @@ -1212,6 +1226,7 @@ 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */, + 642CE4952BE2651E00AD7757 /* HomeView.swift in Sources */, 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */, 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, @@ -1273,6 +1288,7 @@ 18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */, 1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */, 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */, + 64593F122BE28E5F00CD75D2 /* ChatsView.swift in Sources */, 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */, 18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */, 184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */,