From a0763b3a43bb60d139062edfc15e541192f17ac8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 6 Aug 2024 08:12:42 +0100 Subject: [PATCH 1/2] ios: same size of action buttons in chat info sheets (#4587) --- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 163 +++++++++--------- .../Views/Chat/Group/GroupChatInfoView.swift | 54 +++--- .../Chat/Group/GroupMemberInfoView.swift | 120 ++++++------- 3 files changed, 169 insertions(+), 168 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 8b151c2f5d..0303ab2840 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -147,13 +147,14 @@ struct ChatInfoView: View { } .listRowBackground(Color.clear) .listRowSeparator(.hidden) + .padding(.bottom, 18) GeometryReader { g in HStack(alignment: .center, spacing: 8) { let buttonWidth = g.size.width / 4 searchButton(width: buttonWidth) - AudioCallButton(chat: chat, contact: contact, showAlert: { alert = .someAlert(alert: $0) }, width: buttonWidth) - VideoButton(chat: chat, contact: contact, showAlert: { alert = .someAlert(alert: $0) }, width: buttonWidth) + AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } muteButton(width: buttonWidth) } } @@ -328,11 +329,10 @@ struct ChatInfoView: View { } private func contactInfoHeader() -> some View { - VStack { + VStack(spacing: 8) { let cInfo = chat.chatInfo ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) - .padding(.top, 12) - .padding() + .padding(.vertical, 12) if contact.verified { ( Text(Image(systemName: "checkmark.shield")) @@ -394,21 +394,19 @@ struct ChatInfoView: View { } private func searchButton(width: CGFloat) -> some View { - InfoViewActionButtonLayout(image: "magnifyingglass", title: "search", width: width) - .onTapGesture { - dismiss() - onSearch() - } - .disabled(!contact.ready || chat.chatItems.isEmpty) + InfoViewButton(image: "magnifyingglass", title: "search", width: width) { + dismiss() + onSearch() + } + .disabled(!contact.ready || chat.chatItems.isEmpty) } private func muteButton(width: CGFloat) -> some View { - InfoViewActionButtonLayout( + InfoViewButton( image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill", title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute", width: width - ) - .onTapGesture { + ) { toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) } .disabled(!contact.ready || !contact.active) @@ -623,8 +621,8 @@ struct ChatInfoView: View { struct AudioCallButton: View { var chat: Chat var contact: Contact + var width: CGFloat var showAlert: (SomeAlert) -> Void - var width: CGFloat = infoViewActionButtonWidth var body: some View { CallButton( @@ -633,8 +631,8 @@ struct AudioCallButton: View { image: "phone.fill", title: "call", mediaType: .audio, - showAlert: showAlert, - width: width + width: width, + showAlert: showAlert ) } } @@ -642,8 +640,8 @@ struct AudioCallButton: View { struct VideoButton: View { var chat: Chat var contact: Contact + var width: CGFloat var showAlert: (SomeAlert) -> Void - var width: CGFloat = infoViewActionButtonWidth var body: some View { CallButton( @@ -652,8 +650,8 @@ struct VideoButton: View { image: "video.fill", title: "video", mediaType: .video, - showAlert: showAlert, - width: width + width: width, + showAlert: showAlert ) } } @@ -664,91 +662,89 @@ private struct CallButton: View { var image: String var title: LocalizedStringKey var mediaType: CallMediaType + var width: CGFloat var showAlert: (SomeAlert) -> Void - var width: CGFloat = infoViewActionButtonWidth var body: some View { let canCall = contact.ready && contact.active && chat.chatInfo.featureEnabled(.calls) && ChatModel.shared.activeCall == nil - InfoViewActionButtonLayout(image: image, title: title, disabledLook: !canCall, width: width) - .onTapGesture { - if canCall { - CallController.shared.startCall(contact, mediaType) - } else if contact.nextSendGrpInv { + InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) { + if canCall { + CallController.shared.startCall(contact, mediaType) + } else if contact.nextSendGrpInv { + showAlert(SomeAlert( + alert: mkAlert( + title: "Can't call contact", + message: "Send message to enable calls." + ), + id: "can't call contact, send message" + )) + } else if !contact.active { + showAlert(SomeAlert( + alert: mkAlert( + title: "Can't call contact", + message: "Contact is deleted." + ), + id: "can't call contact, contact deleted" + )) + } else if !contact.ready { + showAlert(SomeAlert( + alert: mkAlert( + title: "Can't call contact", + message: "Connecting to contact, please wait or check later!" + ), + id: "can't call contact, contact not ready" + )) + } else if !chat.chatInfo.featureEnabled(.calls) { + switch chat.chatInfo.showEnableCallsAlert { + case .userEnable: + showAlert(SomeAlert( + alert: Alert( + title: Text("Allow calls?"), + message: Text("You need to allow your contact to call to be able to call them."), + primaryButton: .default(Text("Allow")) { + allowFeatureToContact(contact, .calls) + }, + secondaryButton: .cancel() + ), + id: "allow calls" + )) + case .askContact: showAlert(SomeAlert( alert: mkAlert( - title: "Can't call contact", - message: "Send message to enable calls." + title: "Calls prohibited!", + message: "Please ask your contact to enable calls." ), - id: "can't call contact, send message" + id: "calls prohibited, ask contact" )) - } else if !contact.active { + case .other: showAlert(SomeAlert( alert: mkAlert( - title: "Can't call contact", - message: "Contact is deleted." - ), - id: "can't call contact, contact deleted" - )) - } else if !contact.ready { - showAlert(SomeAlert( - alert: mkAlert( - title: "Can't call contact", - message: "Connecting to contact, please wait or check later!" - ), - id: "can't call contact, contact not ready" - )) - } else if !chat.chatInfo.featureEnabled(.calls) { - switch chat.chatInfo.showEnableCallsAlert { - case .userEnable: - showAlert(SomeAlert( - alert: Alert( - title: Text("Allow calls?"), - message: Text("You need to allow your contact to call to be able to call them."), - primaryButton: .default(Text("Allow")) { - allowFeatureToContact(contact, .calls) - }, - secondaryButton: .cancel() - ), - id: "allow calls" - )) - case .askContact: - showAlert(SomeAlert( - alert: mkAlert( - title: "Calls prohibited!", - message: "Please ask your contact to enable calls." - ), - id: "calls prohibited, ask contact" - )) - case .other: - showAlert(SomeAlert( - alert: mkAlert( - title: "Calls prohibited!", - message: "Please check yours and your contact preferences." - ) - , id: "calls prohibited, other" - )) - } - } else { - showAlert(SomeAlert( - alert: mkAlert(title: "Can't call contact"), - id: "can't call contact" + title: "Calls prohibited!", + message: "Please check yours and your contact preferences." + ) + , id: "calls prohibited, other" )) } + } else { + showAlert(SomeAlert( + alert: mkAlert(title: "Can't call contact"), + id: "can't call contact" + )) } - .disabled(ChatModel.shared.activeCall != nil) + } + .disabled(ChatModel.shared.activeCall != nil) } } -let infoViewActionButtonWidth: CGFloat = 83 - let infoViewActionButtonHeight: CGFloat = 60 -struct InfoViewActionButtonLayout: View { +struct InfoViewButton: View { var image: String var title: LocalizedStringKey var disabledLook: Bool = false - var width: CGFloat = infoViewActionButtonWidth + var width: CGFloat + var action: () -> Void var body: some View { VStack(spacing: 4) { @@ -765,6 +761,7 @@ struct InfoViewActionButtonLayout: View { .cornerRadius(10.0) .frame(width: width, height: infoViewActionButtonHeight) .disabled(disabledLook) + .onTapGesture(perform: action) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 94f63fea1a..50eb883e92 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -70,6 +70,7 @@ struct GroupChatInfoView: View { List { groupInfoHeader() .listRowBackground(Color.clear) + .padding(.bottom, 18) infoActionButtons() .padding(.horizontal) @@ -209,31 +210,33 @@ struct GroupChatInfoView: View { } func infoActionButtons() -> some View { - HStack(alignment: .center, spacing: 8) { - searchButton() - if groupInfo.canAddMembers { - addMembersActionButton() + GeometryReader { g in + let buttonWidth = g.size.width / 4 + HStack(alignment: .center, spacing: 8) { + searchButton(width: buttonWidth) + if groupInfo.canAddMembers { + addMembersActionButton(width: buttonWidth) + } + muteButton(width: buttonWidth) } - muteButton() + .frame(maxWidth: .infinity, alignment: .center) } } - private func searchButton() -> some View { - InfoViewActionButtonLayout(image: "magnifyingglass", title: "search") - .onTapGesture { - dismiss() - onSearch() - } - .disabled(!groupInfo.ready || chat.chatItems.isEmpty) + private func searchButton(width: CGFloat) -> some View { + InfoViewButton(image: "magnifyingglass", title: "search", width: width) { + dismiss() + onSearch() + } + .disabled(!groupInfo.ready || chat.chatItems.isEmpty) } - @ViewBuilder private func addMembersActionButton() -> some View { + @ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View { if chat.chatInfo.incognito { ZStack { - InfoViewActionButtonLayout(image: "link.badge.plus", title: "invite") - .onTapGesture { - groupLinkNavLinkActive = true - } + InfoViewButton(image: "link.badge.plus", title: "invite", width: width) { + groupLinkNavLinkActive = true + } NavigationLink(isActive: $groupLinkNavLinkActive) { groupLinkDestinationView() @@ -246,10 +249,9 @@ struct GroupChatInfoView: View { .disabled(!groupInfo.ready) } else { ZStack { - InfoViewActionButtonLayout(image: "person.fill.badge.plus", title: "invite") - .onTapGesture { - addMembersNavLinkActive = true - } + InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) { + addMembersNavLinkActive = true + } NavigationLink(isActive: $addMembersNavLinkActive) { addMembersDestinationView() @@ -263,12 +265,12 @@ struct GroupChatInfoView: View { } } - private func muteButton() -> some View { - InfoViewActionButtonLayout( + private func muteButton(width: CGFloat) -> some View { + InfoViewButton( image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill", - title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute" - ) - .onTapGesture { + title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute", + width: width + ) { toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) } .disabled(!groupInfo.ready) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 283d64aee8..c9f34a61c0 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -84,7 +84,8 @@ struct GroupMemberInfoView: View { groupMemberInfoHeader(member) .listRowBackground(Color.clear) .listRowSeparator(.hidden) - + .padding(.bottom, 18) + infoActionButtons(member) .padding(.horizontal) .frame(maxWidth: .infinity) @@ -254,29 +255,33 @@ struct GroupMemberInfoView: View { } func infoActionButtons(_ member: GroupMember) -> some View { - HStack(alignment: .center, spacing: 8) { - if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) { - knownDirectChatButton(chat) - AudioCallButton(chat: chat, contact: contact, showAlert: { alert = .someAlert(alert: $0) }) - VideoButton(chat: chat, contact: contact, showAlert: { alert = .someAlert(alert: $0) }) - } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { - if let contactId = member.memberContactId { - newDirectChatButton(contactId) - } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { - createMemberContactButton() + GeometryReader { g in + let buttonWidth = g.size.width / 4 + HStack(alignment: .center, spacing: 8) { + if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) { + knownDirectChatButton(chat, width: buttonWidth) + AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + if let contactId = member.memberContactId { + newDirectChatButton(contactId, width: buttonWidth) + } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { + createMemberContactButton(width: buttonWidth) + } + InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert() + } + InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert() + } + } else { // no known contact chat && directMessages are off + InfoViewButton(image: "message.fill", title: "message", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't message member") + } + InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member") + } + InfoViewButton(image: "video.fill", title: "video", disabledLook: true, width: buttonWidth) { showDirectMessagesProhibitedAlert("Can't call member") + } } - InfoViewActionButtonLayout(image: "phone.fill", title: "call", disabledLook: true) - .onTapGesture { showSendMessageToEnableCallsAlert() } - InfoViewActionButtonLayout(image: "video.fill", title: "video", disabledLook: true) - .onTapGesture { showSendMessageToEnableCallsAlert() } - } else { // no known contact chat && directMessages are off - InfoViewActionButtonLayout(image: "message.fill", title: "message", disabledLook: true) - .onTapGesture { showDirectMessagesProhibitedAlert("Can't message member") } - InfoViewActionButtonLayout(image: "phone.fill", title: "call", disabledLook: true) - .onTapGesture { showDirectMessagesProhibitedAlert("Can't call member") } - InfoViewActionButtonLayout(image: "video.fill", title: "video", disabledLook: true) - .onTapGesture { showDirectMessagesProhibitedAlert("Can't call member") } } + .frame(maxWidth: .infinity, alignment: .center) } } @@ -314,56 +319,53 @@ struct GroupMemberInfoView: View { } } - func knownDirectChatButton(_ chat: Chat) -> some View { - InfoViewActionButtonLayout(image: "message.fill", title: "message") - .onTapGesture { + func knownDirectChatButton(_ chat: Chat, width: CGFloat) -> some View { + InfoViewButton(image: "message.fill", title: "message", width: width) { + dismissAllSheets(animated: true) + DispatchQueue.main.async { + chatModel.chatId = chat.id + } + } + } + + func newDirectChatButton(_ contactId: Int64, width: CGFloat) -> some View { + InfoViewButton(image: "message.fill", title: "message", width: width) { + do { + let chat = try apiGetChat(type: .direct, id: contactId) + chatModel.addChat(chat) dismissAllSheets(animated: true) DispatchQueue.main.async { chatModel.chatId = chat.id } + } catch let error { + logger.error("openDirectChatButton apiGetChat error: \(responseError(error))") } + } } - func newDirectChatButton(_ contactId: Int64) -> some View { - InfoViewActionButtonLayout(image: "message.fill", title: "message") - .onTapGesture { + func createMemberContactButton(width: CGFloat) -> some View { + InfoViewButton(image: "message.fill", title: "message", width: width) { + progressIndicator = true + Task { do { - let chat = try apiGetChat(type: .direct, id: contactId) - chatModel.addChat(chat) - dismissAllSheets(animated: true) - DispatchQueue.main.async { - chatModel.chatId = chat.id + let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) + await MainActor.run { + progressIndicator = false + chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) + dismissAllSheets(animated: true) + chatModel.chatId = memberContact.id + chatModel.setContactNetworkStatus(memberContact, .connected) } } catch let error { - logger.error("openDirectChatButton apiGetChat error: \(responseError(error))") - } - } - } - - func createMemberContactButton() -> some View { - InfoViewActionButtonLayout(image: "message.fill", title: "message") - .onTapGesture { - progressIndicator = true - Task { - do { - let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) - await MainActor.run { - progressIndicator = false - chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) - dismissAllSheets(animated: true) - chatModel.chatId = memberContact.id - chatModel.setContactNetworkStatus(memberContact, .connected) - } - } catch let error { - logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") - let a = getErrorAlert(error, "Error creating member contact") - await MainActor.run { - progressIndicator = false - alert = .error(title: a.title, error: a.message) - } + logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") + let a = getErrorAlert(error, "Error creating member contact") + await MainActor.run { + progressIndicator = false + alert = .error(title: a.title, error: a.message) } } } + } } private func groupMemberInfoHeader(_ mem: GroupMember) -> some View { From f893ad15de934737f2f440bf66d71cb2f84f091c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:03:25 +0400 Subject: [PATCH 2/2] ui: only show subsription summary indicator after any chat is created; ios: fix servers summary sheet dismissal, screen protection (#4590) --- .../Shared/Views/ChatList/ChatListView.swift | 23 ++++++++++++------- .../common/views/chatlist/ChatListView.kt | 8 +++++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 1cb00e3a4d..8a3a0cf37b 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -266,13 +266,19 @@ struct SubsStatusIndicator: View { @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false var body: some View { - Button { - showServersSummary = true - } label: { - HStack(spacing: 4) { - SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess) - if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess) + ZStack { + if subs.total == 0 && !hasSess { + EmptyView() + } else { + Button { + showServersSummary = true + } label: { + HStack(spacing: 4) { + SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess) + if showSubscriptionPercentage { + SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess) + } + } } } } @@ -282,8 +288,9 @@ struct SubsStatusIndicator: View { .onDisappear { stopTimer() } - .sheet(isPresented: $showServersSummary) { + .appSheet(isPresented: $showServersSummary) { ServersSummaryView() + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 707a183dd9..7b7c07bef5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -359,8 +359,12 @@ fun SubscriptionStatusIndicator(click: (() -> Unit)) { } } - SimpleButtonFrame(click = click) { - SubscriptionStatusIndicatorView(subs = subs, hasSess = hasSess) + if (subs.total == 0 && !hasSess) { + Box {} + } else { + SimpleButtonFrame(click = click) { + SubscriptionStatusIndicatorView(subs = subs, hasSess = hasSess) + } } }