537 lines
23 KiB
Swift
537 lines
23 KiB
Swift
|
//
|
||
|
// GeneralSettings.swift
|
||
|
// Monal
|
||
|
//
|
||
|
// Created by Vaidik Dubey on 22/03/24.
|
||
|
// Copyright © 2024 monal-im.org. All rights reserved.
|
||
|
//
|
||
|
|
||
|
import ViewExtractor
|
||
|
struct SettingsToggle<T>: View where T: View {
|
||
|
let value: Binding<Bool>
|
||
|
let contents: T
|
||
|
|
||
|
init(isOn value: Binding<Bool>, @ViewBuilder contents: @escaping () -> T) {
|
||
|
self.value = value
|
||
|
self.contents = contents()
|
||
|
}
|
||
|
|
||
|
var body:some View {
|
||
|
VStack(alignment: .leading, spacing: 0) {
|
||
|
Extract(contents) { views in
|
||
|
if views.count == 0 {
|
||
|
Text("")
|
||
|
} else {
|
||
|
Toggle(isOn: value) {
|
||
|
views[0]
|
||
|
.font(.body)
|
||
|
}
|
||
|
if views.count > 1 {
|
||
|
Group {
|
||
|
ForEach(views[1...]) { view in
|
||
|
view
|
||
|
.foregroundColor(Color(UIColor.secondaryLabel))
|
||
|
.font(.footnote)
|
||
|
}
|
||
|
}.fixedSize(horizontal: false, vertical: true).frame(maxWidth: .infinity, alignment: .leading)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getNotificationPrivacyOption(_ option: NotificationPrivacySettingOption) -> String {
|
||
|
switch option{
|
||
|
case .DisplayNameAndMessage:
|
||
|
return NSLocalizedString("Display Name And Message", comment: "")
|
||
|
case .DisplayOnlyName:
|
||
|
return NSLocalizedString("Display Only Name", comment: "")
|
||
|
case .DisplayOnlyPlaceholder:
|
||
|
return NSLocalizedString("Display Only Placeholder", comment: "")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class GeneralSettingsDefaultsDB: ObservableObject {
|
||
|
@defaultsDB("NotificationPrivacySetting")
|
||
|
var notificationPrivacySetting: Int
|
||
|
|
||
|
@defaultsDB("OMEMODefaultOn")
|
||
|
var omemoDefaultOn:Bool
|
||
|
|
||
|
@defaultsDB("AutodeleteInterval")
|
||
|
var AutodeleteInterval: Int
|
||
|
|
||
|
@defaultsDB("SendLastUserInteraction")
|
||
|
var sendLastUserInteraction: Bool
|
||
|
|
||
|
@defaultsDB("SendLastChatState")
|
||
|
var sendLastChatState: Bool
|
||
|
|
||
|
@defaultsDB("SendReceivedMarkers")
|
||
|
var sendReceivedMarkers: Bool
|
||
|
|
||
|
@defaultsDB("SendDisplayedMarkers")
|
||
|
var sendDisplayedMarkers: Bool
|
||
|
|
||
|
@defaultsDB("ShowGeoLocation")
|
||
|
var showGeoLocation: Bool
|
||
|
|
||
|
@defaultsDB("ShowURLPreview")
|
||
|
var showURLPreview: Bool
|
||
|
|
||
|
@defaultsDB("useInlineSafari")
|
||
|
var useInlineSafari: Bool
|
||
|
|
||
|
@defaultsDB("webrtcAllowP2P")
|
||
|
var webrtcAllowP2P: Bool
|
||
|
|
||
|
@defaultsDB("webrtcUseFallbackTurn")
|
||
|
var webrtcUseFallbackTurn: Bool
|
||
|
|
||
|
@defaultsDB("allowVersionIQ")
|
||
|
var allowVersionIQ: Bool
|
||
|
|
||
|
@defaultsDB("allowNonRosterContacts")
|
||
|
var allowNonRosterContacts: Bool
|
||
|
|
||
|
@defaultsDB("allowCallsFromNonRosterContacts")
|
||
|
var allowCallsFromNonRosterContacts: Bool
|
||
|
|
||
|
@defaultsDB("AutodownloadFiletransfers")
|
||
|
var autodownloadFiletransfers : Bool
|
||
|
|
||
|
@defaultsDB("AutodownloadFiletransfersWifiMaxSize")
|
||
|
var autodownloadFiletransfersWifiMaxSize : UInt
|
||
|
|
||
|
@defaultsDB("AutodownloadFiletransfersMobileMaxSize")
|
||
|
var autodownloadFiletransfersMobileMaxSize : UInt
|
||
|
|
||
|
@defaultsDB("ImageUploadQuality")
|
||
|
var imageUploadQuality : Float
|
||
|
|
||
|
@defaultsDB("showKeyboardOnChatOpen")
|
||
|
var showKeyboardOnChatOpen: Bool
|
||
|
|
||
|
@defaultsDB("useDnssecForAllConnections")
|
||
|
var useDnssecForAllConnections: Bool
|
||
|
|
||
|
@defaultsDB("uploadImagesOriginal")
|
||
|
var uploadImagesOriginal: Bool
|
||
|
|
||
|
@defaultsDB("hardlinkFiletransfersIntoDocuments")
|
||
|
var hardlinkFiletransfersIntoDocuments: Bool
|
||
|
|
||
|
@defaultsDB("showAdvancedUI")
|
||
|
var showAdvancedUI: Bool
|
||
|
}
|
||
|
|
||
|
|
||
|
struct GeneralSettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
Section(header:Text("General Settings")) {
|
||
|
NavigationLink(destination: LazyClosureView(UserInterfaceSettings())) {
|
||
|
HStack{
|
||
|
Image(systemName: "hand.tap.fill")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
Text("User Interface")
|
||
|
}
|
||
|
}
|
||
|
NavigationLink(destination: LazyClosureView(SecuritySettings())) {
|
||
|
HStack{
|
||
|
Image(systemName: "shield.checkerboard")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
Text("Security")
|
||
|
}
|
||
|
}
|
||
|
NavigationLink(destination: LazyClosureView(PrivacySettings())) {
|
||
|
HStack{
|
||
|
Image(systemName: "eye")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
Text("Privacy")
|
||
|
}
|
||
|
}
|
||
|
NavigationLink(destination: LazyClosureView(NotificationSettings())) {
|
||
|
HStack{
|
||
|
Image(systemName: "text.bubble")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
Text("Notifications")
|
||
|
}
|
||
|
}
|
||
|
NavigationLink(destination: LazyClosureView(AttachmentSettings())) {
|
||
|
HStack {
|
||
|
Image(systemName: "paperclip")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
Text("Attachments")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Button(action: {
|
||
|
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
|
||
|
}, label: {
|
||
|
HStack {
|
||
|
Image(systemName: "gear")
|
||
|
.resizable()
|
||
|
.aspectRatio(contentMode: .fit)
|
||
|
.frame(width: 20, height: 20)
|
||
|
#if targetEnvironment(macCatalyst)
|
||
|
Text("Open macOS settings")
|
||
|
#else
|
||
|
Text("Open iOS settings")
|
||
|
#endif
|
||
|
}.foregroundColor(Color(UIColor.label))
|
||
|
})
|
||
|
.buttonStyle(.borderless)
|
||
|
}
|
||
|
}
|
||
|
.navigationBarTitle(Text("General Settings"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct UserInterfaceSettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
Section(header: Text("Previews")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.showGeoLocation) {
|
||
|
Text("Show inline geo location")
|
||
|
Text("Received geo locations are shared with Apple's Maps App.")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.showURLPreview) {
|
||
|
Text("Show URL previews")
|
||
|
Text("The operator of the webserver providing that URL may see your IP address.")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.useInlineSafari) {
|
||
|
Text("Open URLs inline in Safari")
|
||
|
Text("When disabled, URLs will opened in your default browser (that might not be Safari).")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Section(header: Text("Input")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.showKeyboardOnChatOpen) {
|
||
|
Text("Autofocus text input on chat open")
|
||
|
Text("Will focus the textfield on macOS or iOS with hardware keyboard attached, will open the software keyboard otherwise.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Section(header: Text("Appearance")) {
|
||
|
VStack(alignment: .leading, spacing: 0) {
|
||
|
NavigationLink(destination: LazyClosureView(BackgroundSettings(contact:nil))) {
|
||
|
Text("Chat background image").font(.body)
|
||
|
}
|
||
|
Text("Configure the background image displayed in open chats.")
|
||
|
.foregroundColor(Color(UIColor.secondaryLabel))
|
||
|
.font(.footnote)
|
||
|
.fixedSize(horizontal: false, vertical: true)
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.showAdvancedUI) {
|
||
|
Text("Show advanced options in UI")
|
||
|
Text("Show power-user options in settings and other parts of the user interface.")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
.navigationBarTitle(Text("User Interface"), displayMode: .inline)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct SecuritySettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
@State var autodeleteInterval: Int = 0
|
||
|
@State var autodeleteIntervalSelection: Int = 0
|
||
|
var autodeleteOptions = [
|
||
|
0: NSLocalizedString("Off", comment:"Message autdelete time"),
|
||
|
30: NSLocalizedString("30 seconds", comment:"Message autdelete time"),
|
||
|
60: NSLocalizedString("1 minute", comment:"Message autdelete time"),
|
||
|
300: NSLocalizedString("5 minutes", comment:"Message autdelete time"),
|
||
|
900: NSLocalizedString("15 minutes", comment:"Message autdelete time"),
|
||
|
1800: NSLocalizedString("30 minutes", comment:"Message autdelete time"),
|
||
|
3600: NSLocalizedString("1 hour", comment:"Message autdelete time"),
|
||
|
43200: NSLocalizedString("12 hours", comment:"Message autdelete time"),
|
||
|
86400: NSLocalizedString("1 day", comment:"Message autdelete time"),
|
||
|
259200: NSLocalizedString("3 days", comment:"Message autdelete time"),
|
||
|
604800: NSLocalizedString("1 week", comment:"Message autdelete time"),
|
||
|
2419200: NSLocalizedString("4 weeks", comment:"Message autdelete time"),
|
||
|
5184000: NSLocalizedString("2 month", comment:"Message autdelete time"), //based on 30 days per month
|
||
|
7776000: NSLocalizedString("3 month", comment:"Message autdelete time"), //based on 30 days per month
|
||
|
]
|
||
|
|
||
|
init() {
|
||
|
_autodeleteInterval = State(wrappedValue:generalSettingsDefaultsDB.AutodeleteInterval)
|
||
|
_autodeleteIntervalSelection = State(wrappedValue:generalSettingsDefaultsDB.AutodeleteInterval)
|
||
|
autodeleteOptions[-1] = NSLocalizedString("Custom", comment:"Message autdelete time")
|
||
|
//check if we have a custom value and change picker value accordingly
|
||
|
if autodeleteOptions[autodeleteInterval] == nil {
|
||
|
_autodeleteIntervalSelection = State(wrappedValue:-1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
Section(header: Text("Encryption")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.omemoDefaultOn) {
|
||
|
Text("Enable encryption by default for new chats")
|
||
|
Text("Every new contact will have encryption enabled, but already known contacts will preserve their encryption settings.")
|
||
|
}
|
||
|
|
||
|
if generalSettingsDefaultsDB.showAdvancedUI {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.useDnssecForAllConnections) {
|
||
|
Text("Use DNSSEC validation for all connections")
|
||
|
Text(
|
||
|
"""
|
||
|
Use DNSSEC to validate all DNS query responses before connecting to the IP address designated \
|
||
|
in the DNS response.\n\
|
||
|
While being more secure, this can lead to connection problems in certain networks \
|
||
|
like hotel wifi, ugly mobile carriers etc.
|
||
|
"""
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.webrtcAllowP2P) {
|
||
|
Text("Calls: Allow P2P sessions")
|
||
|
Text("Allow your device to establish a direct network connection to the remote party. This might leak your IP address to the caller/callee.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Section(header: Text("On this device")) {
|
||
|
VStack(alignment: .leading, spacing: 0) {
|
||
|
Picker(selection: $autodeleteIntervalSelection, label: Text("Autodelete all messages older than")) {
|
||
|
ForEach(autodeleteOptions.keys.sorted(), id: \.self) { key in
|
||
|
Text(autodeleteOptions[key]!).tag(key)
|
||
|
}
|
||
|
}
|
||
|
//custom interval requested explicitly
|
||
|
if autodeleteIntervalSelection == -1 {
|
||
|
HStack {
|
||
|
Text("Custom Time: ")
|
||
|
Stepper(String(format:NSLocalizedString("%@ hours", comment:""), String(describing:(max(1, autodeleteInterval / 3600)).formatted())), value: Binding<Int>(
|
||
|
get: { max(1, autodeleteInterval / 3600) /*clamp to 1 ... .max*/ },
|
||
|
set: { autodeleteInterval = $0 * 3600 }
|
||
|
), in: 1 ... .max)
|
||
|
}
|
||
|
}
|
||
|
Text("Be warned: Message will only be deleted on incoming pushes or if you open the app! This is especially true for shorter time intervals!").foregroundColor(Color(UIColor.secondaryLabel)).font(.footnote)
|
||
|
Text("Also beware: You won't be able to load older history from your server, Monal will immediately delete it after fetching it!").foregroundColor(Color(UIColor.secondaryLabel)).font(.footnote)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
.navigationBarTitle(Text("Security"), displayMode: .inline)
|
||
|
//save only when closing view to not delete messages while the user is selecting a (custom) value
|
||
|
.onDisappear {
|
||
|
if autodeleteIntervalSelection == -1 {
|
||
|
//make sure our custom value is stored clamped, too
|
||
|
autodeleteInterval = max(1, autodeleteInterval / 3600)
|
||
|
} else {
|
||
|
//copy over picker value if not set to custom
|
||
|
autodeleteInterval = autodeleteIntervalSelection
|
||
|
}
|
||
|
generalSettingsDefaultsDB.AutodeleteInterval = autodeleteInterval
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct PrivacySettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
PrivacySettingsSubview(onboardingPart:-1)
|
||
|
}
|
||
|
.navigationBarTitle(Text("Privacy"), displayMode: .inline)
|
||
|
}
|
||
|
}
|
||
|
struct PrivacySettingsSubview: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
var onboardingPart: Int
|
||
|
|
||
|
var body: some View {
|
||
|
if onboardingPart == -1 || onboardingPart == 0 {
|
||
|
Section(header: Text("Activity indications")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.sendReceivedMarkers) {
|
||
|
Text("Send message receipts")
|
||
|
Text("Let your contacts know if you received a message.")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.sendDisplayedMarkers) {
|
||
|
Text("Send read receipts")
|
||
|
Text("Let your contacts know if you read a message.")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.sendLastChatState) {
|
||
|
Text("Send typing notifications")
|
||
|
Text("Let your contacts know if you are typing a message.")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.sendLastUserInteraction) {
|
||
|
Text("Send last interaction time")
|
||
|
Text("Let your contacts know when you last opened the app.")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if onboardingPart == -1 || onboardingPart == 1 {
|
||
|
Section(header: Text("Interactions")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.allowNonRosterContacts) {
|
||
|
Text("Accept incoming messages from strangers")
|
||
|
Text("Allow contacts not in your contact list to contact you.")
|
||
|
}
|
||
|
SettingsToggle(isOn: Binding<Bool>(
|
||
|
get: { generalSettingsDefaultsDB.allowCallsFromNonRosterContacts && generalSettingsDefaultsDB.allowNonRosterContacts },
|
||
|
set: { generalSettingsDefaultsDB.allowCallsFromNonRosterContacts = $0 }
|
||
|
)) {
|
||
|
Text("Accept incoming calls from strangers")
|
||
|
Text("Allow contacts not in your contact list to call you.")
|
||
|
}.disabled(!generalSettingsDefaultsDB.allowNonRosterContacts)
|
||
|
}
|
||
|
}
|
||
|
if onboardingPart == -1 || onboardingPart == 2 {
|
||
|
Section(header: Text("Misc")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.allowVersionIQ) {
|
||
|
Text("Publish version")
|
||
|
#if IS_QUICKSY
|
||
|
Text("Allow contacts in your contact list to query your Quicksy and iOS versions.")
|
||
|
#else
|
||
|
Text("Allow contacts in your contact list to query your Monal and iOS versions.")
|
||
|
#endif
|
||
|
}
|
||
|
//the quicksy.im server always has a proper TURN server, no need for this setting there
|
||
|
#if !IS_QUICKSY
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.webrtcUseFallbackTurn) {
|
||
|
Text("Calls: Allow TURN fallback to Monal-Servers")
|
||
|
Text("This will make calls possible even if your XMPP server does not provide a TURN server, but leaks your IP to Monal's servers if your XMPP server does not provide a TURN server.")
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct NotificationSettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
@State private var pushPermissionEnabled = false
|
||
|
|
||
|
private var pushNotEnabled: Bool {
|
||
|
let xmppAccountInfo = MLXMPPManager.sharedInstance().connectedXMPP as! [xmpp]
|
||
|
var pushNotEnabled = false
|
||
|
for account in xmppAccountInfo {
|
||
|
pushNotEnabled = pushNotEnabled || !account.connectionProperties.pushEnabled
|
||
|
}
|
||
|
return pushNotEnabled
|
||
|
}
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
Section(header: Text("Settings")) {
|
||
|
Picker(selection: $generalSettingsDefaultsDB.notificationPrivacySetting, label: Text("Privacy")) {
|
||
|
ForEach(NotificationPrivacySettingOption.allCases, id: \.self) { option in
|
||
|
Text(getNotificationPrivacyOption(option)).tag(option.rawValue)
|
||
|
}
|
||
|
}
|
||
|
.frame(height: 56, alignment: .trailing)
|
||
|
}
|
||
|
|
||
|
Section(header: Text("Debugging")) {
|
||
|
NavigationLink(destination: LazyClosureView(NotificationDebugging())) {
|
||
|
buildNotificationStateLabel(Text("Debug Notification Problems"), isWorking: !self.pushNotEnabled && self.pushPermissionEnabled)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
.onAppear {
|
||
|
UNUserNotificationCenter.current().getNotificationSettings { (settings) -> Void in
|
||
|
self.pushPermissionEnabled = (settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional);
|
||
|
}
|
||
|
}
|
||
|
.navigationBarTitle(Text("Notifications"), displayMode: .inline)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct AttachmentSettings: View {
|
||
|
@ObservedObject var generalSettingsDefaultsDB = GeneralSettingsDefaultsDB()
|
||
|
|
||
|
var body: some View {
|
||
|
Form {
|
||
|
Section(header: Text("General File Transfer Settings")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.autodownloadFiletransfers) {
|
||
|
Text("Auto-Download Media and Files")
|
||
|
}
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.hardlinkFiletransfersIntoDocuments) {
|
||
|
Text("Make transfered Media and Files accessible in Files App")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Section(header: Text("Download Settings")) {
|
||
|
Text("Adjust the maximum file size for auto-downloads over WiFi")
|
||
|
.foregroundColor(.secondary)
|
||
|
.font(.footnote)
|
||
|
Slider(
|
||
|
value: $generalSettingsDefaultsDB.autodownloadFiletransfersWifiMaxSize.bytecount(mappedTo: 1024*1024),
|
||
|
in: 1.0...100.0,
|
||
|
step: 1.0,
|
||
|
minimumValueLabel: Text("1 MiB"),
|
||
|
maximumValueLabel: Text("100 MiB"),
|
||
|
label: {
|
||
|
Text("Load over wifi")
|
||
|
}
|
||
|
)
|
||
|
Text("Load over WiFi up to: \(String(describing:UInt(generalSettingsDefaultsDB.autodownloadFiletransfersWifiMaxSize/(1024*1024)))) MiB")
|
||
|
}
|
||
|
|
||
|
Section {
|
||
|
Text("Adjust the maximum file size for auto-downloads over cellular network")
|
||
|
.foregroundColor(.secondary)
|
||
|
.font(.footnote)
|
||
|
Slider(
|
||
|
value: $generalSettingsDefaultsDB.autodownloadFiletransfersMobileMaxSize.bytecount(mappedTo: 1024*1024),
|
||
|
in: 0.0...100.0,
|
||
|
step: 1.0,
|
||
|
minimumValueLabel: Text("1 MiB"),
|
||
|
maximumValueLabel: Text("100 MiB"),
|
||
|
label: {
|
||
|
Text("Load over Cellular")
|
||
|
}
|
||
|
)
|
||
|
Text("Load over cellular up to: \(String(describing:UInt(generalSettingsDefaultsDB.autodownloadFiletransfersMobileMaxSize/(1024*1024)))) MiB")
|
||
|
}
|
||
|
|
||
|
Section(header: Text("Upload Settings")) {
|
||
|
SettingsToggle(isOn: $generalSettingsDefaultsDB.uploadImagesOriginal) {
|
||
|
Text("Upload Original Images")
|
||
|
}
|
||
|
if !generalSettingsDefaultsDB.uploadImagesOriginal {
|
||
|
Text("Adjust the quality of images uploaded")
|
||
|
.foregroundColor(.secondary)
|
||
|
.font(.footnote)
|
||
|
Slider(
|
||
|
value: $generalSettingsDefaultsDB.imageUploadQuality,
|
||
|
in: 0.33...1.0,
|
||
|
step: 0.01,
|
||
|
minimumValueLabel: Text("33%"),
|
||
|
maximumValueLabel: Text("100%"),
|
||
|
label: {
|
||
|
Text("Upload Settings")
|
||
|
}
|
||
|
)
|
||
|
Text("Image Upload JPEG-Quality: \(String(format: "%.0f%%", generalSettingsDefaultsDB.imageUploadQuality*100))")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
struct ContentView_Previews: PreviewProvider {
|
||
|
static var previews: some View {
|
||
|
GeneralSettings()
|
||
|
}
|
||
|
}
|