Merge branch 'master' into ios-one-hand-ui

This commit is contained in:
spaced4ndy
2024-08-06 12:10:47 +04:00
5 changed files with 190 additions and 178 deletions

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -292,13 +292,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)
}
}
}
}
}
@@ -308,8 +314,9 @@ struct SubsStatusIndicator: View {
.onDisappear {
stopTimer()
}
.sheet(isPresented: $showServersSummary) {
.appSheet(isPresented: $showServersSummary) {
ServersSummaryView()
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
}

View File

@@ -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)
}
}
}