mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
mobile: simplex links in UI, core: trusted uri for simplex links (#1410)
This commit is contained in:
committed by
GitHub
parent
a7345ee4d9
commit
4485d46307
@@ -1491,12 +1491,21 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
|
||||
@Serializable
|
||||
class FormattedText(val text: String, val format: Format? = null) {
|
||||
// TODO make it dependent on simplexLinkMode preference
|
||||
val link: String? = when (format) {
|
||||
is Format.Uri -> text
|
||||
is Format.SimplexLink -> format.simplexUri
|
||||
is Format.Email -> "mailto:$text"
|
||||
is Format.Phone -> "tel:$text"
|
||||
else -> null
|
||||
}
|
||||
|
||||
// TODO make it dependent on simplexLinkMode preference
|
||||
val viewText: String =
|
||||
if (format is Format.SimplexLink) simplexLinkText(format.linkType, format.smpHosts) else text
|
||||
|
||||
fun simplexLinkText(linkType: SimplexLinkType, smpHosts: List<String>): String =
|
||||
"${linkType.description} (${String.format(generalGetString(R.string.simplex_link_connection), smpHosts.firstOrNull() ?: "?")})"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -1508,6 +1517,8 @@ sealed class Format {
|
||||
@Serializable @SerialName("secret") class Secret: Format()
|
||||
@Serializable @SerialName("colored") class Colored(val color: FormatColor): Format()
|
||||
@Serializable @SerialName("uri") class Uri: Format()
|
||||
// TODO trustedUri: Boolean
|
||||
@Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List<String>): Format()
|
||||
@Serializable @SerialName("email") class Email: Format()
|
||||
@Serializable @SerialName("phone") class Phone: Format()
|
||||
|
||||
@@ -1519,6 +1530,7 @@ sealed class Format {
|
||||
is Secret -> SpanStyle(color = Color.Transparent, background = SecretColor)
|
||||
is Colored -> SpanStyle(color = this.color.uiColor)
|
||||
is Uri -> linkStyle
|
||||
is SimplexLink -> linkStyle
|
||||
is Email -> linkStyle
|
||||
is Phone -> linkStyle
|
||||
}
|
||||
@@ -1528,6 +1540,19 @@ sealed class Format {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class SimplexLinkType(val linkType: String) {
|
||||
contact("contact"),
|
||||
invitation("invitation"),
|
||||
group("group");
|
||||
|
||||
val description: String get() = generalGetString(when (this) {
|
||||
contact -> R.string.simplex_link_contact
|
||||
invitation -> R.string.simplex_link_invitation
|
||||
group -> R.string.simplex_link_group
|
||||
})
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class FormatColor(val color: String) {
|
||||
red("red"),
|
||||
|
||||
@@ -60,6 +60,16 @@ enum class CallOnLockScreen {
|
||||
}
|
||||
}
|
||||
|
||||
enum class SimplexLinkMode {
|
||||
DESCRIPTION,
|
||||
FULL,
|
||||
BROWSER;
|
||||
|
||||
companion object {
|
||||
val default = SimplexLinkMode.DESCRIPTION
|
||||
}
|
||||
}
|
||||
|
||||
class AppPreferences(val context: Context) {
|
||||
private val sharedPreferences: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
|
||||
|
||||
@@ -91,6 +101,18 @@ class AppPreferences(val context: Context) {
|
||||
val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true)
|
||||
val privacyTransferImagesInline = mkBoolPreference(SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE, false)
|
||||
val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true)
|
||||
private val _simplexLinkMode = mkStrPreference(SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE, SimplexLinkMode.default.name)
|
||||
val simplexLinkMode: Preference<SimplexLinkMode> = Preference(
|
||||
get = fun(): SimplexLinkMode {
|
||||
val value = _simplexLinkMode.get() ?: return SimplexLinkMode.default
|
||||
return try {
|
||||
SimplexLinkMode.valueOf(value)
|
||||
} catch (e: Error) {
|
||||
SimplexLinkMode.default
|
||||
}
|
||||
},
|
||||
set = fun(mode: SimplexLinkMode) { _simplexLinkMode.set(mode.name) }
|
||||
)
|
||||
val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false)
|
||||
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
|
||||
val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null)
|
||||
@@ -181,6 +203,7 @@ class AppPreferences(val context: Context) {
|
||||
private const val SHARED_PREFS_PRIVACY_ACCEPT_IMAGES = "PrivacyAcceptImages"
|
||||
private const val SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE = "PrivacyTransferImagesInline"
|
||||
private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews"
|
||||
private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode"
|
||||
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
|
||||
private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName"
|
||||
private const val SHARED_PREFS_CHAT_ARCHIVE_TIME = "ChatArchiveTime"
|
||||
|
||||
@@ -84,7 +84,7 @@ fun MarkdownText (
|
||||
hasLinks = true
|
||||
val ftStyle = ft.format.style
|
||||
withAnnotation(tag = "URL", annotation = link) {
|
||||
withStyle(ftStyle) { append(ft.text) }
|
||||
withStyle(ftStyle) { append(ft.viewText) }
|
||||
}
|
||||
} else {
|
||||
withStyle(ft.format.style) { append(ft.text) }
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
<string name="description_via_one_time_link">über einen Einmal-Link</string>
|
||||
<string name="description_via_one_time_link_incognito">Inkognito über einen Einmal-Link</string>
|
||||
|
||||
<!-- FormattedText, SimpleX links - ChatModel.kt -->
|
||||
<string name="simplex_link_contact">SimpleX Kontaktadressen-Link</string>
|
||||
<string name="simplex_link_invitation">SimpleX Einmal-Link</string>
|
||||
<string name="simplex_link_group">SimpleX Gruppen-Link</string>
|
||||
<string name="simplex_link_connection">über <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
|
||||
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Fehler beim Speichern der SMP-Server</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server Adressen das richtige Format haben, zeilenweise angeordnet und nicht kopiert sind.</string>
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
<string name="description_via_one_time_link">через одноразовую ссылку</string>
|
||||
<string name="description_via_one_time_link_incognito">инкогнито через одноразовую ссылку</string>
|
||||
|
||||
<!-- FormattedText, SimpleX links - ChatModel.kt -->
|
||||
<string name="simplex_link_contact">SimpleX ссылка-контакт</string>
|
||||
<string name="simplex_link_invitation">SimpleX одноразовая ссылка</string>
|
||||
<string name="simplex_link_group">SimpleX ссылка группы</string>
|
||||
<string name="simplex_link_connection">через <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
|
||||
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Ошибка при сохранении SMP серверов</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется.</string>
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
<string name="description_via_one_time_link">via one-time link</string>
|
||||
<string name="description_via_one_time_link_incognito">incognito via one-time link</string>
|
||||
|
||||
<!-- FormattedText, SimpleX links - ChatModel.kt -->
|
||||
<string name="simplex_link_contact">SimpleX contact address</string>
|
||||
<string name="simplex_link_invitation">SimpleX 1-time invitation</string>
|
||||
<string name="simplex_link_group">SimpleX group link</string>
|
||||
<string name="simplex_link_connection">via <xliff:g id="serverHost" example="smp.simplex.im">%1$s</xliff:g></string>
|
||||
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Error saving SMP servers</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
|
||||
|
||||
@@ -70,6 +70,12 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text {
|
||||
case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary)
|
||||
case let .colored(color): return Text(t).foregroundColor(color.uiColor)
|
||||
case .uri: return linkText(t, t, preview, prefix: "")
|
||||
case let .simplexLink(linkType, simplexUri, smpHosts):
|
||||
switch privacySimplexLinkModeDefault.get() {
|
||||
case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "")
|
||||
case .full: return linkText(t, simplexUri, preview, prefix: "")
|
||||
case .browser: return linkText(t, t, preview, prefix: "")
|
||||
}
|
||||
case .email: return linkText(t, t, preview, prefix: "mailto:")
|
||||
case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:")
|
||||
}
|
||||
@@ -88,6 +94,10 @@ private func linkText(_ s: String, _ link: String,
|
||||
]))).underline()
|
||||
}
|
||||
|
||||
private func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String {
|
||||
linkType.description + " " + "(via \(smpHosts.first ?? "?"))"
|
||||
}
|
||||
|
||||
struct MsgContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatItem = ChatItem.getSample(1, .directSnd, .now, "hello")
|
||||
|
||||
@@ -14,6 +14,7 @@ struct PrivacySettings: View {
|
||||
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
@AppStorage(GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE, store: groupDefaults) private var transferImagesInline = false
|
||||
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -21,7 +22,8 @@ struct PrivacySettings: View {
|
||||
Section("Device") {
|
||||
SimplexLockSetting()
|
||||
}
|
||||
Section("Chats") {
|
||||
|
||||
Section {
|
||||
settingsRow("photo") {
|
||||
Toggle("Auto-accept images", isOn: $autoAcceptImages)
|
||||
.onChange(of: autoAcceptImages) {
|
||||
@@ -36,6 +38,23 @@ struct PrivacySettings: View {
|
||||
settingsRow("network") {
|
||||
Toggle("Send link previews", isOn: $useLinkPreviews)
|
||||
}
|
||||
settingsRow("link") {
|
||||
Picker("SimpleX links", selection: $simplexLinkMode) {
|
||||
ForEach(SimpleXLinkMode.values) { mode in
|
||||
Text(mode.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 36)
|
||||
.onChange(of: simplexLinkMode) { mode in
|
||||
privacySimplexLinkModeDefault.set(mode)
|
||||
}
|
||||
} header: {
|
||||
Text("Chats")
|
||||
} footer: {
|
||||
if case .browser = simplexLinkMode {
|
||||
Text("Opening the link in the browser may reduce connection privacy and security.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
|
||||
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
|
||||
let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
|
||||
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews"
|
||||
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
|
||||
let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls"
|
||||
let DEFAULT_CHAT_ARCHIVE_NAME = "chatArchiveName"
|
||||
let DEFAULT_CHAT_ARCHIVE_TIME = "chatArchiveTime"
|
||||
@@ -43,6 +44,7 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_WEBRTC_POLICY_RELAY: true,
|
||||
DEFAULT_PRIVACY_ACCEPT_IMAGES: true,
|
||||
DEFAULT_PRIVACY_LINK_PREVIEWS: true,
|
||||
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: "description",
|
||||
DEFAULT_EXPERIMENTAL_CALLS: false,
|
||||
DEFAULT_CHAT_V3_DB_MIGRATION: "offer",
|
||||
DEFAULT_DEVELOPER_TOOLS: false,
|
||||
@@ -54,6 +56,24 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_CONNECT_VIA_LINK_TAB: "scan"
|
||||
]
|
||||
|
||||
enum SimpleXLinkMode: String, Identifiable {
|
||||
case description
|
||||
case full
|
||||
case browser
|
||||
|
||||
static var values: [SimpleXLinkMode] = [.description, .full, .browser]
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
var text: LocalizedStringKey {
|
||||
switch self {
|
||||
case .description: return "Description"
|
||||
case .full: return "Full link"
|
||||
case .browser: return "Via browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var indent: CGFloat = 36
|
||||
|
||||
let chatArchiveTimeDefault = DateDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CHAT_ARCHIVE_TIME)
|
||||
@@ -64,6 +84,8 @@ let encryptionStartedAtDefault = DateDefault(defaults: UserDefaults.standard, fo
|
||||
|
||||
let connectViaLinkTabDefault = EnumDefault<ConnectViaLinkTab>(defaults: UserDefaults.standard, forKey: DEFAULT_CONNECT_VIA_LINK_TAB, withDefault: .scan)
|
||||
|
||||
let privacySimplexLinkModeDefault = EnumDefault<SimpleXLinkMode>(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_SIMPLEX_LINK_MODE, withDefault: .description)
|
||||
|
||||
func setGroupDefaults() {
|
||||
privacyAcceptImagesGroupDefault.set(UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES))
|
||||
}
|
||||
|
||||
@@ -1713,10 +1713,26 @@ public enum Format: Decodable, Equatable {
|
||||
case secret
|
||||
case colored(color: FormatColor)
|
||||
case uri
|
||||
// TODO trustedUri: Bool
|
||||
case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String])
|
||||
case email
|
||||
case phone
|
||||
}
|
||||
|
||||
public enum SimplexLinkType: String, Decodable {
|
||||
case contact
|
||||
case invitation
|
||||
case group
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .contact: return NSLocalizedString("SimpleX contact address", comment: "simplex link type")
|
||||
case .invitation: return NSLocalizedString("SimpleX 1-time invitation", comment: "simplex link type")
|
||||
case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum FormatColor: String, Decodable {
|
||||
case red = "red"
|
||||
case green = "green"
|
||||
|
||||
Reference in New Issue
Block a user