ios: disappearing messages (#1614)
* ios: disappearing messages * show ttl in meta if different * mark messages as disappearing when read * previews
This commit is contained in:
committed by
GitHub
parent
36eba01ef4
commit
e1740a8be4
@@ -289,10 +289,7 @@ final class ChatModel: ObservableObject {
|
||||
private func markCurrentChatRead(fromIndex i: Int = 0) {
|
||||
var j = i
|
||||
while j < reversedChatItems.count {
|
||||
if case .rcvNew = reversedChatItems[j].meta.itemStatus {
|
||||
reversedChatItems[j].meta.itemStatus = .rcvRead
|
||||
reversedChatItems[j].viewTimestamp = .now
|
||||
}
|
||||
markChatItemRead_(j)
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
@@ -347,9 +344,19 @@ final class ChatModel: ObservableObject {
|
||||
// update preview
|
||||
decreaseUnreadCounter(cInfo)
|
||||
// update current chat
|
||||
if chatId == cInfo.id, let j = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
|
||||
reversedChatItems[j].meta.itemStatus = .rcvRead
|
||||
reversedChatItems[j].viewTimestamp = .now
|
||||
if chatId == cInfo.id, let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) {
|
||||
markChatItemRead_(i)
|
||||
}
|
||||
}
|
||||
|
||||
private func markChatItemRead_(_ i: Int) {
|
||||
let meta = reversedChatItems[i].meta
|
||||
if case .rcvNew = meta.itemStatus {
|
||||
reversedChatItems[i].meta.itemStatus = .rcvRead
|
||||
reversedChatItems[i].viewTimestamp = .now
|
||||
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
|
||||
reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,4 +520,6 @@ final class Chat: ObservableObject, Identifiable {
|
||||
var id: ChatId { get { chatInfo.id } }
|
||||
|
||||
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
||||
|
||||
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
|
||||
}
|
||||
|
||||
@@ -161,5 +161,6 @@ struct CIFileView_Previews: PreviewProvider {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,37 +10,20 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIMetaView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var metaColor = Color.secondary
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
if !chatItem.isDeletedContent {
|
||||
if chatItem.meta.itemEdited {
|
||||
statusImage("pencil", metaColor, 9)
|
||||
}
|
||||
|
||||
switch chatItem.meta.itemStatus {
|
||||
case .sndSent:
|
||||
statusImage("checkmark", metaColor)
|
||||
case .sndErrorAuth:
|
||||
statusImage("multiply", .red)
|
||||
case .sndError:
|
||||
statusImage("exclamationmark.triangle.fill", .yellow)
|
||||
case .rcvNew:
|
||||
statusImage("circlebadge.fill", Color.accentColor)
|
||||
default: EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
chatItem.timestampText
|
||||
.font(.caption)
|
||||
.foregroundColor(metaColor)
|
||||
if chatItem.isDeletedContent {
|
||||
chatItem.timestampText.font(.caption).foregroundColor(metaColor)
|
||||
} else {
|
||||
ciMetaText(chatItem.meta, chatTTL: chat.chatInfo.timedMessagesTTL, color: metaColor)
|
||||
}
|
||||
}
|
||||
|
||||
private func statusImage(_ systemName: String, _ color: Color, _ maxHeight: CGFloat = 8) -> some View {
|
||||
Image(systemName: systemName)
|
||||
private func statusImage(_ icon: String, _ color: Color, _ maxHeight: CGFloat = 8) -> some View {
|
||||
Image(systemName: icon)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(color)
|
||||
@@ -48,6 +31,31 @@ struct CIMetaView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear) -> Text {
|
||||
var r = Text("")
|
||||
if meta.itemEdited {
|
||||
r = r + statusIconText("pencil", color)
|
||||
}
|
||||
if meta.disappearing {
|
||||
r = r + statusIconText("timer", color).font(.caption2)
|
||||
let ttl = meta.itemTimed?.ttl
|
||||
if ttl != chatTTL {
|
||||
r = r + Text(TimedMessagesPreference.shortTtlText(ttl)).foregroundColor(color)
|
||||
}
|
||||
r = r + Text(" ")
|
||||
}
|
||||
if let (icon, color) = meta.statusIcon(color) {
|
||||
r = r + statusIconText(icon, color) + Text(" ")
|
||||
} else if !meta.disappearing {
|
||||
r = r + statusIconText("circlebadge.fill", .clear) + Text(" ")
|
||||
}
|
||||
return (r + meta.timestampText.foregroundColor(color)).font(.caption)
|
||||
}
|
||||
|
||||
private func statusIconText(_ icon: String, _ color: Color) -> Text {
|
||||
Text(Image(systemName: icon)).foregroundColor(color)
|
||||
}
|
||||
|
||||
struct CIMetaView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
@@ -56,5 +64,6 @@ struct CIMetaView_Previews: PreviewProvider {
|
||||
CIMetaView(chatItem: ChatItem.getDeletedContentSample())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 100))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,16 +231,12 @@ struct CIVoiceView_Previews: PreviewProvider {
|
||||
playbackState: .playing,
|
||||
playbackTime: TimeInterval(20)
|
||||
)
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,16 +61,12 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
|
||||
)
|
||||
Group {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "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."), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ private let sentQuoteColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1,
|
||||
private let sentQuoteColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.09)
|
||||
|
||||
struct FramedItemView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var chatInfo: ChatInfo
|
||||
var chatItem: ChatItem
|
||||
@@ -40,7 +39,7 @@ struct FramedItemView: View {
|
||||
ciQuoteView(qi)
|
||||
.onTapGesture {
|
||||
if let proxy = scrollProxy,
|
||||
let ci = m.reversedChatItems.first(where: { $0.id == qi.itemId }) {
|
||||
let ci = ChatModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
|
||||
withAnimation {
|
||||
proxy.scrollTo(ci.viewId, anchor: .bottom)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ private func typing(_ w: Font.Weight = .light) -> Text {
|
||||
}
|
||||
|
||||
struct MsgContentView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var text: String
|
||||
var formattedText: [FormattedText]? = nil
|
||||
var sender: String? = nil
|
||||
@@ -66,7 +67,7 @@ struct MsgContentView: View {
|
||||
if mt.isLive {
|
||||
v = v + typingIndicator(mt.recent)
|
||||
}
|
||||
v = v + reserveSpaceForMeta(mt.timestampText, mt.itemEdited)
|
||||
v = v + reserveSpaceForMeta(mt)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -78,10 +79,8 @@ struct MsgContentView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
private func reserveSpaceForMeta(_ mt: Text, _ edited: Bool) -> Text {
|
||||
let reserve = rightToLeft ? "\n" : edited ? " " : " "
|
||||
return (Text(reserve) + mt)
|
||||
.font(.caption)
|
||||
private func reserveSpaceForMeta(_ mt: CIMeta) -> Text {
|
||||
(rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL)
|
||||
.foregroundColor(.clear)
|
||||
}
|
||||
}
|
||||
@@ -157,5 +156,6 @@ struct MsgContentView_Previews: PreviewProvider {
|
||||
sender: chatItem.memberDisplayName,
|
||||
meta: chatItem.meta
|
||||
)
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case .sndGroupEvent: eventItemView()
|
||||
case .rcvConnEvent: eventItemView()
|
||||
case .sndConnEvent: eventItemView()
|
||||
case let .rcvChatFeature(feature, enabled): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .sndChatFeature(feature, enabled): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .rcvGroupFeature(feature, preference): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .sndGroupFeature(feature, preference): chatFeatureView(feature, preference.enable.iconColor)
|
||||
case let .rcvChatFeature(feature, enabled, param): chatFeatureView(feature, enabled.iconColor, param)
|
||||
case let .sndChatFeature(feature, enabled, param): chatFeatureView(feature, enabled.iconColor, param)
|
||||
case let .rcvGroupFeature(feature, preference, param): chatFeatureView(feature, preference.enable.iconColor, param)
|
||||
case let .sndGroupFeature(feature, preference, param): chatFeatureView(feature, preference.enable.iconColor, param)
|
||||
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ struct ChatItemContentView<Content: View>: View {
|
||||
CIEventView(chatItem: chatItem)
|
||||
}
|
||||
|
||||
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
|
||||
private func chatFeatureView(_ feature: Feature, _ iconColor: Color, _ param: Int? = nil) -> some View {
|
||||
CIChatFeatureView(chatItem: chatItem, feature: feature, iconColor: iconColor)
|
||||
}
|
||||
}
|
||||
@@ -106,12 +106,13 @@ struct ChatItemView_Previews: PreviewProvider {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, false, false, true), revealed: Binding.constant(true))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false))
|
||||
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil)
|
||||
Group{
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
@@ -159,5 +160,6 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
)
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,6 @@ struct ChatView: View {
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
}
|
||||
ChatItemWithMenu(
|
||||
chat: chat,
|
||||
ci: ci,
|
||||
showMember: showMember,
|
||||
maxWidth: maxWidth,
|
||||
@@ -405,13 +404,14 @@ struct ChatView: View {
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
).padding(.leading, 8)
|
||||
)
|
||||
.padding(.leading, 8)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
ChatItemWithMenu(
|
||||
chat: chat,
|
||||
ci: ci,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
@@ -419,12 +419,14 @@ struct ChatView: View {
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
).padding(.horizontal)
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.environmentObject(chat)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChatItemWithMenu: View {
|
||||
var chat: Chat
|
||||
@EnvironmentObject var chat: Chat
|
||||
var ci: ChatItem
|
||||
var showMember: Bool = false
|
||||
var maxWidth: CGFloat
|
||||
|
||||
@@ -642,7 +642,7 @@ struct ComposeView: View {
|
||||
Task {
|
||||
do {
|
||||
var prefs = contactUserPreferencesToPreferences(contact.mergedPreferences)
|
||||
prefs.voice = Preference(allow: .yes)
|
||||
prefs.voice = SimplePreference(allow: .yes)
|
||||
if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) {
|
||||
await MainActor.run {
|
||||
chatModel.updateContact(toContact)
|
||||
|
||||
@@ -22,6 +22,7 @@ struct ContactPreferencesView: View {
|
||||
|
||||
VStack {
|
||||
List {
|
||||
timedMessagesFeatureSection()
|
||||
featureSection(.fullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, $featuresAllowed.fullDelete)
|
||||
featureSection(.voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, $featuresAllowed.voice)
|
||||
|
||||
@@ -48,10 +49,10 @@ struct ContactPreferencesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func featureSection(_ feature: ChatFeature, _ userDefault: FeatureAllowed, _ pref: ContactUserPreference, _ allowFeature: Binding<ContactFeatureAllowed>) -> some View {
|
||||
private func featureSection(_ feature: ChatFeature, _ userDefault: FeatureAllowed, _ pref: ContactUserPreference<SimplePreference>, _ allowFeature: Binding<ContactFeatureAllowed>) -> some View {
|
||||
let enabled = FeatureEnabled.enabled(
|
||||
asymmetric: feature.asymmetric,
|
||||
user: Preference(allow: allowFeature.wrappedValue.allowed),
|
||||
user: SimplePreference(allow: allowFeature.wrappedValue.allowed),
|
||||
contact: pref.contactPreference
|
||||
)
|
||||
return Section {
|
||||
@@ -62,16 +63,51 @@ struct ContactPreferencesView: View {
|
||||
}
|
||||
.frame(height: 36)
|
||||
infoRow("Contact allows", pref.contactPreference.allow.text)
|
||||
} header: {
|
||||
HStack {
|
||||
Image(systemName: "\(feature.icon).fill")
|
||||
.foregroundColor(enabled.forUser ? .green : enabled.forContact ? .yellow : .red)
|
||||
Text(feature.text)
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.enabledDescription(enabled))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
header: { featureHeader(feature, enabled) }
|
||||
footer: { featureFooter(feature, enabled) }
|
||||
}
|
||||
|
||||
private func timedMessagesFeatureSection() -> some View {
|
||||
let pref = contact.mergedPreferences.timedMessages
|
||||
let enabled = FeatureEnabled.enabled(
|
||||
asymmetric: ChatFeature.timedMessages.asymmetric,
|
||||
user: TimedMessagesPreference(allow: featuresAllowed.timedMessagesAllowed ? .yes : .no),
|
||||
contact: pref.contactPreference
|
||||
)
|
||||
return Section {
|
||||
Toggle("You allow", isOn: $featuresAllowed.timedMessagesAllowed)
|
||||
.onChange(of: featuresAllowed.timedMessagesAllowed) { allow in
|
||||
if allow {
|
||||
if featuresAllowed.timedMessagesTTL == nil {
|
||||
featuresAllowed.timedMessagesTTL = 86400
|
||||
}
|
||||
} else {
|
||||
featuresAllowed.timedMessagesTTL = currentFeaturesAllowed.timedMessagesTTL
|
||||
}
|
||||
}
|
||||
infoRow("Contact allows", pref.contactPreference.allow.text)
|
||||
if featuresAllowed.timedMessagesAllowed {
|
||||
timedMessagesTTLPicker($featuresAllowed.timedMessagesTTL)
|
||||
} else if pref.contactPreference.allow == .yes || pref.contactPreference.allow == .always {
|
||||
infoRow("Delete after", TimedMessagesPreference.ttlText(pref.contactPreference.ttl))
|
||||
}
|
||||
}
|
||||
header: { featureHeader(.timedMessages, enabled) }
|
||||
footer: { featureFooter(.timedMessages, enabled) }
|
||||
}
|
||||
|
||||
private func featureHeader(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View {
|
||||
HStack {
|
||||
Image(systemName: feature.iconFilled)
|
||||
.foregroundColor(enabled.forUser ? .green : enabled.forContact ? .yellow : .red)
|
||||
Text(feature.text)
|
||||
}
|
||||
}
|
||||
|
||||
private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View {
|
||||
Text(feature.enabledDescription(enabled))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
@@ -92,6 +128,18 @@ struct ContactPreferencesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func timedMessagesTTLPicker(_ selection: Binding<Int?>) -> some View {
|
||||
Picker("Delete after", selection: selection) {
|
||||
let selectedTTL = selection.wrappedValue
|
||||
let ttlValues = TimedMessagesPreference.ttlValues
|
||||
let values = ttlValues + (ttlValues.contains(selectedTTL) ? [] : [selectedTTL])
|
||||
ForEach(values, id: \.self) { ttl in
|
||||
Text(TimedMessagesPreference.ttlText(ttl))
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
}
|
||||
|
||||
struct ContactPreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContactPreferencesView(
|
||||
|
||||
@@ -22,6 +22,7 @@ struct GroupPreferencesView: View {
|
||||
let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members"
|
||||
VStack {
|
||||
List {
|
||||
featureSection(.timedMessages, $preferences.timedMessages.enable)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.enable)
|
||||
featureSection(.directMessages, $preferences.directMessages.enable)
|
||||
featureSection(.voice, $preferences.voice.enable)
|
||||
@@ -35,6 +36,15 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: preferences.timedMessages.enable) { enable in
|
||||
if enable == .on {
|
||||
if preferences.timedMessages.ttl == nil {
|
||||
preferences.timedMessages.ttl = 86400
|
||||
}
|
||||
} else {
|
||||
preferences.timedMessages.ttl = currentPreferences.timedMessages.ttl
|
||||
}
|
||||
}
|
||||
.modifier(BackButton {
|
||||
if currentPreferences == preferences {
|
||||
dismiss()
|
||||
@@ -55,20 +65,25 @@ struct GroupPreferencesView: View {
|
||||
Section {
|
||||
let color: Color = enableFeature.wrappedValue == .on ? .green : .secondary
|
||||
let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon
|
||||
if (groupInfo.canEdit) {
|
||||
let timedOn = feature == .timedMessages && enableFeature.wrappedValue == .on
|
||||
if groupInfo.canEdit {
|
||||
let enable = Binding(
|
||||
get: { enableFeature.wrappedValue == .on },
|
||||
set: { on, _ in enableFeature.wrappedValue = on ? .on : .off }
|
||||
)
|
||||
settingsRow(icon, color: color) {
|
||||
Picker(feature.text, selection: enableFeature) {
|
||||
ForEach(GroupFeatureEnabled.values) { enable in
|
||||
Text(enable.text)
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
Toggle(feature.text, isOn: enable)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if timedOn {
|
||||
timedMessagesTTLPicker($preferences.timedMessages.ttl)
|
||||
}
|
||||
} else {
|
||||
settingsRow(icon, color: color) {
|
||||
infoRow(feature.text, enableFeature.wrappedValue.text)
|
||||
}
|
||||
if timedOn {
|
||||
infoRow("Delete after", TimedMessagesPreference.ttlText(preferences.timedMessages.ttl))
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit))
|
||||
|
||||
@@ -18,6 +18,7 @@ struct PreferencesView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
timedMessagesFeatureSection($preferences.timedMessages.allow)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.allow)
|
||||
featureSection(.voice, $preferences.voice.allow)
|
||||
|
||||
@@ -40,10 +41,27 @@ struct PreferencesView: View {
|
||||
}
|
||||
.frame(height: 36)
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.allowDescription(allowFeature.wrappedValue))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
footer: { featureFooter(feature, allowFeature) }
|
||||
|
||||
}
|
||||
|
||||
private func timedMessagesFeatureSection(_ allowFeature: Binding<FeatureAllowed>) -> some View {
|
||||
Section {
|
||||
let allow = Binding(
|
||||
get: { allowFeature.wrappedValue == .always || allowFeature.wrappedValue == .yes },
|
||||
set: { yes, _ in allowFeature.wrappedValue = yes ? .yes : .no }
|
||||
)
|
||||
settingsRow(ChatFeature.timedMessages.icon) {
|
||||
Toggle(ChatFeature.timedMessages.text, isOn: allow)
|
||||
}
|
||||
}
|
||||
footer: { featureFooter(.timedMessages, allowFeature) }
|
||||
}
|
||||
|
||||
private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
|
||||
Text(ChatFeature.timedMessages.allowDescription(allowFeature.wrappedValue))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func savePreferences() {
|
||||
|
||||
Reference in New Issue
Block a user