mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-31 03:16:05 +00:00
ios: chat preferences, UI and types (#1360)
This commit is contained in:
committed by
GitHub
parent
07e8c1d76e
commit
faceeb6fce
@@ -99,6 +99,16 @@ struct ChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Preferences") {
|
||||
NavigationLink {
|
||||
ContactPreferencesView()
|
||||
.navigationBarTitle("Contact preferences")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Text("Contact preferences")
|
||||
}
|
||||
}
|
||||
|
||||
Section("Servers") {
|
||||
networkStatusRow()
|
||||
.onTapGesture {
|
||||
|
||||
82
apps/ios/Shared/Views/Chat/ContactPreferencesView.swift
Normal file
82
apps/ios/Shared/Views/Chat/ContactPreferencesView.swift
Normal file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// ContactPreferencesView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 13/11/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ContactPreferencesView: View {
|
||||
@State var allowFullDeletion = ContactFeatureAllowed.yes
|
||||
@State var allowVoice = ContactFeatureAllowed.yes
|
||||
@State var prefs = ContactUserPreferences(
|
||||
fullDelete: ContactUserPreference(
|
||||
enabled: FeatureEnabled(forUser: true, forContact: true),
|
||||
userPreference: .user(preference: Preference(allow: .yes)),
|
||||
contactPreference: Preference(allow: .no)
|
||||
),
|
||||
voice: ContactUserPreference(
|
||||
enabled: FeatureEnabled(forUser: true, forContact: true),
|
||||
userPreference: .user(preference: Preference(allow: .yes)),
|
||||
contactPreference: Preference(allow: .no)
|
||||
)
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
featureSection(.fullDelete, .yes, prefs.fullDelete, $allowFullDeletion)
|
||||
featureSection(.voice, .yes, prefs.voice, $allowVoice)
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Text("Reset")
|
||||
Spacer()
|
||||
Text("Save")
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.disabled(true)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func featureSection(_ feature: Feature, _ userDefault: FeatureAllowed, _ pref: ContactUserPreference, _ allowFeature: Binding<ContactFeatureAllowed>) -> some View {
|
||||
let enabled = FeatureEnabled.enabled(
|
||||
user: Preference(allow: allowFeature.wrappedValue.allowed),
|
||||
contact: pref.contactPreference
|
||||
)
|
||||
return Section {
|
||||
Picker("You allow", selection: allowFeature) {
|
||||
ForEach(ContactFeatureAllowed.values(userDefault)) { allow in
|
||||
Text(allow.text)
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
HStack {
|
||||
Text("Contact allows")
|
||||
Spacer()
|
||||
Text(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactPreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContactPreferencesView()
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ struct DatabaseView: View {
|
||||
|
||||
Section {
|
||||
Picker("Delete messages after", selection: $chatItemTTL) {
|
||||
ForEach([ChatItemTTL.none, ChatItemTTL.month, ChatItemTTL.week, ChatItemTTL.day]) { ttl in
|
||||
ForEach(ChatItemTTL.values) { ttl in
|
||||
Text(ttl.deleteAfterText).tag(ttl)
|
||||
}
|
||||
if case .seconds = chatItemTTL {
|
||||
|
||||
57
apps/ios/Shared/Views/UserSettings/PreferencesView.swift
Normal file
57
apps/ios/Shared/Views/UserSettings/PreferencesView.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// PreferencesView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 13/11/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct PreferencesView: View {
|
||||
@State var allowFullDeletion = FeatureAllowed.yes
|
||||
@State var allowVoice = FeatureAllowed.yes
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
featureSection(.fullDelete, $allowFullDeletion)
|
||||
featureSection(.voice, $allowVoice)
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Text("Reset")
|
||||
Spacer()
|
||||
Text("Save")
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.disabled(true)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func featureSection(_ feature: Feature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
|
||||
Section {
|
||||
settingsRow(feature.icon) {
|
||||
Picker(feature.text, selection: allowFeature) {
|
||||
ForEach(FeatureAllowed.values) { allow in
|
||||
Text(allow.text)
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.allowDescription(allowFeature.wrappedValue))
|
||||
.frame(height: 36, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PreferencesView()
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,13 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
Section("Settings") {
|
||||
NavigationLink {
|
||||
PreferencesView()
|
||||
.navigationTitle("Your preferences")
|
||||
} label: {
|
||||
settingsRow("list.bullet") { Text("Chat preferences") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
NotificationsView()
|
||||
.navigationTitle("Notifications")
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C3279559F40002BEB4 /* SimpleXApp.swift */; };
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; };
|
||||
5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79929211BB900072E13 /* PreferencesView.swift */; };
|
||||
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79B292131E900072E13 /* ContactPreferencesView.swift */; };
|
||||
5CB0BA882826CB3A00B3292C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */; };
|
||||
5CB0BA8B2826CB3A00B3292C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA892826CB3A00B3292C /* Localizable.strings */; };
|
||||
5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8D2827126500B3292C /* OnboardingView.swift */; };
|
||||
@@ -267,6 +269,8 @@
|
||||
5CA059D7279559F40002BEB4 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5CA059DB279559F40002BEB4 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = "<group>"; };
|
||||
5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
5CADE79929211BB900072E13 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CADE79B292131E900072E13 /* ContactPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CB0BA872826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
5CB0BA8A2826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CB0BA8D2827126500B3292C /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
@@ -428,6 +432,7 @@
|
||||
5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */,
|
||||
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */,
|
||||
5CE4407127ADB1D0007B033A /* Emoji.swift */,
|
||||
5CADE79B292131E900072E13 /* ContactPreferencesView.swift */,
|
||||
);
|
||||
path = Chat;
|
||||
sourceTree = "<group>";
|
||||
@@ -571,6 +576,7 @@
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */,
|
||||
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
|
||||
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
|
||||
5CADE79929211BB900072E13 /* PreferencesView.swift */,
|
||||
5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */,
|
||||
5C05DF522840AA1D00C683F9 /* CallSettings.swift */,
|
||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */,
|
||||
@@ -956,6 +962,7 @@
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
|
||||
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
|
||||
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */,
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
|
||||
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
|
||||
@@ -978,6 +985,7 @@
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */,
|
||||
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
|
||||
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */,
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -117,6 +117,181 @@ extension NamedChat {
|
||||
|
||||
public typealias ChatId = String
|
||||
|
||||
public struct FullPreferences: Decodable {
|
||||
public var fullDelete: Preference
|
||||
public var voice: Preference
|
||||
|
||||
public init(fullDelete: Preference, voice: Preference) {
|
||||
self.fullDelete = fullDelete
|
||||
self.voice = voice
|
||||
}
|
||||
}
|
||||
|
||||
public struct Preference: Codable {
|
||||
public var allow: FeatureAllowed
|
||||
|
||||
public init(allow: FeatureAllowed) {
|
||||
self.allow = allow
|
||||
}
|
||||
}
|
||||
|
||||
public struct ContactUserPreferences: Decodable {
|
||||
public var fullDelete: ContactUserPreference
|
||||
public var voice: ContactUserPreference
|
||||
|
||||
public init(fullDelete: ContactUserPreference, voice: ContactUserPreference) {
|
||||
self.fullDelete = fullDelete
|
||||
self.voice = voice
|
||||
}
|
||||
}
|
||||
|
||||
public struct ContactUserPreference: Decodable {
|
||||
public var enabled: FeatureEnabled
|
||||
public var userPreference: ContactUserPref
|
||||
public var contactPreference: Preference
|
||||
|
||||
public init(enabled: FeatureEnabled, userPreference: ContactUserPref, contactPreference: Preference) {
|
||||
self.enabled = enabled
|
||||
self.userPreference = userPreference
|
||||
self.contactPreference = contactPreference
|
||||
}
|
||||
}
|
||||
|
||||
public struct FeatureEnabled: Decodable {
|
||||
public var forUser: Bool
|
||||
public var forContact: Bool
|
||||
|
||||
public init(forUser: Bool, forContact: Bool) {
|
||||
self.forUser = forUser
|
||||
self.forContact = forContact
|
||||
}
|
||||
|
||||
public static func enabled(user: Preference, contact: Preference) -> FeatureEnabled {
|
||||
switch (user.allow, contact.allow) {
|
||||
case (.always, .no): return FeatureEnabled(forUser: false, forContact: true)
|
||||
case (.no, .always): return FeatureEnabled(forUser: true, forContact: false)
|
||||
case (_, .no): return FeatureEnabled(forUser: false, forContact: false)
|
||||
case (.no, _): return FeatureEnabled(forUser: false, forContact: false)
|
||||
default: return FeatureEnabled(forUser: true, forContact: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ContactUserPref: Decodable {
|
||||
case contact(preference: Preference) // contact override is set
|
||||
case user(preference: Preference) // global user default is used
|
||||
}
|
||||
|
||||
public enum Feature {
|
||||
case fullDelete
|
||||
case voice
|
||||
|
||||
public var values: [Feature] { [.fullDelete, .voice] }
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
public var text: LocalizedStringKey {
|
||||
switch self {
|
||||
case .fullDelete: return "Full deletion"
|
||||
case .voice: return "Voice messages"
|
||||
}
|
||||
}
|
||||
|
||||
public var icon: String {
|
||||
switch self {
|
||||
case .fullDelete: return "trash.slash"
|
||||
case .voice: return "speaker.wave.2"
|
||||
}
|
||||
}
|
||||
|
||||
public func allowDescription(_ allowed: FeatureAllowed) -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .fullDelete:
|
||||
switch allowed {
|
||||
case .always: return "Allow your contacts to irreversibly delete sent messages."
|
||||
case .yes: return "Allow irreversible message deletion only if your contact allows it to you."
|
||||
case .no: return "Contacts can mark messages for deletion; you will be able to view them."
|
||||
}
|
||||
case .voice:
|
||||
switch allowed {
|
||||
case .always: return "Allow your contacts to send voice messages."
|
||||
case .yes: return "Allow voice messages only if your contact allows them."
|
||||
case .no: return "Prohibit sending voice messages."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func enabledDescription(_ enabled: FeatureEnabled) -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .fullDelete:
|
||||
return enabled.forUser && enabled.forContact
|
||||
? "Both you and your contact can irreversibly delete sent messages."
|
||||
: enabled.forUser
|
||||
? "Only you can irreversibly delete messages (your contact can mark them for deletion)."
|
||||
: enabled.forContact
|
||||
? "Only your contact can irreversibly delete messages (you can mark them for deletion)."
|
||||
: "Irreversible message deletion is prohibited in this chat."
|
||||
case .voice:
|
||||
return enabled.forUser && enabled.forContact
|
||||
? "Both you and your contact can send voice messages."
|
||||
: enabled.forUser
|
||||
? "Only you can send voice messages."
|
||||
: enabled.forContact
|
||||
? "Only your contact can send voice messages."
|
||||
: "Voice messages are prohibited in this chat."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ContactFeatureAllowed: Identifiable, Hashable {
|
||||
case userDefault(FeatureAllowed)
|
||||
case always
|
||||
case yes
|
||||
case no
|
||||
|
||||
public static func values(_ def: FeatureAllowed) -> [ContactFeatureAllowed] {
|
||||
[.userDefault(def) , .always, .yes, .no]
|
||||
}
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
public var allowed: FeatureAllowed {
|
||||
switch self {
|
||||
case let .userDefault(def): return def
|
||||
case .always: return .always
|
||||
case .yes: return .yes
|
||||
case .no: return .no
|
||||
}
|
||||
}
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case let .userDefault(def): return String.localizedStringWithFormat(NSLocalizedString("default (%@)", comment: "pref value"), def.text)
|
||||
case .always: return NSLocalizedString("always", comment: "pref value")
|
||||
case .yes: return NSLocalizedString("yes", comment: "pref value")
|
||||
case .no: return NSLocalizedString("no", comment: "pref value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum FeatureAllowed: String, Codable, Identifiable {
|
||||
case always
|
||||
case yes
|
||||
case no
|
||||
|
||||
public static var values: [FeatureAllowed] { [.always, .yes, .no] }
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .always: return NSLocalizedString("always", comment: "pref value")
|
||||
case .yes: return NSLocalizedString("yes", comment: "pref value")
|
||||
case .no: return NSLocalizedString("no", comment: "pref value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
case direct(contact: Contact)
|
||||
case group(groupInfo: GroupInfo)
|
||||
@@ -1561,6 +1736,8 @@ public enum ChatItemTTL: Hashable, Identifiable, Comparable {
|
||||
case seconds(_ seconds: Int64)
|
||||
case none
|
||||
|
||||
public static var values: [ChatItemTTL] { [.none, .month, .week, .day] }
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
public init(_ seconds: Int64?) {
|
||||
|
||||
Reference in New Issue
Block a user