diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index e7145711af..4e6d63ffe0 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -9,8 +9,6 @@ import SwiftUI import SimpleXChat -let defaultProfileImageCorner = 22.5 - struct ProfileImage: View { @EnvironmentObject var theme: AppTheme var imageStr: String? = nil @@ -34,26 +32,6 @@ struct ProfileImage: View { } } -private let squareToCircleRatio = 0.935 - -private let radiusFactor = (1 - squareToCircleRatio) / 50 - -@ViewBuilder func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View { - let v = img.resizable() - if radius >= 50 { - v.frame(width: size, height: size).clipShape(Circle()) - } else if radius <= 0 { - let sz = size * squareToCircleRatio - v.frame(width: sz, height: sz).padding((size - sz) / 2) - } else { - let sz = size * (squareToCircleRatio + radius * radiusFactor) - v.frame(width: sz, height: sz) - .clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous)) - .padding((size - sz) / 2) - } -} - - extension Color { func asAnotherColorFromSecondary(_ theme: AppTheme) -> Color { return self diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index ac81c42b2e..ab5da53a9c 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -76,7 +76,7 @@ extension AppSettings { c.androidCallOnLockScreen = AppSettingsLockScreenCalls(rawValue: def.string(forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN)!) c.iosCallKitEnabled = callKitEnabledGroupDefault.get() c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) - c.uiProfileImageCornerRadius = def.float(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) + c.uiProfileImageCornerRadius = def.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) c.uiColorScheme = currentThemeDefault.get() c.uiDarkColorScheme = systemDarkThemeDefault.get() c.uiCurrentThemeIds = currentThemeIdsDefault.get() diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 18cf5fa199..5d667655e3 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -143,7 +143,8 @@ struct AppearanceSettings: View { Text("Themes") .foregroundColor(theme.colors.secondary) } - .onChange(of: profileImageCornerRadius) { _ in + .onChange(of: profileImageCornerRadius) { cornerRadius in + profileImageCornerRadiusGroupDefault.set(cornerRadius) saveThemeToDatabase(nil) } .onChange(of: colorMode) { mode in diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 6b1a619c18..8bd9072f7f 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -70,6 +70,9 @@ struct PrivacySettings: View { Section { settingsRow("network", color: theme.colors.secondary) { Toggle("Send link previews", isOn: $useLinkPreviews) + .onChange(of: useLinkPreviews) { linkPreviews in + privacyLinkPreviewsGroupDefault.set(linkPreviews) + } } settingsRow("message", color: theme.colors.secondary) { Toggle("Show last messages", isOn: $showChatPreviews) @@ -365,6 +368,7 @@ struct SimplexLockView: View { @State private var selfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT) @State private var currentSelfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT) @AppStorage(DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME) private var selfDestructDisplayName = "" + @AppStorage(GROUP_DEFAULT_ALLOW_SHARE_EXTENSION, store: groupDefaults) private var allowShareExtension = false @State private var performLAToggleReset = false @State private var performLAModeReset = false @State private var performLASelfDestructReset = false @@ -436,6 +440,12 @@ struct SimplexLockView: View { } } + if performLA { + Section("Share to SimpleX") { + Toggle("Allow sharing", isOn: $allowShareExtension) + } + } + if performLA && laMode == .passcode { Section(header: Text("Self-destruct passcode").foregroundColor(theme.colors.secondary)) { Toggle(isOn: $selfDestruct) { @@ -460,6 +470,7 @@ struct SimplexLockView: View { } } .onChange(of: performLA) { performLAToggle in + performLAGroupDefault.set(performLAToggle) prefLANoticeShown = true if performLAToggleReset { performLAToggleReset = false diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 0a83db1e5c..fcbef61888 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -18,7 +18,7 @@ let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? let DEFAULT_SHOW_LA_NOTICE = "showLocalAuthenticationNotice" let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown" -let DEFAULT_PERFORM_LA = "performLocalAuthentication" +let DEFAULT_PERFORM_LA = "performLocalAuthentication" // deprecated, moved to app group let DEFAULT_LA_MODE = "localAuthenticationMode" let DEFAULT_LA_LOCK_DELAY = "localAuthenticationLockDelay" let DEFAULT_LA_SELF_DESTRUCT = "localAuthenticationSelfDestruct" @@ -28,7 +28,7 @@ let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay" let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers" let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents" let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead -let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" +let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved to app group let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode" let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft" @@ -165,6 +165,9 @@ let themeOverridesDefault: CodableDefault<[ThemeOverrides]> = CodableDefault(def func setGroupDefaults() { privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES)) + performLAGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)) + privacyLinkPreviewsGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)) + profileImageCornerRadiusGroupDefault.set(UserDefaults.standard.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS)) } public class StringDefault { diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 35d26dea35..8d1443608a 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -58,6 +58,10 @@ class ShareModel: ObservableObject { } func setup(context: NSExtensionContext) { + if performLAGroupDefault.get() && !allowShareExtensionGroupDefault.get() { + errorAlert = ErrorAlert(title: "App is locked!", message: "You can allow sharing in Privacy & Security / SimpleX Lock settings.") + return + } if let item = context.inputItems.first as? NSExtensionItem, let itemProvider = item.attachments?.first { self.itemProvider = itemProvider @@ -433,12 +437,11 @@ extension NSItemProvider { case .url: if let url = try? await loadItem(forTypeIdentifier: type.identifier) as? URL { let content: SharedContent = -// Option to disable previews needs to be taken into account -// if let linkPreview = await getLinkPreview(for: url) { -// .url(preview: linkPreview) -// } else { + if privacyLinkPreviewsGroupDefault.get(), let linkPreview = await getLinkPreview(for: url) { + .url(preview: linkPreview) + } else { .text(string: url.absoluteString) -// } + } return .success(content) } else { return .failure(ErrorAlert("Error preparing message")) } diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index 20e6450b99..395492bf47 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct ShareView: View { @ObservedObject var model: ShareModel @Environment(\.colorScheme) var colorScheme + @AppStorage(GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS, store: groupDefaults) private var radius = defaultProfileImageCorner var body: some View { NavigationView { @@ -21,7 +22,7 @@ struct ShareView: View { HStack { profileImage( chatInfoId: chat.chatInfo.id, - systemFallback: chatIconName(chat.chatInfo), + iconName: chatIconName(chat.chatInfo), size: 30 ) Text(chat.chatInfo.displayName) @@ -159,17 +160,17 @@ struct ShareView: View { .background(Material.ultraThin) } - private func profileImage(chatInfoId: ChatInfo.ID, systemFallback: String, size: Double) -> some View { - Group { - if let uiImage = model.profileImages[chatInfoId] { - Image(uiImage: uiImage).resizable() - } else { - Image(systemName: systemFallback).resizable() - } + @ViewBuilder private func profileImage(chatInfoId: ChatInfo.ID, iconName: String, size: Double) -> some View { + if let uiImage = model.profileImages[chatInfoId] { + clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius) + } else { + Image(systemName: iconName) + .resizable() + .foregroundColor(Color(uiColor: .tertiaryLabel)) + .frame(width: size, height: size) +// add background when adding themes to SE +// .background(Circle().fill(backgroundColor != nil ? backgroundColor! : .clear)) } - .foregroundStyle(Color(.tertiaryLabel)) - .frame(width: size, height: size) - .clipShape(RoundedRectangle(cornerRadius: size * 0.225, style: .continuous)) } private func radioButton(selected: Bool) -> some View { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index df95b5ebb6..796554a839 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2115,7 +2115,7 @@ public struct AppSettings: Codable, Equatable, Hashable { public var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil public var iosCallKitEnabled: Bool? = nil public var iosCallKitCallsInRecents: Bool? = nil - public var uiProfileImageCornerRadius: Float? = nil + public var uiProfileImageCornerRadius: Double? = nil public var uiColorScheme: String? = nil public var uiDarkColorScheme: String? = nil public var uiCurrentThemeIds: [String: String]? = nil diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 6f9ad3b68e..1690c7bc73 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -11,6 +11,8 @@ import SwiftUI public let appSuspendTimeout: Int = 15 // seconds +public let defaultProfileImageCorner: Double = 22.5 + let GROUP_DEFAULT_APP_STATE = "appState" let GROUP_DEFAULT_NSE_STATE = "nseState" let GROUP_DEFAULT_SE_STATE = "seState" @@ -20,11 +22,18 @@ public let GROUP_DEFAULT_CHAT_LAST_BACKGROUND_RUN = "chatLastBackgroundRun" let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode" public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal" // no longer used public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer used +// replaces DEFAULT_PERFORM_LA +let GROUP_DEFAULT_PERFORM_LA = "performLocalAuthentication" +public let GROUP_DEFAULT_ALLOW_SHARE_EXTENSION = "allowShareExtension" +// replaces DEFAULT_PRIVACY_LINK_PREVIEWS +let GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles" public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRelays" +// replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS +public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" @@ -72,10 +81,14 @@ public func registerGroupDefaults() { GROUP_DEFAULT_INCOGNITO: false, GROUP_DEFAULT_STORE_DB_PASSPHRASE: true, GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false, + GROUP_DEFAULT_PERFORM_LA: true, + GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false, + GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: false, GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS: true, + GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner, GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, @@ -190,6 +203,12 @@ public let ntfPreviewModeGroupDefault = EnumDefault( public let incognitoGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INCOGNITO) +public let performLAGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PERFORM_LA) + +public let allowShareExtensionGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_ALLOW_SHARE_EXTENSION) + +public let privacyLinkPreviewsGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS) + // This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES) @@ -197,6 +216,8 @@ public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDef public let privacyAskToApproveRelaysGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS) +public let profileImageCornerRadiusGroupDefault = Default(defaults: groupDefaults, forKey: GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) + public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT) public let networkUseOnionHostsGroupDefault = EnumDefault( diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index c387c84aaa..7a62d863e6 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -428,3 +428,22 @@ public func getLinkPreview(for url: URL) async -> LinkPreview? { getLinkPreview(url: url) { cont.resume(returning: $0) } } } + +private let squareToCircleRatio = 0.935 + +private let radiusFactor = (1 - squareToCircleRatio) / 50 + +@ViewBuilder public func clipProfileImage(_ img: Image, size: CGFloat, radius: Double) -> some View { + let v = img.resizable() + if radius >= 50 { + v.frame(width: size, height: size).clipShape(Circle()) + } else if radius <= 0 { + let sz = size * squareToCircleRatio + v.frame(width: sz, height: sz).padding((size - sz) / 2) + } else { + let sz = size * (squareToCircleRatio + radius * radiusFactor) + v.frame(width: sz, height: sz) + .clipShape(RoundedRectangle(cornerRadius: sz * radius / 100, style: .continuous)) + .padding((size - sz) / 2) + } +}