diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 7d08a1a091..c7e642ac58 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -46,6 +46,7 @@ struct ChatListNavLink: View { @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = false @ObservedObject var chat: Chat + @Binding var parentSheet: SomeSheet? @State private var showContactRequestDialog = false @State private var showJoinGroupDialog = false @State private var showContactConnectionInfo = false @@ -122,7 +123,6 @@ struct ChatListNavLink: View { markReadButton() toggleFavoriteButton() toggleNtfsButton(chat: chat) - tagChatButton(chat) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !chat.chatItems.isEmpty { @@ -141,7 +141,7 @@ struct ChatListNavLink: View { deleteLabel } .tint(.red) - untagChatButton(chat) + tagChatButton(chat) } .frame(height: dynamicRowHeight) } @@ -193,6 +193,7 @@ struct ChatListNavLink: View { if groupInfo.canDelete { deleteGroupChatButton(groupInfo) } + tagChatButton(chat) } default: NavLinkPlain( @@ -215,7 +216,14 @@ struct ChatListNavLink: View { leaveGroupChatButton(groupInfo) } if groupInfo.canDelete { - deleteGroupChatButton(groupInfo) + if !groupInfo.membership.memberCurrent { + deleteGroupChatButton(groupInfo) + tagChatButton(chat) + } else { + // TODO: Show ... + } + } else { + tagChatButton(chat) } } } @@ -311,37 +319,22 @@ struct ChatListNavLink: View { private func tagChatButton(_ chat: Chat) -> some View { Button { - sheet = SomeSheet( + parentSheet = SomeSheet( content: { AnyView( - ChatListTag(chat: chat) + NavigationView { + ChatListTag(chat: chat) + } ) }, - id: "tag sheet", + id: "lists sheet", fraction: 0.7 ) } label: { - SwipeLabel(NSLocalizedString("Tag", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI) + SwipeLabel(NSLocalizedString("Lists", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI) } .tint(Color.indigo) } - - private func untagChatButton(_ chat: Chat) -> some View { - Button { - sheet = SomeSheet( - content: { - AnyView( - ChatListTag(chat: chat, untag: true) - ) - }, - id: "tag sheet", - fraction: 0.7 - ) - } label: { - SwipeLabel(NSLocalizedString("Untag", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI) - } - .tint(Color.yellow.opacity(0.8)) - } private func clearNoteFolderButton() -> some View { Button { @@ -522,99 +515,50 @@ struct ChatListNavLink: View { struct ChatListTag: View { var chat: Chat - var untag: Bool = false @EnvironmentObject var chatTagsModel: ChatTagsModel @EnvironmentObject var m: ChatModel - @State private var emoji: String = "" - @State private var name: String = "" var chatTagsIds: [Int64] { chat.chatInfo.contact?.chatTags ?? chat.chatInfo.groupInfo?.chatTags ?? [] } var body: some View { List { - if untag { - untagView() - } else { - tagView() - } - } - } - - @ViewBuilder private func tagView() -> some View { - Section { - TextField("Emoji..", text: $emoji) - TextField("Tag name...", text: $name) - Button { - createChatTag() + NavigationLink { + CreateChatListTag(chat: chat) } label: { - Text("Create tag") + Label("Create list", systemImage: "plus") } - } - - let tagsToPick = chatTagsModel.tags.compactMap { tag in - if case let .chatTag(emoji, text, tagId) = tag, !chatTagsIds.contains(tagId) { - return (emoji, text, tagId) - } else { - return nil - } - } - - if !tagsToPick.isEmpty { - Section { - ForEach(tagsToPick, id: \.0) { tag in - let (emoji, text, tagId) = tag - Button { - tagChat(tagId) - } label: { - Text("\(emoji) \(text)") + + if !chatTagsModel.tags.isEmpty { + ForEach(chatTagsModel.tags) { tag in + if case let .chatTag(emoji, text, tagId) = tag { + let selected = chatTagsIds.contains(tagId) + + HStack { + Text(emoji) + Text(text) + .padding(.leading, 12) + Spacer() + radioButton(selected: selected) + } + .onTapGesture { + if selected { + untagChat(tagId) + } else { + tagChat(tagId) + } + } + } else { + EmptyView() } } - } header: { - Text("Choose existing") } } } - @ViewBuilder private func untagView() -> some View { - Section { - ForEach(chatTagsModel.tags) { tag in - if case let .chatTag(emoji, text, tagId) = tag, chatTagsIds.contains(tagId) { - Button { - untagChat(tagId) - } label: { - Text("\(emoji) \(text)") - } - } else { - EmptyView() - } - } - } header: { - Text("Choose existing") - } - } - - private func createChatTag() { - Task { - do { - let (userTags, chatTags) = try await apiCreateChatTag( - type: chat.chatInfo.chatType, - id: chat.chatInfo.apiId, - tag: ChatTagData(emoji: emoji, text: name) - ) - - await MainActor.run { - chatTagsModel.tags = userTags.map { - .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) - } - updateChatTags(chat: chat, chatTags: chatTags) - } - } catch let error { - showAlert( - NSLocalizedString("Error creating tag", comment: "alert title"), - message: responseError(error) - ) - } - } + @ViewBuilder private func radioButton(selected: Bool) -> some View { + Image(systemName: selected ? "checkmark.circle.fill" : "circle") + .imageScale(.large) + .foregroundStyle(selected ? Color.accentColor : Color(.tertiaryLabel)) } private func tagChat(_ tagId: Int64) { @@ -634,7 +578,7 @@ struct ChatListTag: View { } } catch let error { showAlert( - NSLocalizedString("Error tagging chat", comment: "alert title"), + NSLocalizedString("Error adding chat to list", comment: "alert title"), message: responseError(error) ) } @@ -658,7 +602,7 @@ struct ChatListTag: View { } } catch let error { showAlert( - NSLocalizedString("Error untagging chat", comment: "alert title"), + NSLocalizedString("Error removing chat from list", comment: "alert title"), message: responseError(error) ) } @@ -677,6 +621,58 @@ struct ChatListTag: View { } } +struct CreateChatListTag: View { + var chat: Chat + @Environment(\.presentationMode) var presentationMode + @EnvironmentObject var chatTagsModel: ChatTagsModel + @EnvironmentObject var m: ChatModel + @State private var emoji: String = "" + @State private var name: String = "" + + var body: some View { + List { + TextField("Emoji..", text: $emoji) + TextField("List name...", text: $name) + Button { + createChatTag() + } label: { + Text("Create list") + } + } + } + + private func createChatTag() { + Task { + do { + let (userTags, chatTags) = try await apiCreateChatTag( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + tag: ChatTagData(emoji: emoji, text: name) + ) + + await MainActor.run { + chatTagsModel.tags = userTags.map { + .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) + } + if var contact = chat.chatInfo.contact { + contact.chatTags = chatTags + m.updateContact(contact) + } else if var group = chat.chatInfo.groupInfo { + group.chatTags = chatTags + m.updateGroup(group) + } + presentationMode.wrappedValue.dismiss() + } + } catch let error { + showAlert( + NSLocalizedString("Error creating list", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert { Alert( title: Text("Reject contact request"), @@ -778,15 +774,15 @@ struct ChatListNavLink_Previews: PreviewProvider { ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.direct, chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] - )) + ), parentSheet: .constant(nil)) ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.direct, chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] - )) + ), parentSheet: .constant(nil)) ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.contactRequest, chatItems: [] - )) + ), parentSheet: .constant(nil)) } .previewLayout(.fixed(width: 360, height: 82)) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index d3fbb4b20c..aa7761f10a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -117,6 +117,7 @@ struct ChatListView: View { @State private var searchChatFilteredBySimplexLink: String? = nil @State private var scrollToSearchBar = false @State private var userPickerShown: Bool = false + @State private var sheet: SomeSheet? = nil @StateObject private var chatTagsModel = ChatTagsModel.shared @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @@ -163,6 +164,17 @@ struct ChatListView: View { } } } + .sheet(item: $sheet) { + if #available(iOS 16.0, *) { + $0.content + .presentationDetents([.fraction($0.fraction)]) + .environmentObject(chatTagsModel) + } else { + $0.content + .environmentObject(chatTagsModel) + + } + } } private var chatListView: some View { @@ -308,7 +320,7 @@ struct ChatListView: View { } if #available(iOS 16.0, *) { ForEach(cs, id: \.viewId) { chat in - ChatListNavLink(chat: chat) + ChatListNavLink(chat: chat, parentSheet: $sheet) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .padding(.trailing, -16) .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) @@ -320,7 +332,7 @@ struct ChatListView: View { VStack(spacing: .zero) { Divider() .padding(.leading, 16) - ChatListNavLink(chat: chat) + ChatListNavLink(chat: chat, parentSheet: $sheet) .padding(.horizontal, 8) .padding(.vertical, 6) }