diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..6256823471 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,68 @@ +name: Bug +description: File a bug report/issue +title: "[Bug]: " +labels: ["type:bug", "type:triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: dropdown + attributes: + label: Platform + description: Multiple selections are possible. + multiple: true + options: + - Linux + - Mac + - Windows + - Android + - iOS + validations: + required: true +- type: input + attributes: + label: OS version + description: Specify the OS version + placeholder: ex. Android 12, Ubuntu 20.04 + validations: + required: true +- type: input + attributes: + label: App version + description: Specify the SimpleX version + placeholder: ex. 4.3.2 + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + placeholder: Bug happened! + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + placeholder: No bug should happen! + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to ... + 3. Click on ... + 4. See error... + validations: + required: true +- type: textarea + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..0086358db1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000000..84b8ac5dcc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,40 @@ +name: Feature +description: Suggest your feature +title: "[Feature]: " +labels: ["type:enhancement", "type:triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: dropdown + attributes: + label: Platform + description: Multiple selections are possible. If selected input is "all", this considered to be a general feature. + multiple: true + options: + - Linux + - Mac + - Windows + - Android + - iOS + - all + validations: + required: true +- type: input + attributes: + label: App version + description: Specify the SimpleX version + placeholder: ex. 4.3.2 + validations: + required: false +- type: textarea + attributes: + label: Feature + description: Describe the feature you would like to see added + placeholder: SimpleX Chat should make me coffee! + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000000..b69aa7a3ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,16 @@ +name: Question +description: Ask your question +title: "[Q]: " +labels: ["type:question", "type:triage"] +body: +- type: markdown + attributes: + value: | + Generally, we encourage you to ask questions in our [official group](https://simplex.chat/invitation/#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3Dsimplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D), but you can do it anyway :) +- type: textarea + attributes: + label: Question + description: Please ask your question in plain english. + placeholder: Is SimpleX - chat? + validations: + required: true diff --git a/.gitignore b/.gitignore index d4b4d6cb33..44fd61e359 100644 --- a/.gitignore +++ b/.gitignore @@ -49,8 +49,8 @@ logs/ # for website website/node_modules/ website/src/blog/ +website/src/docs/ website/translations.json -website/src/_data/supported_languages.json website/src/img/images/ website/src/images/ # Generated files diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 0a256bbb99..2291fcd1dd 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -119,6 +119,7 @@ dependencies { implementation 'androidx.fragment:fragment:1.4.1' implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.3.2' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' + implementation 'com.charleskorn.kaml:kaml:0.43.0' //implementation "androidx.compose.material:material-icons-extended:$compose_version" implementation "androidx.compose.ui:ui-util:$compose_version" implementation "androidx.navigation:navigation-compose:2.4.1" diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 953b598630..5eec89ee5b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -26,8 +27,7 @@ import androidx.lifecycle.* import chat.simplex.app.MainActivity.Companion.enteredBackground import chat.simplex.app.model.* import chat.simplex.app.model.NtfManager.Companion.getUserIdFromIntent -import chat.simplex.app.ui.theme.SimpleButton -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.SplashView import chat.simplex.app.views.call.ActiveCallView import chat.simplex.app.views.call.IncomingCallAlertView @@ -83,11 +83,7 @@ class MainActivity: FragmentActivity() { } setContent { SimpleXTheme { - Surface( - Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() - ) { + Surface(color = MaterialTheme.colors.background) { MainPage( m, userAuthorized, @@ -420,7 +416,7 @@ fun MainPage( @Composable fun authView() { Box( - Modifier.fillMaxSize(), + Modifier.fillMaxSize().background(MaterialTheme.colors.background), contentAlignment = Alignment.Center ) { SimpleButton( @@ -633,7 +629,7 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { ConnectionLinkType.INVITATION -> generalGetString(R.string.connect_via_invitation_link) ConnectionLinkType.GROUP -> generalGetString(R.string.connect_via_group_link) } - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = title, text = if (linkType == ConnectionLinkType.GROUP) generalGetString(R.string.you_will_join_group) diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt index 1559002080..2a6d7587bc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt @@ -6,6 +6,7 @@ import android.util.Log import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.DefaultTheme import chat.simplex.app.views.helpers.* import chat.simplex.app.views.onboarding.OnboardingStage import chat.simplex.app.views.usersettings.NotificationsMode @@ -95,6 +96,7 @@ class SimplexApp: Application(), LifecycleEventObserver { initChatController() ProcessLifecycleOwner.get().lifecycle.addObserver(this) context.getDir("temp", MODE_PRIVATE).deleteRecursively() + runMigrations() } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { @@ -184,6 +186,23 @@ class SimplexApp: Application(), LifecycleEventObserver { MessagesFetcherWorker.scheduleWork() } + private fun runMigrations() { + val lastMigration = chatModel.controller.appPrefs.lastMigratedVersionCode + if (lastMigration.get() < BuildConfig.VERSION_CODE) { + while (true) { + if (lastMigration.get() < 117) { + if (chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.DARK.name) { + chatModel.controller.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) + } + lastMigration.set(117) + } else { + lastMigration.set(BuildConfig.VERSION_CODE) + break + } + } + } + } + companion object { lateinit var context: SimplexApp private set diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 0f54b42d8c..8947c782fd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -1516,7 +1516,7 @@ data class CIMeta ( val isRcvNew: Boolean get() = itemStatus is CIStatus.RcvNew - fun statusIcon(primaryColor: Color, metaColor: Color = HighOrLowlight): Pair? = + fun statusIcon(primaryColor: Color, metaColor: Color = CurrentColors.value.colors.secondary): Pair? = when (itemStatus) { is CIStatus.SndSent -> R.drawable.ic_check_filled to metaColor is CIStatus.SndErrorAuth -> R.drawable.ic_close to Color.Red diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index 1fe7e1aa1a..662867f368 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -26,10 +25,14 @@ import chat.simplex.app.views.call.* import chat.simplex.app.views.newchat.ConnectViaLinkTab import chat.simplex.app.views.onboarding.OnboardingStage import chat.simplex.app.views.usersettings.* +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlConfiguration import kotlinx.coroutines.* import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.* import java.util.Date @@ -57,6 +60,7 @@ enum class SimplexLinkMode { class AppPreferences(val context: Context) { private val sharedPreferences: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) + private val sharedPreferencesThemes: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_THEMES_ID, Context.MODE_PRIVATE) // deprecated, remove in 2024 private val runServiceInBackground = mkBoolPreference(SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND, true) @@ -147,9 +151,15 @@ class AppPreferences(val context: Context) { val confirmDBUpgrades = mkBoolPreference(SHARED_PREFS_CONFIRM_DB_UPGRADES, false) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) - val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb()) + val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name) + val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = { + json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) + }, decode = { + json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) + }, sharedPreferencesThemes) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) + val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0) private fun mkIntPreference(prefName: String, default: Int) = SharedPreference( @@ -204,8 +214,15 @@ class AppPreferences(val context: Context) { } ) + private fun mkMapPreference(prefName: String, default: Map, encode: (Map) -> String, decode: (String) -> Map, prefs: SharedPreferences = sharedPreferences): SharedPreference> = + SharedPreference( + get = fun() = decode(prefs.getString(prefName, encode(default))!!), + set = fun(value) = prefs.edit().putString(prefName, encode(value)).apply() + ) + companion object { internal const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" + internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES" private const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" private const val SHARED_PREFS_RUN_SERVICE_IN_BACKGROUND = "RunServiceInBackground" private const val SHARED_PREFS_NOTIFICATIONS_MODE = "NotificationsMode" @@ -258,8 +275,10 @@ class AppPreferences(val context: Context) { private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" - private const val SHARED_PREFS_PRIMARY_COLOR = "PrimaryColor" + private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" + private const val SHARED_PREFS_THEMES = "Themes" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" + private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" } } @@ -2581,7 +2600,7 @@ data class FeatureEnabled( } val iconColor: Color - get() = if (forUser) SimplexGreen else if (forContact) WarningYellow else HighOrLowlight + get() = if (forUser) SimplexGreen else if (forContact) WarningYellow else CurrentColors.value.colors.secondary companion object { fun enabled(asymmetric: Boolean, user: ChatPreference, contact: ChatPreference): FeatureEnabled = @@ -2965,7 +2984,7 @@ enum class GroupFeatureEnabled { } val iconColor: Color - get() = if (this == ON) SimplexGreen else HighOrLowlight + get() = if (this == ON) SimplexGreen else CurrentColors.value.colors.secondary } @@ -2976,6 +2995,11 @@ val json = Json { explicitNulls = false } +val yaml = Yaml(configuration = YamlConfiguration( + strictMode = false, + encodeDefaults = false, +)) + @Serializable class APIResponse(val resp: CR, val corr: String? = null) { companion object { diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt index e0187c60e4..84686d7482 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Color.kt @@ -13,7 +13,6 @@ val SimplexGreen = Color(77, 218, 103, 255) val SecretColor = Color(0x40808080) val LightGray = Color(241, 242, 246, 255) val DarkGray = Color(43, 44, 46, 255) -val DarkGrayBackground = Color(38, 38, 38, 255) val HighOrLowlight = Color(139, 135, 134, 255) val MessagePreviewDark = Color(179, 175, 174, 255) val MessagePreviewLight = Color(49, 45, 44, 255) @@ -22,7 +21,6 @@ val ToolbarDark = Color(80, 80, 80, 12) val SettingsSecondaryLight = Color(200, 196, 195, 90) val GroupDark = Color(80, 80, 80, 60) val IncomingCallLight = Color(239, 237, 236, 255) -val IncomingCallDark = Color(34, 30, 29, 255) val WarningOrange = Color(255, 127, 0, 255) val WarningYellow = Color(255, 192, 0, 255) val FileLight = Color(183, 190, 199, 255) diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt index ccd7cce6f2..24652c76ff 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt @@ -2,16 +2,189 @@ package chat.simplex.app.ui.theme import android.app.UiModeManager import android.content.Context +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.* import androidx.compose.ui.unit.dp +import chat.simplex.app.R import chat.simplex.app.SimplexApp +import chat.simplex.app.views.helpers.* import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import okhttp3.internal.toHexString enum class DefaultTheme { - SYSTEM, DARK, LIGHT + SYSTEM, LIGHT, DARK, SIMPLEX; + + // Call it only with base theme, not SYSTEM + fun hasChangedAnyColor(colors: Colors, appColors: AppColors): Boolean { + val palette = when (this) { + SYSTEM -> return false + LIGHT -> LightColorPalette + DARK -> DarkColorPalette + SIMPLEX -> SimplexColorPalette + } + val appPalette = when (this) { + SYSTEM -> return false + LIGHT -> LightColorPaletteApp + DARK -> DarkColorPaletteApp + SIMPLEX -> SimplexColorPaletteApp + } + return colors.primary != palette.primary || + colors.primaryVariant != palette.primaryVariant || + colors.secondary != palette.secondary || + colors.secondaryVariant != palette.secondaryVariant || + colors.background != palette.background || + colors.surface != palette.surface || + appColors != appPalette + } +} + +data class AppColors( + val title: Color, + val sentMessage: Color, + val receivedMessage: Color +) + +enum class ThemeColor { + PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE; + + fun fromColors(colors: Colors, appColors: AppColors): Color { + return when (this) { + PRIMARY -> colors.primary + PRIMARY_VARIANT -> colors.primaryVariant + SECONDARY -> colors.secondary + SECONDARY_VARIANT -> colors.secondaryVariant + BACKGROUND -> colors.background + SURFACE -> colors.surface + TITLE -> appColors.title + SENT_MESSAGE -> appColors.sentMessage + RECEIVED_MESSAGE -> appColors.receivedMessage + } + } + + val text: String + get() = when (this) { + PRIMARY -> generalGetString(R.string.color_primary) + PRIMARY_VARIANT -> generalGetString(R.string.color_primary_variant) + SECONDARY -> generalGetString(R.string.color_secondary) + SECONDARY_VARIANT -> generalGetString(R.string.color_secondary_variant) + BACKGROUND -> generalGetString(R.string.color_background) + SURFACE -> generalGetString(R.string.color_surface) + TITLE -> generalGetString(R.string.color_title) + SENT_MESSAGE -> generalGetString(R.string.color_sent_message) + RECEIVED_MESSAGE -> generalGetString(R.string.color_received_message) + } +} + +@Serializable +data class ThemeColors( + @SerialName("accent") + val primary: String? = null, + @SerialName("accentVariant") + val primaryVariant: String? = null, + val secondary: String? = null, + val secondaryVariant: String? = null, + val background: String? = null, + @SerialName("menus") + val surface: String? = null, + val title: String? = null, + val sentMessage: String? = null, + val receivedMessage: String? = null, +) { + fun toColors(base: DefaultTheme): Colors { + val baseColors = when (base) { + DefaultTheme.LIGHT -> LightColorPalette + DefaultTheme.DARK -> DarkColorPalette + DefaultTheme.SIMPLEX -> SimplexColorPalette + // shouldn't be here + DefaultTheme.SYSTEM -> LightColorPalette + } + return baseColors.copy( + primary = primary?.colorFromReadableHex() ?: baseColors.primary, + primaryVariant = primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant, + secondary = secondary?.colorFromReadableHex() ?: baseColors.secondary, + secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant, + background = background?.colorFromReadableHex() ?: baseColors.background, + surface = surface?.colorFromReadableHex() ?: baseColors.surface, + ) + } + + fun toAppColors(base: DefaultTheme): AppColors { + val baseColors = when (base) { + DefaultTheme.LIGHT -> LightColorPaletteApp + DefaultTheme.DARK -> DarkColorPaletteApp + DefaultTheme.SIMPLEX -> SimplexColorPaletteApp + // shouldn't be here + DefaultTheme.SYSTEM -> LightColorPaletteApp + } + return baseColors.copy( + title = title?.colorFromReadableHex() ?: baseColors.title, + sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage, + receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage, + ) + } + + fun withFilledColors(base: DefaultTheme): ThemeColors { + val c = toColors(base) + val ac = toAppColors(base) + return ThemeColors( + primary = c.primary.toReadableHex(), + primaryVariant = c.primaryVariant.toReadableHex(), + secondary = c.secondary.toReadableHex(), + secondaryVariant = c.secondaryVariant.toReadableHex(), + background = c.background.toReadableHex(), + surface = c.surface.toReadableHex(), + title = ac.title.toReadableHex(), + sentMessage = ac.sentMessage.toReadableHex(), + receivedMessage = ac.receivedMessage.toReadableHex() + ) + } +} + +private fun String.colorFromReadableHex(): Color = + Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong()) + +private fun Color.toReadableHex(): String = "#" + toArgb().toHexString() + +@Serializable +data class ThemeOverrides ( + val base: DefaultTheme, + val colors: ThemeColors +) { + fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides { + return copy(colors = when (name) { + ThemeColor.PRIMARY -> colors.copy(primary = color) + ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color) + ThemeColor.SECONDARY -> colors.copy(secondary = color) + ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color) + ThemeColor.BACKGROUND -> colors.copy(background = color) + ThemeColor.SURFACE -> colors.copy(surface = color) + ThemeColor.TITLE -> colors.copy(title = color) + ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color) + ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color) + }) + } +} + +fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, shape: Shape = RectangleShape): Modifier { + return if (baseTheme == DefaultTheme.SIMPLEX) { + this.background(brush = Brush.linearGradient( + listOf( + CurrentColors.value.colors.background.darker(0.4f), + CurrentColors.value.colors.background.lighter(0.4f) + ), + Offset(0f, Float.POSITIVE_INFINITY), + Offset(Float.POSITIVE_INFINITY, 0f) + ), shape = shape) + } else { + this.background(color = CurrentColors.value.colors.background, shape = shape) + } } val DEFAULT_PADDING = 20.dp @@ -22,10 +195,11 @@ val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp val DarkColorPalette = darkColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files - primaryVariant = SimplexGreen, - secondary = DarkGray, + primaryVariant = SimplexBlue, + secondary = HighOrLowlight, + secondaryVariant = DarkGray, // background = Color.Black, -// surface = Color.Black, + surface = Color(0xFF222222), // background = Color(0xFF121212), // surface = Color(0xFF121212), error = Color.Red, @@ -33,27 +207,59 @@ val DarkColorPalette = darkColors( onSurface = Color(0xFFFFFBFA), // onError: Color = Color.Black, ) +val DarkColorPaletteApp = AppColors( + title = SimplexBlue, + sentMessage = Color(0x1E45B8FF), + receivedMessage = Color(0x20B1B0B5) +) + val LightColorPalette = lightColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files - primaryVariant = SimplexGreen, - secondary = LightGray, + primaryVariant = SimplexBlue, + secondary = HighOrLowlight, + secondaryVariant = LightGray, error = Color.Red, // background = Color.White, -// surface = Color.White + surface = Color.White, // onPrimary = Color.White, // onSecondary = Color.Black, // onBackground = Color.Black, // onSurface = Color.Black, ) +val LightColorPaletteApp = AppColors( + title = SimplexBlue, + sentMessage = Color(0x1E45B8FF), + receivedMessage = Color(0x20B1B0B5) +) -val CurrentColors: MutableStateFlow> = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) +val SimplexColorPalette = darkColors( + primary = Color(0xFF70F0F9), // If this value changes also need to update #0088ff in string resource files + primaryVariant = Color(0xFF1298A5), + secondary = HighOrLowlight, + secondaryVariant = Color(0xFF2C464D), + background = Color(0xFF111528), + // surface = Color.Black, + // background = Color(0xFF121212), + surface = Color(0xFF121C37), + error = Color.Red, +// onBackground = Color(0xFFFFFBFA), +// onSurface = Color(0xFFFFFBFA), + // onError: Color = Color.Black, +) +val SimplexColorPaletteApp = AppColors( + title = Color(0xFF267BE5), + sentMessage = Color(0x1E45B8FF), + receivedMessage = Color(0x20B1B0B5) +) + +val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) // Non-@Composable implementation private fun isInNightMode() = (SimplexApp.context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager).nightMode == UiModeManager.MODE_NIGHT_YES @Composable -fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.first.isLight +fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight @Composable fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { @@ -64,14 +270,14 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { } val systemDark = isSystemInDarkTheme() LaunchedEffect(systemDark) { - if (CurrentColors.value.second == DefaultTheme.SYSTEM && CurrentColors.value.first.isLight == systemDark) { + if (SimplexApp.context.chatModel.controller.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == systemDark) { // Change active colors from light to dark and back based on system theme ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, systemDark) } } val theme by CurrentColors.collectAsState() MaterialTheme( - colors = theme.first, + colors = theme.colors, typography = Typography, shapes = Shapes, content = content diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt index a1d5ee4526..3453dad7e9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/ThemeManager.kt @@ -7,22 +7,53 @@ import chat.simplex.app.R import chat.simplex.app.SimplexApp import chat.simplex.app.model.AppPreferences import chat.simplex.app.views.helpers.generalGetString +import okhttp3.internal.toHexString object ThemeManager { private val appPrefs: AppPreferences by lazy { - AppPreferences(SimplexApp.context) + SimplexApp.context.chatModel.controller.appPrefs } - fun currentColors(darkForSystemTheme: Boolean): Pair { - val theme = appPrefs.currentTheme.get()!! - val systemThemeColors = if (darkForSystemTheme) DarkColorPalette else LightColorPalette - val res = when (theme) { - DefaultTheme.SYSTEM.name -> Pair(systemThemeColors, DefaultTheme.SYSTEM) - DefaultTheme.DARK.name -> Pair(DarkColorPalette, DefaultTheme.DARK) - DefaultTheme.LIGHT.name -> Pair(LightColorPalette, DefaultTheme.LIGHT) - else -> Pair(systemThemeColors, DefaultTheme.SYSTEM) + data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors) + + private fun systemDarkThemeColors(): Pair = when (appPrefs.systemDarkTheme.get()) { + DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK + DefaultTheme.SIMPLEX.name -> SimplexColorPalette to DefaultTheme.SIMPLEX + else -> SimplexColorPalette to DefaultTheme.SIMPLEX + } + + fun currentColors(darkForSystemTheme: Boolean): ActiveTheme { + val themeName = appPrefs.currentTheme.get()!! + val themeOverrides = appPrefs.themeOverrides.get() + + val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + themeName + } else { + if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name } - return res.copy(first = res.first.copy(primary = Color(appPrefs.primaryColor.get()))) + val theme = themeOverrides[nonSystemThemeName] + val baseTheme = when (nonSystemThemeName) { + DefaultTheme.LIGHT.name -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) + DefaultTheme.DARK.name -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp) + DefaultTheme.SIMPLEX.name -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp) + else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) + } + if (theme == null) { + return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third) + } + return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base), theme.colors.toAppColors(theme.base)) + } + + fun currentThemeOverridesForExport(darkForSystemTheme: Boolean): ThemeOverrides { + val themeName = appPrefs.currentTheme.get()!! + val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + themeName + } else { + if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + } + val overrides = appPrefs.themeOverrides.get().toMutableMap() + val nonFilledTheme = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) + return nonFilledTheme.copy(colors = nonFilledTheme.colors.withFilledColors(CurrentColors.value.base)) } // colors, default theme enum, localized name of theme @@ -30,7 +61,7 @@ object ThemeManager { val allThemes = ArrayList>() allThemes.add( Triple( - if (darkForSystemTheme) DarkColorPalette else LightColorPalette, + if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette, DefaultTheme.SYSTEM, generalGetString(R.string.theme_system) ) @@ -49,16 +80,73 @@ object ThemeManager { generalGetString(R.string.theme_dark) ) ) + allThemes.add( + Triple( + SimplexColorPalette, + DefaultTheme.SIMPLEX, + generalGetString(R.string.theme_simplex) + ) + ) return allThemes } - fun applyTheme(name: String, darkForSystemTheme: Boolean) { - appPrefs.currentTheme.set(name) + fun applyTheme(theme: String, darkForSystemTheme: Boolean) { + appPrefs.currentTheme.set(theme) CurrentColors.value = currentColors(darkForSystemTheme) } - fun saveAndApplyPrimaryColor(color: Color) { - appPrefs.primaryColor.set(color.toArgb()) - CurrentColors.value = currentColors(!CurrentColors.value.first.isLight) + fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) { + appPrefs.systemDarkTheme.set(theme) + CurrentColors.value = currentColors(darkForSystemTheme) + } + + fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) { + val themeName = appPrefs.currentTheme.get()!! + val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + themeName + } else { + if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + } + var colorToSet = color + if (colorToSet == null) { + // Setting default color from a base theme + colorToSet = when(nonSystemThemeName) { + DefaultTheme.LIGHT.name -> name.fromColors(LightColorPalette, LightColorPaletteApp) + DefaultTheme.DARK.name -> name.fromColors(DarkColorPalette, DarkColorPaletteApp) + DefaultTheme.SIMPLEX.name -> name.fromColors(SimplexColorPalette, SimplexColorPaletteApp) + // Will not be here + else -> return + } + } + val overrides = appPrefs.themeOverrides.get().toMutableMap() + val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) + overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex()) + appPrefs.themeOverrides.set(overrides) + CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) + } + + fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean) { + val overrides = appPrefs.themeOverrides.get().toMutableMap() + val prevValue = overrides[theme.base.name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) + overrides[theme.base.name] = prevValue.copy(colors = theme.colors) + appPrefs.themeOverrides.set(overrides) + appPrefs.currentTheme.set(theme.base.name) + CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) + } + + fun resetAllThemeColors(darkForSystemTheme: Boolean) { + val themeName = appPrefs.currentTheme.get()!! + val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + themeName + } else { + if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + } + val overrides = appPrefs.themeOverrides.get().toMutableMap() + val prevValue = overrides[nonSystemThemeName] ?: return + overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors()) + appPrefs.themeOverrides.set(overrides) + CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) } } + +private fun Color.toReadableHex(): String = "#" + toArgb().toHexString() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt index f37a6fca8e..a831bcc3a9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/SplashView.kt @@ -1,6 +1,5 @@ package chat.simplex.app.views -import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -11,8 +10,8 @@ import androidx.compose.ui.Modifier fun SplashView() { Surface( Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() + .fillMaxSize(), + color = MaterialTheme.colors.background ) { // Image( // painter = painterResource(R.drawable.logo), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 8641b4a08a..15688db8be 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -99,8 +99,8 @@ fun TerminalLayout( Surface( modifier = Modifier .padding(contentPadding) - .fillMaxWidth() - .background(MaterialTheme.colors.background) + .fillMaxWidth(), + color = MaterialTheme.colors.background ) { TerminalLog(terminalItems) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index cf9084ea1b..1dc1dca0a7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -44,77 +44,75 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) { val fullName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - Surface(Modifier.background(MaterialTheme.colors.onBackground)) { - Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) - ) { - /*CloseSheetBar(close = { - if (chatModel.users.isEmpty()) { - chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo - } else { - close() - } - })*/ - Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { - AppBarTitle(stringResource(R.string.create_profile)) - ReadableText(R.string.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues()) - ReadableText(R.string.profile_is_only_shared_with_your_contacts, TextAlign.Center) - Spacer(Modifier.height(DEFAULT_PADDING * 1.5f)) - Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Text( - stringResource(R.string.display_name), - fontSize = 16.sp - ) - if (!isValidDisplayName(displayName.value)) { - Text( - stringResource(R.string.no_spaces), - fontSize = 16.sp, - color = Color.Red - ) - } - } - ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) - Spacer(Modifier.height(DEFAULT_PADDING)) + Column( + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) + ) { + /*CloseSheetBar(close = { + if (chatModel.users.isEmpty()) { + chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + } else { + close() + } + })*/ + Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { + AppBarTitle(stringResource(R.string.create_profile)) + ReadableText(R.string.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues()) + ReadableText(R.string.profile_is_only_shared_with_your_contacts, TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 1.5f)) + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( - stringResource(R.string.full_name_optional__prompt), - fontSize = 16.sp, - modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) + stringResource(R.string.display_name), + fontSize = 16.sp ) - ProfileNameField(fullName, "", ::isValidDisplayName) - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Row { - if (chatModel.users.isEmpty()) { - SimpleButtonDecorated( - text = stringResource(R.string.about_simplex), - icon = painterResource(R.drawable.ic_arrow_back_ios_new), - textDecoration = TextDecoration.None, - fontWeight = FontWeight.Medium - ) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } - } - Spacer(Modifier.fillMaxWidth().weight(1f)) - val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) - val createModifier: Modifier - val createColor: Color - if (enabled) { - createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value, close) }.padding(8.dp) - createColor = MaterialTheme.colors.primary - } else { - createModifier = Modifier.padding(8.dp) - createColor = HighOrLowlight - } - Surface(shape = RoundedCornerShape(20.dp)) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) { - Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium) - Icon(painterResource(R.drawable.ic_arrow_forward_ios), stringResource(R.string.create_profile_button), tint = createColor) - } + if (!isValidDisplayName(displayName.value)) { + Text( + stringResource(R.string.no_spaces), + fontSize = 16.sp, + color = Color.Red + ) } } + ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(R.string.full_name_optional__prompt), + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) + ) + ProfileNameField(fullName, "", ::isValidDisplayName) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Row { + if (chatModel.users.isEmpty()) { + SimpleButtonDecorated( + text = stringResource(R.string.about_simplex), + icon = painterResource(R.drawable.ic_arrow_back_ios_new), + textDecoration = TextDecoration.None, + fontWeight = FontWeight.Medium + ) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } + } + Spacer(Modifier.fillMaxWidth().weight(1f)) + val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) + val createModifier: Modifier + val createColor: Color + if (enabled) { + createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value, close) }.padding(8.dp) + createColor = MaterialTheme.colors.primary + } else { + createModifier = Modifier.padding(8.dp) + createColor = MaterialTheme.colors.secondary + } + Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) { + Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium) + Icon(painterResource(R.drawable.ic_arrow_forward_ios), stringResource(R.string.create_profile_button), tint = createColor) + } + } + } - LaunchedEffect(Unit) { - delay(300) - focusRequester.requestFocus() - } + LaunchedEffect(Unit) { + delay(300) + focusRequester.requestFocus() } } } @@ -147,9 +145,9 @@ fun ProfileNameField(name: MutableState, placeholder: String = "", isVal derivedStateOf { if (valid) { if (focused) { - HighOrLowlight.copy(alpha = 0.6f) + CurrentColors.value.colors.secondary.copy(alpha = 0.6f) } else { - HighOrLowlight.copy(alpha = 0.3f) + CurrentColors.value.colors.secondary.copy(alpha = 0.3f) } } else Color.Red } @@ -176,7 +174,7 @@ fun ProfileNameField(name: MutableState, placeholder: String = "", isVal autoCorrect = false ), singleLine = true, - cursorBrush = SolidColor(HighOrLowlight) + cursorBrush = SolidColor(MaterialTheme.colors.secondary) ) } LaunchedEffect(Unit) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt index 9025ba32db..e7e235c606 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/CallView.kt @@ -316,7 +316,7 @@ private fun ActiveCallOverlayLayout( private fun ControlButton(call: Call, icon: Painter, @StringRes iconText: Int, action: () -> Unit, enabled: Boolean = true) { if (call.hasMedia) { IconButton(onClick = action, enabled = enabled) { - Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else HighOrLowlight, modifier = Modifier.size(40.dp)) + Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp)) } } else { Spacer(Modifier.size(40.dp)) @@ -367,7 +367,7 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { // horizontalAlignment = Alignment.CenterHorizontally, // verticalArrangement = Arrangement.spacedBy(12.dp), // modifier = Modifier -// .background(MaterialTheme.colors.background) +// .themedBackground() // .fillMaxSize() // ) { // WebRTCView(callCommand) { apiMsg -> diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt index 55abc1a941..d5282f3c37 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt @@ -97,8 +97,9 @@ fun IncomingCallActivityView(m: ChatModel) { SimpleXTheme { Surface( Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize()) { + .fillMaxSize(), + color = MaterialTheme.colors.background + ) { if (showCallView) { Box { ActiveCallView(m) @@ -212,7 +213,7 @@ private fun LockScreenCallButton(text: String, icon: Painter, color: Color, acti Icon(icon, text, tint = color, modifier = Modifier.scale(1.75f)) } Spacer(Modifier.height(DEFAULT_PADDING)) - Text(text, style = MaterialTheme.typography.body2, color = HighOrLowlight) + Text(text, style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary) } } } @@ -226,8 +227,9 @@ fun PreviewIncomingCallLockScreenAlert() { SimpleXTheme(true) { Surface( Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize()) { + .fillMaxSize(), + color = MaterialTheme.colors.background + ) { IncomingCallLockScreenAlertLayout( invitation = RcvCallInvitation( user = User.sampleData, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt index 56d57e1443..6afe1b3492 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallAlertView.kt @@ -50,13 +50,13 @@ fun IncomingCallAlertLayout( ignoreCall: () -> Unit, acceptCall: () -> Unit ) { - val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight + val color = if (isInDarkTheme()) MaterialTheme.colors.surface else IncomingCallLight Column(Modifier.fillMaxWidth().background(color).padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) { IncomingCallInfo(invitation, chatModel) Spacer(Modifier.height(8.dp)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { Row(Modifier.fillMaxWidth().weight(1f), verticalAlignment = Alignment.CenterVertically) { - ProfilePreview(profileOf = invitation.contact, size = 64.dp, color = Color.White) + ProfilePreview(profileOf = invitation.contact, size = 64.dp) } Row(verticalAlignment = Alignment.CenterVertically) { CallButton(stringResource(R.string.reject), painterResource(R.drawable.ic_call_end_filled), Color.Red, rejectCall) @@ -72,13 +72,13 @@ fun IncomingCallInfo(invitation: RcvCallInvitation, chatModel: ChatModel) { @Composable fun CallIcon(icon: Painter, descr: String) = Icon(icon, descr, tint = SimplexGreen) Row(verticalAlignment = Alignment.CenterVertically) { if (chatModel.users.size > 1) { - ProfileImage(size = 32.dp, image = invitation.user.profile.image, color = MaterialTheme.colors.secondary) + ProfileImage(size = 32.dp, image = invitation.user.profile.image, color = MaterialTheme.colors.secondaryVariant) Spacer(Modifier.width(4.dp)) } if (invitation.callType.media == CallMediaType.Video) CallIcon(painterResource(R.drawable.ic_videocam_filled), stringResource(R.string.icon_descr_video_call)) else CallIcon(painterResource(R.drawable.ic_call_filled), stringResource(R.string.icon_descr_audio_call)) Spacer(Modifier.width(4.dp)) - Text(invitation.callTypeText) + Text(invitation.callTypeText, color = MaterialTheme.colors.onBackground) } } @@ -96,7 +96,7 @@ private fun CallButton(text: String, icon: Painter, color: Color, action: () -> horizontalAlignment = Alignment.CenterHorizontally ) { Icon(icon, text, tint = color, modifier = Modifier.scale(1.2f)) - Text(text, style = MaterialTheme.typography.body2, color = HighOrLowlight) + Text(text, style = MaterialTheme.typography.body2, color = MaterialTheme.colors.secondary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index c74b579f08..0474ff71cd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -111,7 +111,7 @@ fun ChatInfoView( } fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.delete_contact_question), text = generalGetString(R.string.delete_contact_all_messages_deleted_cannot_undo_warning), confirmText = generalGetString(R.string.delete_verb), @@ -125,12 +125,13 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> close?.invoke() } } - } + }, + destructive = true, ) } fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.clear_chat_question), text = generalGetString(R.string.clear_chat_warning), confirmText = generalGetString(R.string.clear_verb), @@ -143,7 +144,8 @@ fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit close?.invoke() } } - } + }, + destructive = true, ) } @@ -240,7 +242,7 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) Row(Modifier.padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically) { if (contact.verified) { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = MaterialTheme.colors.secondary) } Text( contact.profile.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), @@ -281,13 +283,13 @@ fun LocalAliasEditor( Text( generalGetString(R.string.text_field_set_contact_placeholder), textAlign = if (center) TextAlign.Center else TextAlign.Start, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) }, leadingIcon = if (leadingIcon) { { Icon(painterResource(R.drawable.ic_edit_filled), null, Modifier.padding(start = 7.dp)) } } else null, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, focus = focus, textStyle = TextStyle.Default.copy(textAlign = if (value.isEmpty() || !center) TextAlign.Start else TextAlign.Center), keyboardActions = KeyboardActions(onDone = { updateValue(value) }) @@ -334,7 +336,7 @@ private fun NetworkStatusRow(networkStatus: NetworkStatus) { ) { Text( networkStatus.statusString, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) ServerImage(networkStatus) } @@ -348,10 +350,10 @@ private fun ServerImage(networkStatus: NetworkStatus) { is NetworkStatus.Connected -> Icon(painterResource(R.drawable.ic_circle_filled), stringResource(R.string.icon_descr_server_status_connected), tint = MaterialTheme.colors.primaryVariant) is NetworkStatus.Disconnected -> - Icon(painterResource(R.drawable.ic_pending_filled), stringResource(R.string.icon_descr_server_status_disconnected), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_pending_filled), stringResource(R.string.icon_descr_server_status_disconnected), tint = MaterialTheme.colors.secondary) is NetworkStatus.Error -> - Icon(painterResource(R.drawable.ic_error_filled), stringResource(R.string.icon_descr_server_status_error), tint = HighOrLowlight) - else -> Icon(painterResource(R.drawable.ic_circle), stringResource(R.string.icon_descr_server_status_pending), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_error_filled), stringResource(R.string.icon_descr_server_status_error), tint = MaterialTheme.colors.secondary) + else -> Icon(painterResource(R.drawable.ic_circle), stringResource(R.string.icon_descr_server_status_pending), tint = MaterialTheme.colors.secondary) } } } @@ -379,7 +381,7 @@ fun VerifyCodeButton(contactVerified: Boolean, onClick: () -> Unit) { if (contactVerified) painterResource(R.drawable.ic_verified_user) else painterResource(R.drawable.ic_shield), stringResource(if (contactVerified) R.string.view_security_code else R.string.verify_security_code), click = onClick, - iconColor = HighOrLowlight, + iconColor = MaterialTheme.colors.secondary, ) } @@ -421,13 +423,14 @@ private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: C } private fun showSwitchContactAddressAlert(m: ChatModel, contactId: Long) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.switch_receiving_address_question), text = generalGetString(R.string.switch_receiving_address_desc), confirmText = generalGetString(R.string.switch_verb), onConfirm = { switchContactAddress(m, contactId) - } + }, + destructive = true, ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 1731ba49a6..d2bc6bc0fa 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -326,7 +326,6 @@ fun ChatLayout( Box( Modifier .fillMaxWidth() - .background(MaterialTheme.colors.background) ) { ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( @@ -462,7 +461,7 @@ fun ChatInfoToolbar( } @Composable -fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondary) { +fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically @@ -496,7 +495,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo @Composable private fun ContactVerifiedShield() { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) } data class CIListState(val scrolled: Boolean, val itemCount: Int, val keyboardState: KeyboardState) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt index a36b09a169..d90d2493ba 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeFileView.kt @@ -2,6 +2,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -10,16 +11,16 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.SentColorLight @Composable fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage Row( Modifier .height(60.dp) .fillMaxWidth() .padding(top = 8.dp) - .background(SentColorLight), + .background(sentColor), verticalAlignment = Alignment.CenterVertically ) { Icon( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt index 6823f29ce8..f84bee44c8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeImageView.kt @@ -14,21 +14,18 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.app.R -import chat.simplex.app.model.durationText -import chat.simplex.app.ui.theme.DEFAULT_PADDING_HALF -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.views.chat.item.SentColorLight +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.UploadContent import chat.simplex.app.views.helpers.base64ToBitmap @Composable fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage Row( Modifier .padding(top = 8.dp) - .background(SentColorLight), + .background(sentColor), verticalAlignment = Alignment.CenterVertically, ) { LazyRow( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index 1840600666..9284e81c9f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -33,7 +33,6 @@ import androidx.core.content.ContextCompat import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.chat.item.* import chat.simplex.app.views.helpers.* import kotlinx.coroutines.* @@ -689,7 +688,7 @@ fun ComposeView( Icon( painterResource(R.drawable.ic_attach_file_filled_500), contentDescription = stringResource(R.string.attach), - tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else HighOrLowlight, + tint = if (!composeState.value.attachmentDisabled && userCanSend.value) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, modifier = Modifier .size(28.dp) .clip(CircleShape) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt index f0994b870e..9c6babe65e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.durationText import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.chat.item.SentColorLight import chat.simplex.app.views.helpers.* import kotlinx.coroutines.flow.distinctUntilChanged @@ -57,12 +56,13 @@ fun ComposeVoiceView( .height(3.dp) .background(MaterialTheme.colors.primary) ) + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage Row( Modifier .height(60.dp) .fillMaxWidth() .padding(top = 8.dp) - .background(SentColorLight), + .background(sentColor), verticalAlignment = Alignment.CenterVertically ) { IconButton( @@ -80,7 +80,7 @@ fun ComposeVoiceView( Modifier .padding(start = 4.dp, end = 2.dp) .size(36.dp), - tint = if (finishedRecording) MaterialTheme.colors.primary else HighOrLowlight + tint = if (finishedRecording) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) } val numberInText = remember(recordedDurationMs, progress.value) { @@ -95,7 +95,7 @@ fun ComposeVoiceView( Text( durationText(numberInText.value), fontSize = 18.sp, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, ) Spacer(Modifier.weight(1f)) if (cancelEnabled) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt index 46c96e47af..c5fa9daa57 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt @@ -194,10 +194,10 @@ private fun TimedMessagesFeatureSection( private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) { SectionView { SectionItemView(reset, disabled = disabled) { - Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView(save, disabled = disabled) { - Text(stringResource(R.string.save_and_notify_contact), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.save_and_notify_contact), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt index c030fb86d2..5a24eaf525 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.ui.graphics.painter.Painter import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -13,8 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.item.* import kotlinx.datetime.Clock @@ -25,10 +25,12 @@ fun ContextItemView( cancelContextItem: () -> Unit ) { val sent = contextItem.chatDir.sent + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Row( Modifier .padding(top = 8.dp) - .background(if (sent) SentColorLight else ReceivedColorLight), + .background(if (sent) sentColor else receivedColor), verticalAlignment = Alignment.CenterVertically ) { Row( @@ -45,7 +47,7 @@ fun ContextItemView( .height(20.dp) .width(20.dp), contentDescription = stringResource(R.string.icon_descr_context), - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) MarkdownText( contextItem.text, contextItem.formattedText, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index 7b9b323cb6..d9d67074d3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -44,7 +44,7 @@ import androidx.core.widget.* import chat.simplex.app.R import chat.simplex.app.SimplexApp import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.ui.theme.CurrentColors import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.ItemAction import chat.simplex.app.views.helpers.* @@ -187,7 +187,7 @@ private fun NativeKeyboard( ) { val cs = composeState.value val textColor = MaterialTheme.colors.onBackground - val tintColor = MaterialTheme.colors.secondary + val tintColor = MaterialTheme.colors.secondaryVariant val padding = PaddingValues(12.dp, 7.dp, 45.dp, 0.dp) val paddingStart = with(LocalDensity.current) { 12.dp.roundToPx() } val paddingTop = with(LocalDensity.current) { 7.dp.roundToPx() } @@ -240,7 +240,7 @@ private fun NativeKeyboard( editText.setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom) editText.setText(cs.message) if (Build.VERSION.SDK_INT >= 29) { - editText.textCursorDrawable?.let { DrawableCompat.setTint(it, HighOrLowlight.toArgb()) } + editText.textCursorDrawable?.let { DrawableCompat.setTint(it, CurrentColors.value.colors.secondary.toArgb()) } } else { try { val f: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes") @@ -284,7 +284,7 @@ private fun ComposeOverlay(textId: Int, textStyle: MutableState, padd Text( generalGetString(textId), Modifier.padding(padding), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, style = textStyle.value.copy(fontStyle = FontStyle.Italic) ) } @@ -295,7 +295,7 @@ private fun BoxScope.DeleteTextButton(composeState: MutableState) { composeState.value = composeState.value.copy(message = "") }, Modifier.align(Alignment.TopEnd).size(36.dp) ) { - Icon(painterResource(R.drawable.ic_close), null, Modifier.padding(7.dp).size(36.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_close), null, Modifier.padding(7.dp).size(36.dp), tint = MaterialTheme.colors.secondary) } } @@ -354,7 +354,7 @@ private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) { Icon( painterResource(R.drawable.ic_keyboard_voice), stringResource(R.string.icon_descr_record_voice_message), - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, modifier = Modifier .size(36.dp) .padding(4.dp) @@ -424,7 +424,7 @@ private fun RecordVoiceButton(interactionSource: MutableInteractionSource) { @Composable private fun ProgressIndicator() { - CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = HighOrLowlight, strokeWidth = 3.dp) + CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp) } @Composable @@ -474,7 +474,7 @@ private fun SendMsgButton( .padding(4.dp) .alpha(alpha.value) .clip(CircleShape) - .background(if (enabled) MaterialTheme.colors.primary else HighOrLowlight) + .background(if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) .padding(3.dp) ) } @@ -497,7 +497,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { Icon( BoltFilled, stringResource(R.string.icon_descr_send_message), - tint = if (enabled) MaterialTheme.colors.primary else HighOrLowlight, + tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, modifier = Modifier .size(36.dp) .padding(4.dp) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt index 81d20e8444..12e8ab1aa9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/VerifyCodeView.kt @@ -65,7 +65,7 @@ private fun VerifyCodeLayout( val splitCode = splitToParts(connectionCode, 24) Row(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.Center) { if (connectionVerified) { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 4.dp).size(22.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 4.dp).size(22.dp), tint = MaterialTheme.colors.secondary) Text(String.format(stringResource(R.string.is_verified), displayName)) } else { Text(String.format(stringResource(R.string.is_not_verified), displayName)) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt index a57f563250..096b19de0a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt @@ -133,7 +133,7 @@ fun AddGroupMembersLayout( Text( stringResource(R.string.no_contacts_to_add), Modifier.padding(), - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } else { @@ -214,7 +214,7 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec if (selectedContactsCount >= 1) { Text( String.format(generalGetString(R.string.num_contacts_selected), selectedContactsCount), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 12.sp ) Box( @@ -222,14 +222,14 @@ fun InviteSectionFooter(selectedContactsCount: Int, enabled: Boolean, clearSelec ) { Text( stringResource(R.string.clear_contacts_selection_button), - color = if (enabled) MaterialTheme.colors.primary else HighOrLowlight, + color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, fontSize = 12.sp ) } } else { Text( stringResource(R.string.no_contacts_selected), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 12.sp ) } @@ -270,13 +270,13 @@ fun ContactCheckRow( val iconColor: Color if (prohibitedToInviteIncognito) { icon = painterResource(R.drawable.ic_theater_comedy_filled) - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary } else if (checked) { icon = painterResource(R.drawable.ic_check_circle_filled) - iconColor = if (enabled) MaterialTheme.colors.primary else HighOrLowlight + iconColor = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary } else { icon = painterResource(R.drawable.ic_circle) - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary } SectionItemView( click = if (enabled) { @@ -294,7 +294,7 @@ fun ContactCheckRow( Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON)) Text( contact.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = if (prohibitedToInviteIncognito) HighOrLowlight else Color.Unspecified + color = if (prohibitedToInviteIncognito) MaterialTheme.colors.secondary else Color.Unspecified ) Spacer(Modifier.fillMaxWidth().weight(1f)) Icon( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt index 89c9d1b1dc..728056f1d9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt @@ -108,7 +108,7 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM val alertTextKey = if (groupInfo.membership.memberCurrent) R.string.delete_group_for_all_members_cannot_undo_warning else R.string.delete_group_for_self_cannot_undo_warning - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.delete_group_question), text = generalGetString(alertTextKey), confirmText = generalGetString(R.string.delete_verb), @@ -122,12 +122,13 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM close?.invoke() } } - } + }, + destructive = true, ) } fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.leave_group_question), text = generalGetString(R.string.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved), confirmText = generalGetString(R.string.leave_group_button), @@ -136,7 +137,8 @@ fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> U chatModel.controller.leaveGroup(groupInfo.groupId) close?.invoke() } - } + }, + destructive = true, ) } @@ -189,7 +191,7 @@ fun GroupChatInfoLayout( } val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers - val tint = if (chat.chatInfo.incognito) HighOrLowlight else MaterialTheme.colors.primary + val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary AddMembersButton(tint, onAddMembersClick) } SectionItemView(minHeight = 54.dp) { @@ -303,7 +305,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) { val statusDescr = if (user) String.format(generalGetString(R.string.group_info_member_you), s) else s Text( statusDescr, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -312,14 +314,14 @@ private fun MemberRow(member: GroupMember, user: Boolean = false) { } val role = member.memberRole if (role == GroupMemberRole.Owner || role == GroupMemberRole.Admin) { - Text(role.text, color = HighOrLowlight) + Text(role.text, color = MaterialTheme.colors.secondary) } } } @Composable private fun MemberVerifiedShield() { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = MaterialTheme.colors.secondary) } @Composable @@ -328,7 +330,7 @@ private fun GroupLinkButton(onClick: () -> Unit) { painterResource(R.drawable.ic_link), stringResource(R.string.group_link), onClick, - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary ) } @@ -338,7 +340,7 @@ private fun CreateGroupLinkButton(onClick: () -> Unit) { painterResource(R.drawable.ic_add_link), stringResource(R.string.create_group_link), onClick, - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary ) } @@ -348,7 +350,7 @@ fun EditGroupProfileButton(onClick: () -> Unit) { painterResource(R.drawable.ic_edit), stringResource(R.string.button_edit_group_profile), onClick, - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary ) } @@ -363,7 +365,7 @@ private fun AddOrEditWelcomeMessage(welcomeMessage: String?, onClick: () -> Unit painterResource(R.drawable.ic_maps_ugc), text, onClick, - iconColor = HighOrLowlight + iconColor = MaterialTheme.colors.secondary ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt index a044e9cc1b..42a893ce99 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupLinkView.kt @@ -4,8 +4,7 @@ import SectionBottomSpacer import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -66,7 +65,7 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St } }, deleteLink = { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.delete_link_question), text = generalGetString(R.string.all_group_members_will_remain_connected), confirmText = generalGetString(R.string.delete_verb), @@ -78,7 +77,8 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St onGroupLinkUpdated(null to null) } } - } + }, + destructive = true, ) } ) @@ -176,7 +176,7 @@ fun ProgressIndicator() { Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt index 0ab3e38cf9..0ba92cc3fc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupMemberInfoView.kt @@ -116,7 +116,7 @@ fun GroupMemberInfoView( } fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.button_remove_member), text = generalGetString(R.string.member_will_be_removed_from_group_cannot_be_undone), confirmText = generalGetString(R.string.remove_member_confirmation), @@ -128,7 +128,8 @@ fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: Cha } close?.invoke() } - } + }, + destructive = true, ) } @@ -238,7 +239,7 @@ fun GroupMemberInfoHeader(member: GroupMember) { ProfileImage(size = 192.dp, member.image, color = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) Row(verticalAlignment = Alignment.CenterVertically) { if (member.verified) { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.padding(end = 6.dp, top = 4.dp).size(24.dp), tint = MaterialTheme.colors.secondary) } Text( member.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt index aa9715cf1a..2b2d85e2a3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt @@ -123,7 +123,7 @@ private fun FeatureSection( SectionView { val on = enableFeature.value == GroupFeatureEnabled.ON val icon = if (on) feature.iconFilled() else feature.icon - val iconTint = if (on) SimplexGreen else HighOrLowlight + val iconTint = if (on) SimplexGreen else MaterialTheme.colors.secondary val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON if (groupInfo.canEdit) { PreferenceToggleWithIcon( @@ -157,10 +157,10 @@ private fun FeatureSection( private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) { SectionView { SectionItemView(reset, disabled = disabled) { - Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView(save, disabled = disabled) { - Text(stringResource(R.string.save_and_notify_group_members), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt index 844c0f149e..93e37c9e74 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt @@ -114,7 +114,7 @@ fun GroupProfileLayout( ) { Box(contentAlignment = Alignment.TopEnd) { Box(contentAlignment = Alignment.Center) { - ProfileImage(108.dp, profileImage.value, color = HighOrLowlight.copy(alpha = 0.1f)) + ProfileImage(108.dp, profileImage.value, color = MaterialTheme.colors.secondary.copy(alpha = 0.1f)) EditImageButton { scope.launch { bottomSheetModalState.show() } } } if (profileImage.value != null) { @@ -163,7 +163,7 @@ fun GroupProfileLayout( } else { Text( stringResource(R.string.save_group_profile), - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt index 906a8815e9..9e9408e993 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/WelcomeMessageView.kt @@ -79,7 +79,7 @@ private fun GroupWelcomeLayout( private fun SaveButton(save: () -> Unit, disabled: Boolean) { SectionView { SectionItemView(save, disabled = disabled) { - Text(stringResource(R.string.save_and_update_group_profile), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.save_and_update_group_profile), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt index 06224d1cc0..378ee4d5b4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CICallItemView.kt @@ -1,8 +1,7 @@ package chat.simplex.app.views.chat.item import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,15 +34,15 @@ fun CICallItemView(cInfo: ChatInfo, cItem: ChatItem, status: CICallStatus, durat CICallStatus.Negotiated -> ConnectingCallIcon() CICallStatus.Progress -> Icon(painterResource(R.drawable.ic_phone_in_talk_filled), stringResource(R.string.icon_descr_call_progress), tint = SimplexGreen) CICallStatus.Ended -> Row { - Icon(painterResource(R.drawable.ic_call_end), stringResource(R.string.icon_descr_call_ended), tint = HighOrLowlight, modifier = Modifier.padding(end = 4.dp)) - Text(durationText(duration), color = HighOrLowlight) + Icon(painterResource(R.drawable.ic_call_end), stringResource(R.string.icon_descr_call_ended), tint = MaterialTheme.colors.secondary, modifier = Modifier.padding(end = 4.dp)) + Text(durationText(duration), color = MaterialTheme.colors.secondary) } CICallStatus.Error -> {} } Text( cItem.timestampText, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 14.sp, modifier = Modifier.padding(start = 3.dp) ) @@ -55,7 +54,7 @@ fun AcceptCallButton(cInfo: ChatInfo, acceptCall: (Contact) -> Unit) { if (cInfo is ChatInfo.Direct) { SimpleButton(stringResource(R.string.answer_call), painterResource(R.drawable.ic_ring_volume)) { acceptCall(cInfo.contact) } } else { - Icon(painterResource(R.drawable.ic_ring_volume), stringResource(R.string.answer_call), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_ring_volume), stringResource(R.string.answer_call), tint = MaterialTheme.colors.secondary) } // if case let .direct(contact) = chatInfo { // Button { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt index 5b37c402b7..32933a57d7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIEventView.kt @@ -13,8 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* @Composable fun CIEventView(ci: ChatItem) { @@ -22,28 +21,25 @@ fun CIEventView(ci: ChatItem) { fun chatEventTextView(text: AnnotatedString) { Text(text, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp)) } - - Surface { - Row( - Modifier.padding(horizontal = 6.dp, vertical = 6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - val memberDisplayName = ci.memberDisplayName - if (memberDisplayName != null) { - chatEventTextView( - buildAnnotatedString { - withStyle(chatEventStyle) { append(memberDisplayName) } - append(" ") - }.plus(chatEventText(ci)) - ) - } else { - chatEventTextView(chatEventText(ci)) - } + Row( + Modifier.padding(horizontal = 6.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val memberDisplayName = ci.memberDisplayName + if (memberDisplayName != null) { + chatEventTextView( + buildAnnotatedString { + withStyle(chatEventStyle) { append(memberDisplayName) } + append(" ") + }.plus(chatEventText(ci)) + ) + } else { + chatEventTextView(chatEventText(ci)) } } } -val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight) +val chatEventStyle = SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = CurrentColors.value.colors.secondary) fun chatEventText(ci: ChatItem): AnnotatedString = buildAnnotatedString { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt index bb3f400eab..ab9d0896a1 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFeaturePreferenceView.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.helpers.generalGetString @Composable @@ -27,7 +26,7 @@ fun CIFeaturePreferenceView( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = HighOrLowlight) + Icon(feature.icon, feature.text, Modifier.size(18.dp), tint = MaterialTheme.colors.secondary) if (contact != null && allowed != FeatureAllowed.NO && contact.allowsFeature(feature) && !contact.userAllowsFeature(feature)) { val acceptStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp) val setParam = feature == ChatFeature.TimedMessages && contact.mergedPreferences.timedMessages.userPreference.pref.ttl == null @@ -48,7 +47,7 @@ fun CIFeaturePreferenceView( ) } else { Text(chatItem.content.text + " " + chatItem.timestampText, - fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight) + fontSize = 12.sp, fontWeight = FontWeight.Light, color = MaterialTheme.colors.secondary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt index 0194da14d8..39ac18f08a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt @@ -194,7 +194,7 @@ fun CIFileView( ) Text( formatBytes(file.fileSize) + metaReserve, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 14.sp, maxLines = 1 ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt index d55c4ee8ba..e53af51af0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIGroupInvitationView.kt @@ -8,7 +8,6 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -69,12 +68,14 @@ fun CIGroupInvitationView( } } + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Surface( modifier = if (action) Modifier.clickable(onClick = { joinGroup(groupInvitation.groupId) }) else Modifier, shape = RoundedCornerShape(18.dp), - color = if (sent) SentColorLight else ReceivedColorLight, + color = if (sent) sentColor else receivedColor, ) { Box( Modifier @@ -105,7 +106,7 @@ fun CIGroupInvitationView( } Text( ci.timestampText, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 14.sp, modifier = Modifier.padding(start = 3.dp) ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt index 81afbc10e4..23ed293876 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt @@ -14,11 +14,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.ui.theme.CurrentColors import kotlinx.datetime.Clock @Composable -fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = HighOrLowlight) { +fun CIMetaView(chatItem: ChatItem, timedMessagesTTL: Int?, metaColor: Color = MaterialTheme.colors.secondary) { Row(Modifier.padding(start = 3.dp), verticalAlignment = Alignment.CenterVertically) { if (chatItem.isDeletedContent) { Text( @@ -72,7 +72,7 @@ fun reserveSpaceForMeta(meta: CIMeta, chatTTL: Int?): String { res += TimedMessagesPreference.shortTtlText(ttl) } } - if (meta.statusIcon(HighOrLowlight) != null || !meta.disappearing) { + if (meta.statusIcon(CurrentColors.value.colors.secondary) != null || !meta.disappearing) { res += iconSpace } return res + meta.timestampText diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt index 8d6bd2c8cc..d8acbe6c49 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIVoiceView.kt @@ -137,7 +137,7 @@ private fun DurationText(text: State, padding: PaddingValues) { Modifier .padding(padding) .widthIn(min = minWidth), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, fontSize = 16.sp, maxLines = 1 ) @@ -156,9 +156,11 @@ private fun PlayPauseButton( pause: () -> Unit, longClick: () -> Unit ) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Surface( Modifier.drawRingModifier(angle, strokeColor, strokeWidth), - color = if (sent) SentColorLight else ReceivedColorLight, + color = if (sent) sentColor else receivedColor, shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)) ) { Box( @@ -174,7 +176,7 @@ private fun PlayPauseButton( if (audioPlaying) painterResource(R.drawable.ic_pause_filled) else painterResource(R.drawable.ic_play_arrow_filled), contentDescription = null, Modifier.size(36.dp), - tint = if (error) WarningOrange else if (!enabled) HighOrLowlight else MaterialTheme.colors.primary + tint = if (error) WarningOrange else if (!enabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index 5e64d13e8d..f5376355f4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -247,7 +247,7 @@ fun ChatItemView( val ct = if (cInfo is ChatInfo.Direct) cInfo.contact else null CIFeaturePreferenceView(cItem, ct, c.feature, c.allowed, acceptFeature) } - is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, HighOrLowlight, icon = c.feature.icon,) + is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon,) is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor) is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red) @@ -380,13 +380,13 @@ fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMes TextButton(onClick = { deleteMessage(chatItem.id, CIDeleteMode.cidmInternal) AlertManager.shared.hideAlert() - }) { Text(stringResource(R.string.for_me_only)) } + }) { Text(stringResource(R.string.for_me_only), color = MaterialTheme.colors.error) } if (chatItem.meta.editable) { Spacer(Modifier.padding(horizontal = 4.dp)) TextButton(onClick = { deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast) AlertManager.shared.hideAlert() - }) { Text(stringResource(R.string.for_everybody)) } + }) { Text(stringResource(R.string.for_everybody), color = MaterialTheme.colors.error) } } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt index 06499418b1..49a9ce28e6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/DeletedItemView.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.* @@ -13,15 +14,16 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* @Composable fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) { val sent = ci.chatDir.sent + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Surface( shape = RoundedCornerShape(18.dp), - color = if (sent) SentColorLight else ReceivedColorLight, + color = if (sent) sentColor else receivedColor, ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), @@ -30,7 +32,7 @@ fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = Text( buildAnnotatedString { appendSender(this, if (showMember) ci.memberDisplayName else null, true) - withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = HighOrLowlight)) { append(ci.content.text) } + withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(ci.content.text) } }, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), modifier = Modifier.padding(end = 8.dp) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index fe80af7bc5..99d6ab7eae 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -29,11 +29,6 @@ import chat.simplex.app.views.helpers.* import kotlinx.datetime.Clock import kotlin.math.min -val SentColorLight = Color(0x1E45B8FF) -val ReceivedColorLight = Color(0x20B1B0B5) -val SentQuoteColorLight = Color(0x2545B8FF) -val ReceivedQuoteColorLight = Color(0x25B1B0B5) - @Composable fun FramedItemView( chatInfo: ChatInfo, @@ -54,6 +49,9 @@ fun FramedItemView( return if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.membership else null } + @Composable + fun Color.toQuote(): Color = if (isInDarkTheme()) lighter(0.12f) else darker(0.12f) + @Composable fun ciQuotedMsgView(qi: CIQuote) { Box( @@ -70,9 +68,11 @@ fun FramedItemView( @Composable fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Row( Modifier - .background(if (sent) SentQuoteColorLight else ReceivedQuoteColorLight) + .background(if (sent) sentColor.toQuote() else receivedColor.toQuote()) .fillMaxWidth() .padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (ci.quotedItem == null) 6.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -87,7 +87,7 @@ fun FramedItemView( } Text( buildAnnotatedString { - withStyle(SpanStyle(fontSize = 12.sp, fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, color = HighOrLowlight)) { + withStyle(SpanStyle(fontSize = 12.sp, fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, color = MaterialTheme.colors.secondary)) { append(caption) } }, @@ -100,9 +100,11 @@ fun FramedItemView( @Composable fun ciQuoteView(qi: CIQuote) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Row( Modifier - .background(if (sent) SentQuoteColorLight else ReceivedQuoteColorLight) + .background(if (sent) sentColor.toQuote() else receivedColor.toQuote()) .fillMaxWidth() .combinedClickable( onLongClick = { showMenu.value = true }, @@ -163,16 +165,18 @@ fun FramedItemView( val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) && !ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Box(Modifier .clip(RoundedCornerShape(18.dp)) .background( when { transparentBackground -> Color.Transparent - sent -> SentColorLight - else -> ReceivedColorLight + sent -> sentColor + else -> receivedColor } )) { - var metaColor = HighOrLowlight + var metaColor = MaterialTheme.colors.secondary Box(contentAlignment = Alignment.BottomEnd) { Column(Modifier.width(IntrinsicSize.Max)) { PriorityLayout(Modifier, CHAT_IMAGE_LAYOUT_ID) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt index fa25e0f88a..e1ddd3b3e7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/IntegrityErrorItemView.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -18,6 +19,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.ChatItem import chat.simplex.app.model.MsgErrorType +import chat.simplex.app.ui.theme.CurrentColors import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.AlertManager import chat.simplex.app.views.helpers.generalGetString @@ -50,10 +52,11 @@ fun IntegrityErrorItemView(msgError: MsgErrorType, ci: ChatItem, timedMessagesTT @Composable fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false, onClick: () -> Unit) { + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Surface( Modifier.clickable(onClick = onClick), shape = RoundedCornerShape(18.dp), - color = ReceivedColorLight, + color = receivedColor, ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt index a63a232939..4bb56b60fa 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/MarkedDeletedItemView.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.* @@ -16,15 +17,16 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.CIDeleted import chat.simplex.app.model.ChatItem -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.generalGetString @Composable fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showMember: Boolean = false) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage Surface( shape = RoundedCornerShape(18.dp), - color = if (ci.chatDir.sent) SentColorLight else ReceivedColorLight, + color = if (ci.chatDir.sent) sentColor else receivedColor, ) { Row( Modifier.padding(horizontal = 12.dp, vertical = 6.dp), @@ -47,7 +49,7 @@ private fun MarkedDeletedText(text: String) { Text( buildAnnotatedString { // appendSender(this, if (showMember) ci.memberDisplayName else null, true) // TODO font size - withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = HighOrLowlight)) { append(text) } + withStyle(SpanStyle(fontSize = 12.sp, fontStyle = FontStyle.Italic, color = MaterialTheme.colors.secondary)) { append(text) } }, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp), modifier = Modifier.padding(end = 8.dp), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index dafc328cf5..de5fd1594e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.unit.* import androidx.core.text.BidiFormatter import chat.simplex.app.TAG import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight +import chat.simplex.app.ui.theme.CurrentColors import chat.simplex.app.views.helpers.detectGesture import kotlinx.coroutines.* @@ -57,7 +57,7 @@ private val typingIndicators: List = listOf( private fun typingIndicator(recent: Boolean, @IntRange (from = 0, to = 4) typingIdx: Int): AnnotatedString = buildAnnotatedString { - pushStyle(SpanStyle(color = HighOrLowlight, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp)) + pushStyle(SpanStyle(color = CurrentColors.value.colors.secondary, fontFamily = FontFamily.Monospace, letterSpacing = (-1).sp)) append(if (recent) typingIndicators[typingIdx] else noTyping) } @@ -228,7 +228,7 @@ fun ClickableText( } } }, shouldConsumeEvent = { pos -> - var consume = false + var consume = false layoutResult.value?.let { layoutResult -> consume = shouldConsumeEvent(layoutResult.getOffsetForPosition(pos)) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt index 921197a6f7..e005ae0c3d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -328,7 +328,7 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: @Composable private fun InvalidDataView() { Row { - ProfileImage(72.dp, null, R.drawable.ic_account_circle_filled, HighOrLowlight) + ProfileImage(72.dp, null, R.drawable.ic_account_circle_filled, MaterialTheme.colors.secondary) Column( modifier = Modifier .padding(horizontal = 8.dp) @@ -466,7 +466,8 @@ fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel onSuccess() } } - } + }, + destructive = true, ) } @@ -484,6 +485,7 @@ fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) { } } }, + destructive = true, dismissText = generalGetString(R.string.cancel_verb), ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 1580a265d2..6104f7e6b0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -84,7 +84,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) hoveredElevation = 0.dp, focusedElevation = 0.dp, ), - backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight, + backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, contentColor = Color.White ) { Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(R.drawable.ic_edit_filled) else painterResource(R.drawable.ic_close), stringResource(R.string.add_contact_or_create_group)) @@ -96,7 +96,6 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colors.background) ) { if (chatModel.chats.isNotEmpty()) { ChatList(chatModel, search = searchInList) @@ -105,7 +104,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) { OnboardingButtons(showNewChatSheet) } - Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight) + Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) } } } @@ -136,7 +135,7 @@ private fun OnboardingButtons(openNewChatSheet: () -> Unit) { } Spacer(Modifier.height(DEFAULT_PADDING)) ConnectButton(generalGetString(R.string.tap_to_start_new_chat), openNewChatSheet) - val color = MaterialTheme.colors.primary + val color = MaterialTheme.colors.primaryVariant Canvas(modifier = Modifier.width(40.dp).height(10.dp), onDraw = { val trianglePath = Path().apply { moveTo(0.dp.toPx(), 0f) @@ -159,7 +158,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { onClick, shape = RoundedCornerShape(21.dp), colors = ButtonDefaults.textButtonColors( - backgroundColor = MaterialTheme.colors.primary + backgroundColor = MaterialTheme.colors.primaryVariant ), elevation = null, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF), @@ -282,7 +281,7 @@ private fun ProgressIndicator() { Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index 1c641f696a..5a4745ced8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -46,7 +46,7 @@ fun ChatPreviewView( painterResource(R.drawable.ic_cancel_filled), stringResource(R.string.icon_descr_group_inactive), Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape), - tint = HighOrLowlight + tint = MaterialTheme.colors.secondary ) } @@ -76,7 +76,7 @@ fun ChatPreviewView( @Composable fun VerifiedIcon() { - Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) } fun messageDraft(draft: ComposeState): Pair> { @@ -110,7 +110,7 @@ fun ChatPreviewView( "attachmentIcon" to InlineTextContent( Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter) ) { - Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(R.drawable.ic_edit_note), null, tint = HighOrLowlight) + Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(R.drawable.ic_edit_note), null, tint = MaterialTheme.colors.secondary) } ) return text to inlineContent @@ -124,12 +124,12 @@ fun ChatPreviewView( if (cInfo.contact.verified) { VerifiedIcon() } - chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else HighOrLowlight) + chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else MaterialTheme.colors.secondary) } is ChatInfo.Group -> when (cInfo.groupInfo.membership.memberStatus) { GroupMemberStatus.MemInvited -> chatPreviewTitleText(if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary) - GroupMemberStatus.MemAccepted -> chatPreviewTitleText(HighOrLowlight) + GroupMemberStatus.MemAccepted -> chatPreviewTitleText(MaterialTheme.colors.secondary) else -> chatPreviewTitleText() } else -> chatPreviewTitleText() @@ -170,12 +170,12 @@ fun ChatPreviewView( when (cInfo) { is ChatInfo.Direct -> if (!cInfo.ready) { - Text(stringResource(R.string.contact_connection_pending), color = HighOrLowlight) + Text(stringResource(R.string.contact_connection_pending), color = MaterialTheme.colors.secondary) } is ChatInfo.Group -> when (cInfo.groupInfo.membership.memberStatus) { GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(chatModelIncognito, currentUserProfileDisplayName, cInfo.groupInfo)) - GroupMemberStatus.MemAccepted -> Text(stringResource(R.string.group_connection_pending), color = HighOrLowlight) + GroupMemberStatus.MemAccepted -> Text(stringResource(R.string.group_connection_pending), color = MaterialTheme.colors.secondary) else -> {} } else -> {} @@ -208,7 +208,7 @@ fun ChatPreviewView( ) { Text( ts, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, modifier = Modifier.padding(bottom = 5.dp) ) @@ -221,10 +221,10 @@ fun ChatPreviewView( ) { Text( if (n > 0) unreadCountStr(n) else "", - color = MaterialTheme.colors.onPrimary, + color = Color.White, fontSize = 11.sp, modifier = Modifier - .background(if (stopped || showNtfsIcon) HighOrLowlight else MaterialTheme.colors.primary, shape = CircleShape) + .background(if (stopped || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape) .badgeLayout() .padding(horizontal = 3.dp) .padding(vertical = 1.dp) @@ -238,7 +238,7 @@ fun ChatPreviewView( Icon( painterResource(R.drawable.ic_notifications_off_filled), contentDescription = generalGetString(R.string.notifications), - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, modifier = Modifier .padding(horizontal = 3.dp) .padding(vertical = 1.dp) @@ -280,7 +280,7 @@ fun ChatStatusImage(s: NetworkStatus?) { Icon( painterResource(R.drawable.ic_error), contentDescription = descr, - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, modifier = Modifier .size(19.dp) ) @@ -289,7 +289,7 @@ fun ChatStatusImage(s: NetworkStatus?) { Modifier .padding(horizontal = 2.dp) .size(15.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 1.5.dp ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt index 9e4cd20cad..de6fe43d50 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt @@ -34,7 +34,7 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) { overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) val height = with(LocalDensity.current) { 46.sp.toDp() } Text(contactConnection.description, Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight) @@ -45,7 +45,7 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) { ) { Text( ts, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, modifier = Modifier.padding(bottom = 5.dp) ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt index 064cb4a66a..936d3d2590 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt @@ -42,7 +42,7 @@ fun ContactRequestView(chatModelIncognito: Boolean, contactRequest: ChatInfo.Con ) { Text( ts, - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, modifier = Modifier.padding(bottom = 5.dp) ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt index f601e65378..1f74929065 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt @@ -19,8 +19,7 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.Indigo +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import kotlinx.coroutines.flow.MutableStateFlow @@ -36,7 +35,6 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colors.background) ) { if (chatModel.chats.isNotEmpty()) { ShareList(chatModel, search = searchInList) @@ -54,7 +52,7 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { @Composable private fun EmptyList() { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text(stringResource(R.string.you_have_no_chats), color = HighOrLowlight) + Text(stringResource(R.string.you_have_no_chats), color = MaterialTheme.colors.secondary) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt index d073d2d2de..31d6e96efa 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/UserPicker.kt @@ -107,7 +107,7 @@ fun UserPicker( .width(IntrinsicSize.Min) .height(IntrinsicSize.Min) .shadow(8.dp, RoundedCornerShape(corner = CornerSize(25.dp)), clip = true) - .background(if (isInDarkTheme()) DarkGrayBackground else MaterialTheme.colors.background, RoundedCornerShape(corner = CornerSize(25.dp))) + .background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp))) .clip(RoundedCornerShape(corner = CornerSize(25.dp))) ) { Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) { @@ -169,7 +169,7 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues if (u.activeUser) { Icon(painterResource(R.drawable.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) } else if (u.hidden) { - Icon(painterResource(R.drawable.ic_lock), null, Modifier.size(20.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) } else if (unreadCount > 0) { Box( contentAlignment = Alignment.Center @@ -179,13 +179,13 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues color = Color.White, fontSize = 11.sp, modifier = Modifier - .background(if (u.showNtfs) MaterialTheme.colors.primary else HighOrLowlight, shape = CircleShape) + .background(MaterialTheme.colors.primaryVariant, shape = CircleShape) .padding(2.dp) .badgeLayout() ) } } else if (!u.showNtfs) { - Icon(painterResource(R.drawable.ic_notifications_off), null, Modifier.size(20.dp), tint = HighOrLowlight) + Icon(painterResource(R.drawable.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) } else { Box(Modifier.size(20.dp)) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt index 5dc5b55faf..6df30f9170 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/database/ChatArchiveView.kt @@ -116,7 +116,8 @@ private fun deleteArchiveAlert(m: ChatModel, archivePath: String) { } else { Log.e(TAG, "deleteArchiveAlert delete() error") } - } + }, + destructive = true, ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt index a2e5993e07..8923962bcd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseEncryptionView.kt @@ -112,7 +112,7 @@ fun DatabaseEncryptionView(m: ChatModel) { Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } @@ -211,7 +211,7 @@ fun DatabaseEncryptionLayout( ) SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) { - Text(generalGetString(R.string.update_database_passphrase), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(generalGetString(R.string.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } @@ -244,7 +244,7 @@ fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) { text = generalGetString(R.string.database_will_be_encrypted_and_passphrase_stored) + "\n" + storeSecurelySaved(), confirmText = generalGetString(R.string.encrypt_database), onConfirm = onConfirm, - destructive = false, + destructive = true, ) } @@ -292,7 +292,7 @@ fun SavePassphraseSetting( Icon( if (storedKey) painterResource(R.drawable.ic_vpn_key_filled) else painterResource(R.drawable.ic_vpn_key_off_filled), stringResource(R.string.save_passphrase_in_keychain), - tint = if (storedKey) SimplexGreen else HighOrLowlight + tint = if (storedKey) SimplexGreen else MaterialTheme.colors.secondary ) Spacer(Modifier.padding(horizontal = 4.dp)) Text( @@ -360,7 +360,7 @@ fun PassphraseField( if (showKey) painterResource(R.drawable.ic_visibility_off_filled) else painterResource(R.drawable.ic_visibility_filled) } else painterResource(R.drawable.ic_error) val iconColor = if (valid) { - if (showStrength && key.value.isNotEmpty()) PassphraseStrength.check(key.value).color else HighOrLowlight + if (showStrength && key.value.isNotEmpty()) PassphraseStrength.check(key.value).color else MaterialTheme.colors.secondary } else Color.Red val keyboard = LocalSoftwareKeyboardController.current val keyboardOptions = KeyboardOptions( @@ -417,7 +417,7 @@ fun PassphraseField( TextFieldDefaults.TextFieldDecorationBox( value = state.value.text, innerTextField = innerTextField, - placeholder = { Text(placeholder, color = HighOrLowlight) }, + placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) }, singleLine = true, enabled = enabled, isError = !valid, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt index 410bdea630..c1aa1fe5a6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseErrorView.kt @@ -180,7 +180,7 @@ fun DatabaseErrorView( Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseView.kt index 8ac6f8cb40..2a74524b3c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/database/DatabaseView.kt @@ -114,7 +114,7 @@ fun DatabaseView( Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } @@ -185,7 +185,7 @@ fun DatabaseLayout( .drawable.ic_lock), stringResource(R.string.database_passphrase), click = showSettingsModal() { DatabaseEncryptionView(it) }, - iconColor = if (unencrypted) WarningOrange else HighOrLowlight, + iconColor = if (unencrypted) WarningOrange else MaterialTheme.colors.secondary, disabled = operationsDisabled ) AppDataBackupPreference(privacyFullBackup, initialRandomDBPassphrase) @@ -250,7 +250,7 @@ fun DatabaseLayout( ) { Text( stringResource(if (users.size > 1) R.string.delete_files_and_media_for_all_users else R.string.delete_files_and_media_all), - color = if (deleteFilesDisabled) HighOrLowlight else Color.Red + color = if (deleteFilesDisabled) MaterialTheme.colors.secondary else Color.Red ) } } @@ -270,7 +270,7 @@ fun DatabaseLayout( private fun AppDataBackupPreference(privacyFullBackup: SharedPreference, initialRandomDBPassphrase: SharedPreference) { SettingsPreferenceItem( painterResource(R.drawable.ic_backup), - iconColor = HighOrLowlight, + iconColor = MaterialTheme.colors.secondary, pref = privacyFullBackup, text = stringResource(R.string.full_backup) ) { @@ -294,7 +294,8 @@ private fun setChatItemTTLAlert( text = generalGetString(R.string.enable_automatic_deletion_message), confirmText = generalGetString(R.string.delete_messages), onConfirm = { setCiTTL(m, selectedChatItemTTL, progressIndicator, appFilesCountAndSize, context) }, - onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value } + onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value }, + destructive = true, ) } @@ -543,7 +544,8 @@ private fun importArchiveAlert( title = generalGetString(R.string.import_database_question), text = generalGetString(R.string.your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one), confirmText = generalGetString(R.string.import_database_confirmation), - onConfirm = { importArchive(m, context, importedArchiveUri, appFilesCountAndSize, progressIndicator) } + onConfirm = { importArchive(m, context, importedArchiveUri, appFilesCountAndSize, progressIndicator) }, + destructive = true, ) } @@ -608,7 +610,8 @@ private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState Unit)? = null + confirmText: String = generalGetString(R.string.ok) ) { showAlert { AlertDialog( @@ -163,23 +160,19 @@ class AlertManager { horizontalArrangement = Arrangement.Center ) { TextButton(onClick = { - onConfirm?.invoke() hideAlert() }) { Text(confirmText, color = Color.Unspecified) } } }, - backgroundColor = if (isInDarkTheme()) DarkGrayBackground else MaterialTheme.colors.background, shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } } - fun showAlertMsg( title: Int, text: Int? = null, confirmText: Int = R.string.ok, - onConfirm: (() -> Unit)? = null - ) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm) + ) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText)) @Composable fun showInView() { @@ -212,7 +205,7 @@ private fun alertText(text: String?): (@Composable () -> Unit)? { Modifier.fillMaxWidth(), textAlign = TextAlign.Center, fontSize = 16.sp, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) }) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 04d2744484..d8463d94a7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -20,7 +20,7 @@ import chat.simplex.app.model.ChatInfo import chat.simplex.app.ui.theme.SimpleXTheme @Composable -fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondary) { +fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { val icon = if (chatInfo is ChatInfo.Group) R.drawable.ic_supervised_user_circle_filled else R.drawable.ic_account_circle_filled @@ -28,7 +28,7 @@ fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme } @Composable -fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondary) { +fun IncognitoImage(size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { Box(Modifier.size(size)) { Icon( painterResource(R.drawable.ic_theater_comedy_filled), stringResource(R.string.incognito), @@ -43,7 +43,7 @@ fun ProfileImage( size: Dp, image: String? = null, icon: Int = R.drawable.ic_account_circle_filled, - color: Color = MaterialTheme.colors.secondary + color: Color = MaterialTheme.colors.secondaryVariant ) { Box(Modifier.size(size)) { if (image == null) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt index 1c9cb170d0..6ee9b62a05 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt @@ -3,9 +3,12 @@ package chat.simplex.app.views.helpers import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -41,14 +44,20 @@ fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> U @Composable fun AppBarTitle(title: String, withPadding: Boolean = true) { + val theme = CurrentColors.collectAsState() + val titleColor = CurrentColors.collectAsState().value.appColors.title + val brush = if (theme.value.base == DefaultTheme.SIMPLEX) + Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) + else // color is not updated when changing themes if I pass null here + Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) Text( title, Modifier .fillMaxWidth() .padding(bottom = DEFAULT_PADDING * 1.5f, start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp,), overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1, - color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.h1.copy(brush = brush), + color = MaterialTheme.colors.primaryVariant, textAlign = TextAlign.Center ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt index e3d4048cfa..2ea147014f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultBasicTextField.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.text.input.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.database.PassphraseStrength import chat.simplex.app.views.database.validKey import kotlinx.coroutines.delay @@ -137,7 +136,7 @@ fun DefaultConfigurableTextField( if (showKey) painterResource(R.drawable.ic_visibility_off_filled) else painterResource(R.drawable.ic_visibility_filled) } else painterResource(R.drawable.ic_error) val iconColor = if (valid) { - if (showPasswordStrength && state.value.text.isNotEmpty()) PassphraseStrength.check(state.value.text).color else HighOrLowlight + if (showPasswordStrength && state.value.text.isNotEmpty()) PassphraseStrength.check(state.value.text).color else MaterialTheme.colors.secondary } else Color.Red val keyboard = LocalSoftwareKeyboardController.current val keyboardOptions = KeyboardOptions( @@ -189,7 +188,7 @@ fun DefaultConfigurableTextField( TextFieldDefaults.TextFieldDecorationBox( value = state.value.text, innerTextField = innerTextField, - placeholder = { Text(placeholder, color = HighOrLowlight) }, + placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) }, singleLine = true, enabled = enabled, isError = !valid, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt index e6ec87e14d..71af738bda 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultDropdownMenu.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.helpers +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -19,7 +20,6 @@ fun DefaultDropdownMenu( dropdownMenuItems: (@Composable () -> Unit)? ) { MaterialTheme( - colors = MaterialTheme.colors.copy(surface = if (isInDarkTheme()) Color(0xFF0B0B0B) else MaterialTheme.colors.background), shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp))) ) { DropdownMenu( @@ -27,6 +27,7 @@ fun DefaultDropdownMenu( onDismissRequest = { showMenu.value = false }, Modifier .widthIn(min = 250.dp) + .background(MaterialTheme.colors.surface) .padding(vertical = 4.dp), offset = offset, ) { @@ -42,11 +43,13 @@ fun ExposedDropdownMenuBoxScope.DefaultExposedDropdownMenu( dropdownMenuItems: (@Composable () -> Unit)? ) { MaterialTheme( - colors = MaterialTheme.colors.copy(surface = if (isInDarkTheme()) Color(0xFF080808) else MaterialTheme.colors.background), shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(corner = CornerSize(25.dp))) ) { ExposedDropdownMenu( - modifier = Modifier.widthIn(min = 200.dp).then(modifier), + modifier = Modifier + .widthIn(min = 200.dp) + .background(MaterialTheme.colors.surface) + .then(modifier), expanded = expanded.value, onDismissRequest = { expanded.value = false diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt index 251cb8df30..613caed54d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultSwitch.kt @@ -9,7 +9,6 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.* import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp -import chat.simplex.app.ui.theme.HighOrLowlight @Composable fun DefaultSwitch( @@ -20,12 +19,12 @@ fun DefaultSwitch( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, colors: SwitchColors = SwitchDefaults.colors( checkedThumbColor = MaterialTheme.colors.primary, - uncheckedThumbColor = HighOrLowlight, + uncheckedThumbColor = MaterialTheme.colors.secondary, checkedTrackAlpha = 0.0f, uncheckedTrackAlpha = 0.0f, ) ) { - val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else HighOrLowlight.copy(alpha = 0.3f) + val color = if (checked) MaterialTheme.colors.primary.copy(alpha = 0.3f) else MaterialTheme.colors.secondary.copy(alpha = 0.3f) val size = with(LocalDensity.current) { Size(46.dp.toPx(), 28.dp.toPx()) } val offset = with(LocalDensity.current) { Offset(1.dp.toPx(), 10.dp.toPx()) } val radius = with(LocalDensity.current) { 28.dp.toPx() } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt index 225dd393ba..9896b0302e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt @@ -47,7 +47,7 @@ fun DefaultTopAppBar( fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) { IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) { Icon( - painterResource(R.drawable.ic_arrow_back_ios_new), stringResource(R.string.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else HighOrLowlight + painterResource(R.drawable.ic_arrow_back_ios_new), stringResource(R.string.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt index 7fa87a482c..b39b36e365 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ExposedDropDownSettingRow.kt @@ -23,7 +23,7 @@ fun ExposedDropDownSettingRow( selection: State, label: String? = null, icon: Painter? = null, - iconTint: Color = HighOrLowlight, + iconTint: Color = MaterialTheme.colors.secondary, enabled: State = mutableStateOf(true), onSelected: (T) -> Unit ) { @@ -46,13 +46,13 @@ fun ExposedDropDownSettingRow( Modifier.widthIn(max = maxWidth), maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) Spacer(Modifier.size(12.dp)) Icon( if (!expanded.value) painterResource(R.drawable.ic_expand_more) else painterResource(R.drawable.ic_expand_less), generalGetString(R.string.icon_descr_more_button), - tint = HighOrLowlight + tint = MaterialTheme.colors.secondary ) } DefaultExposedDropdownMenu( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt index 41d6c73190..463d0eacbd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt @@ -80,10 +80,16 @@ suspend fun PointerInputScope.detectGesture( pressScope.release() } } catch (_: PointerEventTimeoutCancellationException) { - onLongPress?.invoke(down.position) - if (shouldConsume) - consumeUntilUp() - pressScope.release() + if (onLongPress != null) { + onLongPress(down.position) + if (shouldConsume) + consumeUntilUp() + pressScope.cancel() + } else { + if (shouldConsume) + consumeUntilUp() + pressScope.release() + } } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt index 7624665036..f6c749b554 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/LinkPreviews.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap @@ -19,9 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.LinkPreview -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme -import chat.simplex.app.views.chat.item.SentColorLight +import chat.simplex.app.ui.theme.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jsoup.Jsoup @@ -79,8 +78,9 @@ suspend fun getLinkPreview(url: String): LinkPreview? { @Composable fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) { + val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage Row( - Modifier.fillMaxWidth().padding(top = 8.dp).background(SentColorLight), + Modifier.fillMaxWidth().padding(top = 8.dp).background(sentColor), verticalAlignment = Alignment.CenterVertically ) { if (linkPreview == null) { @@ -90,7 +90,7 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit) { ) { CircularProgressIndicator( Modifier.size(16.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.dp ) } @@ -134,7 +134,7 @@ fun ChatItemLinkView(linkPreview: LinkPreview) { if (linkPreview.description != "") { Text(linkPreview.description, maxLines = 12, overflow = TextOverflow.Ellipsis, fontSize = 14.sp, lineHeight = 20.sp) } - Text(linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 12.sp, color = HighOrLowlight) + Text(linkPreview.uri, maxLines = 1, overflow = TextOverflow.Ellipsis, fontSize = 12.sp, color = MaterialTheme.colors.secondary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt index 7e834806a1..3ba958a540 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.app.TAG import chat.simplex.app.ui.theme.isInDarkTheme +import chat.simplex.app.ui.theme.themedBackground import java.util.concurrent.atomic.AtomicBoolean @Composable @@ -24,7 +25,7 @@ fun ModalView( ) { BackHandler(onBack = close) Surface(Modifier.fillMaxSize()) { - Column(Modifier.background(background)) { + Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) { CloseSheetBar(close, endButtons) Box(modifier) { content() } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt index 2a50888e8e..38b00dadee 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Section.kt @@ -25,7 +25,7 @@ fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), Column { if (title != null) { Text( - title, color = HighOrLowlight, style = MaterialTheme.typography.body2, + title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp ) } @@ -37,7 +37,7 @@ fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), fun SectionView( title: String, icon: Painter, - iconTint: Color = HighOrLowlight, + iconTint: Color = MaterialTheme.colors.secondary, leadingIcon: Boolean = false, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit) @@ -46,7 +46,7 @@ fun SectionView( val iconSize = with(LocalDensity.current) { 21.sp.toDp() } Row(Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) { if (leadingIcon) Icon(icon, null, Modifier.padding(end = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint) - Text(title, color = HighOrLowlight, style = MaterialTheme.typography.body2, fontSize = 12.sp) + Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 12.sp) if (!leadingIcon) Icon(icon, null, Modifier.padding(start = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint) } Column(Modifier.padding(padding).fillMaxWidth()) { content() } @@ -147,7 +147,7 @@ fun SectionItemWithValue( values: List>, label: String? = null, icon: Painter? = null, - iconTint: Color = HighOrLowlight, + iconTint: Color = MaterialTheme.colors.secondary, enabled: State = mutableStateOf(true), onSelected: () -> Unit ) { @@ -161,7 +161,7 @@ fun SectionItemWithValue( values.first { it.value == currentValue.value }.title + (if (label != null) " $label" else ""), maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } @@ -177,7 +177,7 @@ fun SectionTextFooter(text: AnnotatedString) { Text( text, Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, lineHeight = 18.sp, fontSize = 14.sp ) @@ -228,10 +228,10 @@ fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color SectionItemViewSpaceBetween { Row { val iconSize = with(LocalDensity.current) { 21.sp.toDp() } - if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: HighOrLowlight) + if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: MaterialTheme.colors.secondary) Text(title) } - Text(value, color = HighOrLowlight) + Text(value, color = MaterialTheme.colors.secondary) } } @@ -247,7 +247,7 @@ fun InfoRowEllipsis(title: String, value: String, onClick: () -> Unit) { .widthIn(max = (configuration.screenWidthDp / 2).dp), maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt index 9f32850e95..779f51d9e3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt @@ -1,14 +1,15 @@ package chat.simplex.app.ui.theme +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.ui.res.painterResource import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.font.FontWeight @@ -54,10 +55,10 @@ fun SimpleButton( ) { SimpleButtonFrame(click, disabled = disabled) { Icon( - icon, text, tint = if (disabled) HighOrLowlight else color, + icon, text, tint = if (disabled) MaterialTheme.colors.secondary else color, modifier = Modifier.padding(end = 8.dp) ) - Text(text, style = MaterialTheme.typography.caption, color = if (disabled) HighOrLowlight else color) + Text(text, style = MaterialTheme.typography.caption, color = if (disabled) MaterialTheme.colors.secondary else color) } } @@ -79,7 +80,7 @@ fun SimpleButtonIconEnded( @Composable fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled: Boolean = false, content: @Composable () -> Unit) { - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { val modifier = if (disabled) modifier else modifier.clickable { click() } Row( verticalAlignment = Alignment.CenterVertically, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt index 71f9874eed..e00f9ba454 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/TextEditor.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.* import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.HighOrLowlight @Composable fun TextEditor( @@ -41,11 +40,11 @@ fun TextEditor( autoCorrect = false ), modifier = modifier, - cursorBrush = SolidColor(HighOrLowlight), + cursorBrush = SolidColor(MaterialTheme.colors.secondary), decorationBox = { innerTextField -> Surface( shape = if (border) RoundedCornerShape(10.dp) else RectangleShape, - border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondary) else null + border = if (border) BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant) else null ) { Row( Modifier.background(background), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index 0a274d20b9..39f7d88932 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -37,6 +37,8 @@ import androidx.core.text.HtmlCompat import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.* +import chat.simplex.app.ui.theme.ThemeOverrides +import com.charleskorn.kaml.decodeFromStream import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString @@ -388,6 +390,22 @@ fun getDrawableFromUri(uri: Uri, withAlertOnException: Boolean = true): Drawable } } +fun getThemeFromUri(uri: Uri, withAlertOnException: Boolean = true): ThemeOverrides? { + SimplexApp.context.contentResolver.openInputStream(uri).use { + runCatching { + return yaml.decodeFromStream(it!!) + }.onFailure { + if (withAlertOnException) { + AlertManager.shared.showAlertMsg( + title = generalGetString(R.string.import_theme_error), + text = generalGetString(R.string.import_theme_error_desc), + ) + } + } + } + return null +} + fun saveImage(context: Context, uri: Uri): String? { val bitmap = getBitmapFromUri(uri) ?: return null return saveImage(context, bitmap) @@ -566,6 +584,9 @@ fun getBitmapFromVideo(uri: Uri, timestamp: Long? = null, random: Boolean = true fun Color.darker(factor: Float = 0.1f): Color = Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha) +fun Color.lighter(factor: Float = 0.1f): Color = + Color(min(red * (1 + factor), 1f), min(green * (1 + factor), 1f), min(blue * (1 + factor), 1f), alpha) + fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT) fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt b/apps/android/app/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt index 20ee2fbcde..0f6f063082 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/localauth/PasswordEntry.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.* -import chat.simplex.app.ui.theme.HighOrLowlight @Composable fun PasscodeEntry( @@ -129,7 +128,7 @@ private fun PasswordDigit(size: Dp, d: Int, password: MutableState) { fontSize = 30.sp, letterSpacing = (-0.5).sp ), - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } @@ -137,7 +136,7 @@ private fun PasswordDigit(size: Dp, d: Int, password: MutableState) { @Composable private fun PasswordEdit(size: Dp, image: Painter, action: () -> Unit) { PasswordButton(size, action) { - Icon(image, null, tint = HighOrLowlight) + Icon(image, null, tint = MaterialTheme.colors.secondary) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index b307ea62d9..5ad769adb6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -63,7 +63,7 @@ fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit) .size(36.dp) .padding(4.dp) .align(Alignment.CenterHorizontally), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 3.dp ) } @@ -113,7 +113,7 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean Icon( painterResource(R.drawable.ic_info), stringResource(R.string.incognito), - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, modifier = Modifier.padding(end = 10.dp).size(20.dp) ) Text(offText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt index d8acc440bf..b166d07bf1 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt @@ -141,7 +141,7 @@ fun AddGroupLayout(createGroup: (GroupProfile) -> Unit, close: () -> Unit) { } .padding(8.dp)) } else { - CreateGroupButton(HighOrLowlight, Modifier.padding(8.dp)) + CreateGroupButton(MaterialTheme.colors.secondary, Modifier.padding(8.dp)) } LaunchedEffect(Unit) { delay(300) @@ -159,7 +159,7 @@ fun CreateGroupButton(color: Color, modifier: Modifier) { Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End ) { - Surface(shape = RoundedCornerShape(20.dp)) { + Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent) { Row(modifier, verticalAlignment = Alignment.CenterVertically) { Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = color, fontWeight = FontWeight.Bold) Icon(painterResource(R.drawable.ic_arrow_forward_ios), stringResource(R.string.create_profile_button), tint = color) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt index ba9c8fc25d..18a149eb52 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectViaLinkView.kt @@ -4,12 +4,12 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.ChatModel -import chat.simplex.app.ui.theme.HighOrLowlight enum class ConnectViaLinkTab { SCAN, PASTE @@ -44,7 +44,7 @@ fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { } TabRow( selectedTabIndex = selection.value.ordinal, - backgroundColor = MaterialTheme.colors.background, + backgroundColor = Color.Transparent, contentColor = MaterialTheme.colors.primary, ) { tabTitles.forEachIndexed { index, it -> @@ -62,7 +62,7 @@ fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { ) }, selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = HighOrLowlight, + unselectedContentColor = MaterialTheme.colors.secondary, ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt index 6e4c6849d4..90e58a6986 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/CreateLinkView.kt @@ -5,13 +5,13 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.DEFAULT_PADDING -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.helpers.ModalManager import chat.simplex.app.views.helpers.withApi import chat.simplex.app.views.usersettings.UserAddressView @@ -67,7 +67,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { } TabRow( selectedTabIndex = selection.value.ordinal, - backgroundColor = MaterialTheme.colors.background, + backgroundColor = Color.Transparent, contentColor = MaterialTheme.colors.primary, ) { tabTitles.forEachIndexed { index, it -> @@ -84,7 +84,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { ) }, selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = HighOrLowlight, + unselectedContentColor = MaterialTheme.colors.secondary, ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt index c0214eb3cb..0808229e69 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt @@ -150,7 +150,7 @@ private fun NewChatSheetLayout( hoveredElevation = 0.dp, focusedElevation = 0.dp, ), - backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight, + backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, contentColor = Color.White ) { Icon( @@ -173,14 +173,14 @@ fun ActionButton( disabled: Boolean = false, click: () -> Unit = {} ) { - Surface(shape = RoundedCornerShape(18.dp)) { + Surface(shape = RoundedCornerShape(18.dp), color = Color.Transparent) { Column( Modifier .clickable(onClick = click) .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - val tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary + val tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary Icon( icon, text, tint = tint, @@ -226,7 +226,7 @@ fun ActionButton( .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - val tint = if (disabled) HighOrLowlight else tint + val tint = if (disabled) MaterialTheme.colors.secondary else tint Icon( icon, text, tint = tint, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt index c69f9c18fb..19a35a0f6d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt @@ -45,7 +45,7 @@ fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) { } } if (linkType == ConnectionLinkType.GROUP) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.connect_via_group_link), text = generalGetString(R.string.you_will_join_group), confirmText = generalGetString(R.string.connect_via_link_verb), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt index 4f6e4c41d0..d1210c8e16 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt @@ -48,7 +48,7 @@ fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { } } if (linkType == ConnectionLinkType.GROUP) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.connect_via_group_link), text = generalGetString(R.string.you_will_join_group), confirmText = generalGetString(R.string.connect_via_link_verb), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt index ada3de9dad..bd8e30186f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/OnboardingView.kt @@ -31,7 +31,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { Box( modifier = Modifier .fillMaxSize() - .background(color = MaterialTheme.colors.background) .padding(20.dp) ) { CreateProfilePanel(chatModel, close) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt index 774a962c42..a0d2c66b11 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt @@ -53,7 +53,7 @@ fun SetNotificationsMode(m: ChatModel) { private fun NotificationButton(currentMode: MutableState, mode: NotificationsMode, @StringRes title: Int, @StringRes description: Int) { TextButton( onClick = { currentMode.value = mode }, - border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight.copy(alpha = 0.5f)), + border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), shape = RoundedCornerShape(35.dp), ) { Column(Modifier.padding(horizontal = 14.dp).padding(top = 4.dp, bottom = 8.dp)) { @@ -61,7 +61,7 @@ private fun NotificationButton(currentMode: MutableState, mod stringResource(title), style = MaterialTheme.typography.h2, fontWeight = FontWeight.Medium, - color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight, + color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), textAlign = TextAlign.Center ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt index a3acb2e34b..1e7c8d6b22 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/WhatsNewView.kt @@ -1,6 +1,7 @@ package chat.simplex.app.views.onboarding import android.content.res.Configuration +import android.os.Build import androidx.annotation.IntegerRes import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -9,6 +10,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource @@ -18,6 +20,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import chat.simplex.app.BuildConfig import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.* @@ -47,7 +50,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 4.dp) ) { - Icon(icon, stringResource(titleId), tint = HighOrLowlight) + Icon(icon, stringResource(titleId), tint = MaterialTheme.colors.secondary) Text( generalGetString(titleId), maxLines = 1, @@ -71,7 +74,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ) { if (currentVersion.value > 0) { val prev = currentVersion.value - 1 - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -87,7 +90,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { Spacer(Modifier.fillMaxWidth().weight(1f)) if (currentVersion.value < versionDescriptions.lastIndex) { val next = currentVersion.value + 1 - Surface(shape = RoundedCornerShape(20.dp)) { + Box(Modifier.clip(RoundedCornerShape(20.dp))) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -113,17 +116,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { - Text( - String.format(generalGetString(R.string.new_in_version), v.version), - Modifier - .fillMaxWidth() - .padding(DEFAULT_PADDING), - textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1, - fontWeight = FontWeight.Normal, - color = HighOrLowlight - ) + AppBarTitle(String.format(generalGetString(R.string.new_in_version), v.version)) v.features.forEach { feature -> featureDescription(painterResource(feature.icon), feature.titleId, feature.descrId, feature.link) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt index 4c5ff2fd85..c383370042 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AcceptRequestsView.kt @@ -60,7 +60,7 @@ private fun AcceptRequestsLayout( PreferenceToggleWithIcon( stringResource(R.string.incognito), if (autoAcceptState.value.incognito) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_theater_comedy), - if (autoAcceptState.value.incognito) Indigo else HighOrLowlight, + if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary, autoAcceptState.value.incognito, ) { autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.welcomeText) @@ -81,7 +81,7 @@ private fun AcceptRequestsLayout( Spacer(Modifier.height(DEFAULT_PADDING)) if (autoAcceptState.value.enable) { Text( - stringResource(R.string.section_title_welcome_message), color = HighOrLowlight, style = MaterialTheme.typography.body2, + stringResource(R.string.section_title_welcome_message), color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, modifier = Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), fontSize = 12.sp ) TextEditor(Modifier.padding(horizontal = DEFAULT_PADDING).height(160.dp), text = welcomeText) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt index a8841e824c..044260ac83 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/AdvancedNetworkSettings.kt @@ -184,13 +184,13 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { } } else { SectionItemView { - Text("TCP_KEEPIDLE", color = HighOrLowlight) + Text("TCP_KEEPIDLE", color = MaterialTheme.colors.secondary) } SectionItemView { - Text("TCP_KEEPINTVL", color = HighOrLowlight) + Text("TCP_KEEPINTVL", color = MaterialTheme.colors.secondary) } SectionItemView { - Text("TCP_KEEPCNT", color = HighOrLowlight) + Text("TCP_KEEPCNT", color = MaterialTheme.colors.secondary) } } } @@ -208,7 +208,7 @@ fun ResetToDefaultsButton(reset: () -> Unit, disabled: Boolean) { modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically ) { - val color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary + val color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary Text(stringResource(R.string.network_options_reset_to_defaults), color = color) } } @@ -256,14 +256,14 @@ fun IntSettingRow(title: String, selection: MutableState, values: List "${selection.value} $label", maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) Spacer(Modifier.size(4.dp)) Icon( if (!expanded.value) painterResource(R.drawable.ic_expand_more) else painterResource(R.drawable.ic_expand_less), generalGetString(R.string.invite_to_group_button), modifier = Modifier.padding(start = 8.dp), - tint = HighOrLowlight + tint = MaterialTheme.colors.secondary ) } DefaultExposedDropdownMenu( @@ -317,14 +317,14 @@ fun TimeoutSettingRow(title: String, selection: MutableState, values: List "${df.format(selection.value / 1_000_000.toDouble())} $label", maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) Spacer(Modifier.size(4.dp)) Icon( if (!expanded.value) painterResource(R.drawable.ic_expand_more) else painterResource(R.drawable.ic_expand_less), generalGetString(R.string.invite_to_group_button), modifier = Modifier.padding(start = 8.dp), - tint = HighOrLowlight + tint = MaterialTheme.colors.secondary ) } DefaultExposedDropdownMenu( @@ -376,18 +376,18 @@ fun FooterButton(icon: Painter, title: String, action: () -> Unit, disabled: Boo Icon( icon, title, - tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary + tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) Text( title, - color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary + color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } } fun showUpdateNetworkSettingsDialog(action: () -> Unit) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.update_network_settings_question), text = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers), confirmText = generalGetString(R.string.update_network_settings_confirmation), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt index ae84518bfd..961765c13a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Appearance.kt @@ -1,16 +1,23 @@ package chat.simplex.app.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced +import SectionItemView import SectionItemViewSpaceBetween import SectionSpacer import SectionView import android.app.Activity import android.content.ComponentName +import android.content.Context import android.content.pm.PackageManager import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED +import android.net.Uri +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -32,13 +39,15 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import chat.simplex.app.* import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.model.SharedPreference +import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* import com.godaddy.android.colorpicker.* import kotlinx.coroutines.delay +import kotlinx.serialization.encodeToString +import java.io.BufferedOutputStream import java.util.* +import kotlin.collections.ArrayList enum class AppIcon(val resId: Int) { DEFAULT(R.mipmap.icon), @@ -46,7 +55,7 @@ enum class AppIcon(val resId: Int) { } @Composable -fun AppearanceView(m: ChatModel) { +fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { val appIcon = remember { mutableStateOf(findEnabledIcon()) } fun setAppIcon(newIcon: AppIcon) { @@ -69,10 +78,12 @@ fun AppearanceView(m: ChatModel) { AppearanceLayout( appIcon, m.controller.appPrefs.appLanguage, + m.controller.appPrefs.systemDarkTheme, changeIcon = ::setAppIcon, - editPrimaryColor = { primary -> + showSettingsModal = showSettingsModal, + editColor = { name, initialColor -> ModalManager.shared.showModalCloseable { close -> - ColorEditor(primary, close) + ColorEditor(name, initialColor, close) } }, ) @@ -81,8 +92,10 @@ fun AppearanceView(m: ChatModel) { @Composable fun AppearanceLayout( icon: MutableState, languagePref: SharedPreference, + systemDarkTheme: SharedPreference, changeIcon: (AppIcon) -> Unit, - editPrimaryColor: (Color) -> Unit, + showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + editColor: (ThemeColor, Color) -> Unit, ) { Column( Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), @@ -127,7 +140,7 @@ fun AppearanceView(m: ChatModel) { contentDescription = "", contentScale = ContentScale.Fit, modifier = Modifier - .shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondary) + .shadow(if (item == icon.value) 1.dp else 0.dp, ambientColor = colors.secondaryVariant) .size(70.dp) .clickable { changeIcon(item) } .padding(10.dp) @@ -144,26 +157,108 @@ fun AppearanceView(m: ChatModel) { val currentTheme by CurrentColors.collectAsState() SectionView(stringResource(R.string.settings_section_title_themes)) { val darkTheme = isSystemInDarkTheme() - val state = remember { derivedStateOf { currentTheme.second } } + val state = remember { derivedStateOf { currentTheme.name } } ThemeSelector(state) { - ThemeManager.applyTheme(it.name, darkTheme) + ThemeManager.applyTheme(it, darkTheme) } - SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }) { + if (state.value == DefaultTheme.SYSTEM.name) { + DarkThemeSelector(remember { systemDarkTheme.state }) { + ThemeManager.changeDarkTheme(it, darkTheme) + } + } + } + SectionItemView(showSettingsModal { _ -> CustomizeThemeView(editColor) }) { Text(stringResource(R.string.customize_theme_title)) } + SectionBottomSpacer() + } +} + +@Composable +fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { + Column( + Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ) { + val currentTheme by CurrentColors.collectAsState() + + AppBarTitle(stringResource(R.string.customize_theme_title)) + + SectionView(stringResource(R.string.theme_colors_section_title)) { + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { val title = generalGetString(R.string.color_primary) Text(title) Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.primary) } + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) { + val title = generalGetString(R.string.color_primary_variant) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.primaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) { + val title = generalGetString(R.string.color_secondary) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.secondary) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) { + val title = generalGetString(R.string.color_secondary_variant) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.secondaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) { + val title = generalGetString(R.string.color_background) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.background) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) { + val title = generalGetString(R.string.color_surface) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = colors.surface) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) { + val title = generalGetString(R.string.color_title) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = currentTheme.appColors.title) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) { + val title = generalGetString(R.string.color_sent_message) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) { + val title = generalGetString(R.string.color_received_message) + Text(title) + Icon(painterResource(R.drawable.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage) + } } - if (currentTheme.first.primary != LightColorPalette.primary) { - SectionCustomFooter(PaddingValues(start = 7.dp, end = 7.dp, top = 5.dp)) { - TextButton( - onClick = { - ThemeManager.saveAndApplyPrimaryColor(LightColorPalette.primary) - }, - ) { - Text(generalGetString(R.string.reset_color)) + val isInDarkTheme = isInDarkTheme() + if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors)) { + SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) { + Text(generalGetString(R.string.reset_color), color = colors.primary) + } + } + SectionSpacer() + SectionView { + val context = LocalContext.current + val theme = remember { mutableStateOf(null as String?) } + val exportThemeLauncher = rememberSaveThemeLauncher(context, theme) + SectionItemView({ + val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) + theme.value = yaml.encodeToString(overrides) + exportThemeLauncher.launch("simplex.theme") + }) { + Text(generalGetString(R.string.export_theme), color = colors.primary) + } + + val importThemeLauncher = rememberGetContentLauncher { uri: Uri? -> + if (uri != null) { + val theme = getThemeFromUri(uri) + if (theme != null) { + ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme) + } } } + // Can not limit to YAML mime type since it's unsupported by Android + SectionItemView({ importThemeLauncher.launch("*/*") }) { + Text(generalGetString(R.string.import_theme), color = colors.primary) + } } SectionBottomSpacer() } @@ -171,6 +266,7 @@ fun AppearanceView(m: ChatModel) { @Composable fun ColorEditor( + name: ThemeColor, initialColor: Color, close: () -> Unit, ) { @@ -178,17 +274,17 @@ fun ColorEditor( Modifier .fillMaxWidth() ) { - AppBarTitle(stringResource(R.string.color_primary)) + AppBarTitle(name.text) var currentColor by remember { mutableStateOf(initialColor) } ColorPicker(initialColor) { currentColor = it } SectionSpacer() - + val isInDarkTheme = isInDarkTheme() TextButton( onClick = { - ThemeManager.saveAndApplyPrimaryColor(currentColor) + ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) close() }, Modifier.align(Alignment.CenterHorizontally), @@ -206,7 +302,7 @@ fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { modifier = Modifier .fillMaxWidth() .height(300.dp), - showAlphaBar = false, + showAlphaBar = true, onColorChanged = { color: HsvColor -> onColorChanged(color.toColor()) } @@ -241,9 +337,9 @@ private fun LangSelector(state: State, onSelected: (String) -> Unit) { } @Composable -private fun ThemeSelector(state: State, onSelected: (DefaultTheme) -> Unit) { +private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { val darkTheme = isSystemInDarkTheme() - val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second to it.third }) } + val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) } ExposedDropDownSettingRow( generalGetString(R.string.theme), values, @@ -254,10 +350,55 @@ private fun ThemeSelector(state: State, onSelected: (DefaultTheme) ) } +@Composable +private fun DarkThemeSelector(state: State, onSelected: (String) -> Unit) { + val values by remember { + val darkThemes = ArrayList>() + darkThemes.add(DefaultTheme.DARK.name to generalGetString(R.string.theme_dark)) + darkThemes.add(DefaultTheme.SIMPLEX.name to generalGetString(R.string.theme_simplex)) + mutableStateOf(darkThemes.toList()) + } + ExposedDropDownSettingRow( + generalGetString(R.string.dark_theme), + values, + state, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { if (it != null) onSelected(it) } + ) +} + //private fun openSystemLangPicker(activity: Activity) { // activity.startActivity(Intent(Settings.ACTION_APP_LOCALE_SETTINGS, Uri.parse("package:" + SimplexApp.context.packageName))) //} +@Composable +private fun rememberSaveThemeLauncher(cxt: Context, theme: MutableState): ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument(), + onResult = { destination -> + try { + destination?.let { + val theme = theme.value + if (theme != null) { + val contentResolver = cxt.contentResolver + contentResolver.openOutputStream(destination)?.let { stream -> + BufferedOutputStream(stream).use { outputStream -> + theme.byteInputStream().use { it.copyTo(outputStream) } + } + Toast.makeText(cxt, generalGetString(R.string.file_saved), Toast.LENGTH_SHORT).show() + } + } + } + } catch (e: Error) { + Toast.makeText(cxt, generalGetString(R.string.error_saving_file), Toast.LENGTH_SHORT).show() + Log.e(TAG, "rememberSaveThemeLauncher error saving theme $e") + } finally { + theme.value = null + } + } + ) + private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon -> SimplexApp.context.packageManager.getComponentEnabledSetting( ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}") @@ -271,8 +412,10 @@ fun PreviewAppearanceSettings() { AppearanceLayout( icon = remember { mutableStateOf(AppIcon.DARK_BLUE) }, languagePref = SharedPreference({ null }, {}), + systemDarkTheme = SharedPreference({ null }, {}), changeIcon = {}, - editPrimaryColor = {}, + showSettingsModal = { {} }, + editColor = { _, _ -> }, ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt index e487276231..997a9f9e54 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/CallSettings.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.views.helpers.* @Composable @@ -108,7 +107,7 @@ fun SharedPreferenceToggleWithIcon( ) { val prefState = preferenceState ?: remember { mutableStateOf(preference.get()) } Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - Text(text, Modifier.padding(end = 4.dp), color = if (stopped) HighOrLowlight else Color.Unspecified) + Text(text, Modifier.padding(end = 4.dp), color = if (stopped) MaterialTheme.colors.secondary else Color.Unspecified) Icon( icon, null, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt index 75017d8478..676cbd3600 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/HiddenProfileView.kt @@ -77,7 +77,7 @@ private fun HiddenProfileLayout( PassphraseField(confirmHidePassword, stringResource(R.string.confirm_password), isValid = { confirmValid }, dependsOn = hidePassword) } SectionItemViewSpaceBetween({ saveProfilePassword(hidePassword.value) }, disabled = saveDisabled, minHeight = TextFieldDefaults.MinHeight) { - Text(generalGetString(R.string.save_profile_password), color = if (saveDisabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(generalGetString(R.string.save_profile_password), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } SectionTextFooter(stringResource(R.string.to_reveal_profile_enter_password)) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt index fce9aa3894..c20e4eb0be 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NetworkAndServers.kt @@ -59,7 +59,7 @@ fun NetworkAndServersView( showCustomModal = showCustomModal, toggleSocksProxy = { enable -> if (enable) { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.network_enable_socks), text = generalGetString(R.string.network_enable_socks_info), confirmText = generalGetString(R.string.confirm_verb), @@ -73,7 +73,7 @@ fun NetworkAndServersView( } ) } else { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.network_disable_socks), text = generalGetString(R.string.network_disable_socks_info), confirmText = generalGetString(R.string.confirm_verb), @@ -207,7 +207,7 @@ fun UseSocksProxySwitch( Icon( painterResource(R.drawable.ic_settings_ethernet), stringResource(R.string.network_socks_toggle), - tint = HighOrLowlight + tint = MaterialTheme.colors.secondary ) TextIconSpaced(false) if (networkUseSocksProxy.value) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt index 6d3190ab53..e80283ecba 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/NotificationsSettingsView.kt @@ -88,7 +88,7 @@ fun NotificationsSettingsLayout( modes.first { it.value == notificationsMode.value }.title, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } SettingsActionItemWithContent(null, stringResource(R.string.settings_notification_preview_mode_title), { showPage(CurrentPage.NOTIFICATION_PREVIEW_MODE) }) { @@ -96,7 +96,7 @@ fun NotificationsSettingsLayout( previewModes.first { it.value == notificationPreviewMode.value }.title, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt index a8fe451c5e..ad0537331b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt @@ -112,7 +112,7 @@ private fun TimedMessagesFeatureSection(allowFeature: State, onS PreferenceToggleWithIcon( ChatFeature.TimedMessages.text, ChatFeature.TimedMessages.icon, - HighOrLowlight, + MaterialTheme.colors.secondary, allowFeature.value == FeatureAllowed.ALWAYS || allowFeature.value == FeatureAllowed.YES, extraPadding = false, onSelected @@ -125,10 +125,10 @@ private fun TimedMessagesFeatureSection(allowFeature: State, onS private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) { SectionView { SectionItemView(reset, disabled = disabled) { - Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView(save, disabled = disabled) { - Text(stringResource(R.string.save_and_notify_contacts), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary) + Text(stringResource(R.string.save_and_notify_contacts), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt index 9611e6d900..dc960dfd8b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServerView.kt @@ -60,7 +60,7 @@ fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerPr Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } @@ -107,7 +107,7 @@ private fun PresetServer( Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp), style = TextStyle( fontFamily = FontFamily.Monospace, fontSize = 16.sp, - color = HighOrLowlight + color = MaterialTheme.colors.secondary ) ) } @@ -172,13 +172,13 @@ private fun UseServerSection( ) { SectionView(stringResource(R.string.smp_servers_use_server).uppercase()) { SectionItemViewSpaceBetween(testServer, disabled = !valid || testing) { - Text(stringResource(R.string.smp_servers_test_server), color = if (valid && !testing) MaterialTheme.colors.onBackground else HighOrLowlight) + Text(stringResource(R.string.smp_servers_test_server), color = if (valid && !testing) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) ShowTestStatus(server) } val enabled = rememberUpdatedState(server.enabled) PreferenceToggle(stringResource(R.string.smp_servers_use_server_for_new_conn), enabled.value) { onUpdate(server.copy(enabled = it)) } SectionItemView(onDelete, disabled = testing) { - Text(stringResource(R.string.smp_servers_delete_server), color = if (testing) HighOrLowlight else MaterialTheme.colors.error) + Text(stringResource(R.string.smp_servers_delete_server), color = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.error) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt index 173f25c5c0..5e07e6fdba 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/ProtocolServersView.kt @@ -166,7 +166,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () Modifier .padding(horizontal = 2.dp) .size(30.dp), - color = HighOrLowlight, + color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp ) } @@ -207,8 +207,8 @@ private fun ProtocolServersLayout( stringResource(R.string.smp_servers_add), addServer, disabled = testing, - textColor = if (testing) HighOrLowlight else MaterialTheme.colors.primary, - iconColor = if (testing) HighOrLowlight else MaterialTheme.colors.primary + textColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + iconColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } SectionTextFooter( @@ -225,14 +225,14 @@ private fun ProtocolServersLayout( SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) SectionView { SectionItemView(resetServers, disabled = serversUnchanged) { - Text(stringResource(R.string.reset_verb), color = if (!serversUnchanged) MaterialTheme.colors.onBackground else HighOrLowlight) + Text(stringResource(R.string.reset_verb), color = if (!serversUnchanged) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) } val testServersDisabled = testing || allServersDisabled SectionItemView(testServers, disabled = testServersDisabled) { - Text(stringResource(R.string.smp_servers_test_servers), color = if (!testServersDisabled) MaterialTheme.colors.onBackground else HighOrLowlight) + Text(stringResource(R.string.smp_servers_test_servers), color = if (!testServersDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) } SectionItemView(saveSMPServers, disabled = saveDisabled) { - Text(stringResource(R.string.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else HighOrLowlight) + Text(stringResource(R.string.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) } } SectionDividerSpaced(maxBottomPadding = false) @@ -248,15 +248,15 @@ private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, s val address = parseServerAddress(srv.server) when { address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer() - !srv.enabled -> Icon(painterResource(R.drawable.ic_do_not_disturb_on), null, tint = HighOrLowlight) + !srv.enabled -> Icon(painterResource(R.drawable.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) else -> ShowTestStatus(srv) } Spacer(Modifier.padding(horizontal = 4.dp)) val text = address?.hostnames?.firstOrNull() ?: srv.server if (srv.enabled) { - Text(text, color = if (disabled) HighOrLowlight else MaterialTheme.colors.onBackground, maxLines = 1) + Text(text, color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground, maxLines = 1) } else { - Text(text, maxLines = 1, color = HighOrLowlight) + Text(text, maxLines = 1, color = MaterialTheme.colors.secondary) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt index c37d2a95bb..bfecd10027 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/RTCServers.kt @@ -70,7 +70,8 @@ fun RTCServersView( resetRTCServers() isUserRTCServers = false userRTCServersStr.value = "" - } + }, + destructive = true, ) } else { isUserRTCServers = false @@ -154,7 +155,7 @@ fun RTCServersLayout( .height(160.dp) .fillMaxWidth(), shape = RoundedCornerShape(10.dp), - border = BorderStroke(1.dp, MaterialTheme.colors.secondary) + border = BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant) ) { SelectionContainer( Modifier.verticalScroll(rememberScrollState()) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index c28aa38f5a..1b1b272923 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -8,7 +8,6 @@ import SectionView import TextIconSpaced import android.content.Context import android.content.res.Configuration -import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -97,7 +96,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) } } Box( - Modifier.fillMaxSize(), + Modifier.fillMaxSize().background(MaterialTheme.colors.background), contentAlignment = Alignment.Center ) { SimpleButton( @@ -134,8 +133,9 @@ fun SettingsLayout( showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { + val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current - Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).background(MaterialTheme.colors.background)) { + Box(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).themedBackground(theme.value.base)) { Column( Modifier .fillMaxSize() @@ -160,7 +160,7 @@ fun SettingsLayout( SettingsActionItem(painterResource(R.drawable.ic_wifi_tethering), stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal, showCustomModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(R.drawable.ic_videocam), stringResource(R.string.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(R.drawable.ic_lock), stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(R.drawable.ic_light_mode), stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView(it) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(R.drawable.ic_light_mode), stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true) DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() @@ -199,7 +199,7 @@ fun SettingsIncognitoActionItem( ) { SettingsPreferenceItemWithInfo( if (incognito.value) painterResource(R.drawable.ic_theater_comedy_filled) else painterResource(R.drawable.ic_theater_comedy), - if (incognito.value) Indigo else HighOrLowlight, + if (incognito.value) Indigo else MaterialTheme.colors.secondary, stringResource(R.string.incognito), stopped, onClickInfo, @@ -237,7 +237,7 @@ fun MaintainIncognitoState(chatModel: ChatModel) { Icon( painterResource(R.drawable.ic_database), contentDescription = stringResource(R.string.database_passphrase_and_export), - tint = if (encrypted) HighOrLowlight else WarningOrange, + tint = if (encrypted) MaterialTheme.colors.secondary else WarningOrange, ) TextIconSpaced(true) Text(stringResource(R.string.database_passphrase_and_export)) @@ -282,10 +282,10 @@ fun ChatLockItem( click = showSettingsModal { SimplexLockView(chatModel, currentLAMode, setPerformLA) }, icon = if (performLA.value) painterResource(R.drawable.ic_lock_filled) else painterResource(R.drawable.ic_lock), text = stringResource(R.string.chat_lock), - iconColor = if (performLA.value) SimplexGreen else HighOrLowlight, + iconColor = if (performLA.value) SimplexGreen else MaterialTheme.colors.secondary, extraPadding = false, ) { - Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(androidx.compose.ui.R.string.off), color = HighOrLowlight) + Text(if (performLA.value) remember { currentLAMode.state }.value.text else generalGetString(androidx.compose.ui.R.string.off), color = MaterialTheme.colors.secondary) } } @@ -294,7 +294,7 @@ fun ChatLockItem( Icon( painterResource(R.drawable.ic_keyboard), contentDescription = "GitHub", - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) TextIconSpaced(extraPadding = true) Text(generalGetString(R.string.contribute), color = MaterialTheme.colors.primary) @@ -310,7 +310,7 @@ fun ChatLockItem( Icon( painterResource(R.drawable.ic_star), contentDescription = "Google Play", - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) TextIconSpaced(extraPadding = true) Text(generalGetString(R.string.rate_the_app), color = MaterialTheme.colors.primary) @@ -322,7 +322,7 @@ fun ChatLockItem( Icon( painter = painterResource(id = R.drawable.ic_github), contentDescription = "GitHub", - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) TextIconSpaced(extraPadding = true) Text(generalGetString(R.string.star_on_github), color = MaterialTheme.colors.primary) @@ -334,7 +334,7 @@ fun ChatLockItem( Icon( painter = painterResource(id = R.drawable.ic_outline_terminal), contentDescription = stringResource(R.string.chat_console), - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) TextIconSpaced() Text(stringResource(R.string.chat_console)) @@ -346,7 +346,7 @@ fun ChatLockItem( Icon( painter = painterResource(id = R.drawable.ic_github), contentDescription = "GitHub", - tint = HighOrLowlight, + tint = MaterialTheme.colors.secondary, ) TextIconSpaced() Text(generalGetString(R.string.install_simplex_chat_for_terminal), color = MaterialTheme.colors.primary) @@ -361,15 +361,15 @@ fun ChatLockItem( Text("v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") } -@Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, color: Color = MaterialTheme.colors.secondary, stopped: Boolean = false) { - ProfileImage(size = size, image = profileOf.image, color = color) +@Composable fun ProfilePreview(profileOf: NamedChat, size: Dp = 60.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, textColor: Color = MaterialTheme.colors.onBackground, stopped: Boolean = false) { + ProfileImage(size = size, image = profileOf.image, color = iconColor) Spacer(Modifier.padding(horizontal = 8.dp)) Column(Modifier.height(size), verticalArrangement = Arrangement.Center) { Text( profileOf.displayName, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Bold, - color = if (stopped) HighOrLowlight else Color.Unspecified, + color = if (stopped) MaterialTheme.colors.secondary else textColor, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -377,7 +377,7 @@ fun ChatLockItem( Text( profileOf.fullName, Modifier.padding(vertical = 5.dp), - color = if (stopped) HighOrLowlight else Color.Unspecified, + color = if (stopped) MaterialTheme.colors.secondary else textColor, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -386,16 +386,16 @@ fun ChatLockItem( } @Composable -fun SettingsActionItem(icon: Painter, text: String, click: (() -> Unit)? = null, textColor: Color = Color.Unspecified, iconColor: Color = HighOrLowlight, disabled: Boolean = false, extraPadding: Boolean = false) { +fun SettingsActionItem(icon: Painter, text: String, click: (() -> Unit)? = null, textColor: Color = Color.Unspecified, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false) { SectionItemView(click, disabled = disabled, extraPadding = extraPadding) { - Icon(icon, text, tint = if (disabled) HighOrLowlight else iconColor) + Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) TextIconSpaced(extraPadding) - Text(text, color = if (disabled) HighOrLowlight else textColor) + Text(text, color = if (disabled) MaterialTheme.colors.secondary else textColor) } } @Composable -fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = HighOrLowlight, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { +fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { SectionItemView( click, extraPadding = extraPadding, @@ -406,11 +406,11 @@ fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: ( disabled = disabled ) { if (icon != null) { - Icon(icon, text, Modifier, tint = if (disabled) HighOrLowlight else iconColor) + Icon(icon, text, Modifier, tint = if (disabled) MaterialTheme.colors.secondary else iconColor) TextIconSpaced(extraPadding) } if (text != null) { - Text(text, Modifier.weight(1f), color = if (disabled) HighOrLowlight else MaterialTheme.colors.onBackground) + Text(text, Modifier.weight(1f), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING)) } content() @@ -422,7 +422,7 @@ fun SettingsPreferenceItem( icon: Painter?, text: String, pref: SharedPreference, - iconColor: Color = HighOrLowlight, + iconColor: Color = MaterialTheme.colors.secondary, enabled: Boolean = true, onChange: ((Boolean) -> Unit)? = null, ) { @@ -464,12 +464,12 @@ fun PreferenceToggle( fun PreferenceToggleWithIcon( text: String, icon: Painter? = null, - iconColor: Color? = HighOrLowlight, + iconColor: Color? = MaterialTheme.colors.secondary, checked: Boolean, extraPadding: Boolean = false, onChange: (Boolean) -> Unit = {}, ) { - SettingsActionItemWithContent(icon, text, iconColor = iconColor ?: HighOrLowlight, extraPadding = extraPadding) { + SettingsActionItemWithContent(icon, text, iconColor = iconColor ?: MaterialTheme.colors.secondary, extraPadding = extraPadding) { DefaultSwitch( checked = checked, onCheckedChange = { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt index 574cc158a3..6a2cad337e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserAddressView.kt @@ -44,7 +44,7 @@ fun UserAddressView(chatModel: ChatModel) { } }, deleteAddress = { - AlertManager.shared.showAlertMsg( + AlertManager.shared.showAlertDialog( title = generalGetString(R.string.delete_address__question), text = generalGetString(R.string.all_your_contacts_will_remain_connected), confirmText = generalGetString(R.string.delete_verb), @@ -53,7 +53,8 @@ fun UserAddressView(chatModel: ChatModel) { chatModel.controller.apiDeleteUserAddress() chatModel.userAddress.value = null } - } + }, + destructive = true, ) } ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index 5e70e268db..e0d7926486 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -115,7 +115,7 @@ fun UserProfileLayout( ) { Box(contentAlignment = Alignment.TopEnd) { Box(contentAlignment = Alignment.Center) { - ProfileImage(108.dp, profileImage.value, color = HighOrLowlight.copy(alpha = 0.1f)) + ProfileImage(108.dp, profileImage.value, color = MaterialTheme.colors.secondary.copy(alpha = 0.1f)) EditImageButton { scope.launch { bottomSheetModalState.show() } } } if (profileImage.value != null) { @@ -156,7 +156,7 @@ fun UserProfileLayout( saveColor = MaterialTheme.colors.primary } else { saveModifier = Modifier - saveColor = HighOrLowlight + saveColor = MaterialTheme.colors.secondary } Text( stringResource(R.string.save_and_notify_contacts), diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt index 780d9292f9..98c1519320 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfilesView.kt @@ -281,7 +281,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( PassphraseField(actionPassword, generalGetString(R.string.profile_password), isValid = { passwordValid }, showStrength = true) } SectionItemViewSpaceBetween({ doAction(actionPassword.value) }, disabled = !actionEnabled, minHeight = TextFieldDefaults.MinHeight) { - Text(generalGetString(label), color = if (actionEnabled) color else HighOrLowlight) + Text(generalGetString(label), color = if (actionEnabled) color else MaterialTheme.colors.secondary) } } } diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 578c14a298..ea69b48417 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -564,6 +564,8 @@ Update transport isolation mode? Set Use .onion hosts to No if SOCKS proxy does not support them. Appearance + Customize theme + THEME COLORS App version App version: v%s App build: %s @@ -1156,15 +1158,29 @@ System Light Dark + SimpleX System Theme + Dark theme Save color + Import theme + Import theme error + Make sure the file has correct YAML syntax. Export theme to have an example of the theme file structure. + Export theme Reset colors Accent + Additional accent + Secondary + Additional secondary + Background + Menus & alerts + Title + Sent message + Received message You allow diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 53bb7db2d6..3ecfa70793 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -618,6 +618,16 @@ func apiUpdateProfile(profile: Profile) async throws -> Profile? { } } +func apiSetProfileAddress(on: Bool) async throws -> User? { + let userId = try currentUserId("apiSetProfileAddress") + let r = await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) + switch r { + case .userProfileNoChange: return nil + case let .userProfileUpdated(user, _, _): return user + default: throw r + } +} + func apiSetContactPrefs(contactId: Int64, preferences: Preferences) async throws -> Contact? { let r = await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) if case let .contactPrefsUpdated(_, _, toContact) = r { return toContact } @@ -643,10 +653,10 @@ func apiCreateUserAddress() async throws -> String { throw r } -func apiDeleteUserAddress() async throws { +func apiDeleteUserAddress() async throws -> User? { let userId = try currentUserId("apiDeleteUserAddress") let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId)) - if case .userContactLinkDeleted = r { return } + if case let .userContactLinkDeleted(user) = r { return user } throw r } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 5cab211e89..b72006b3fe 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -116,6 +116,21 @@ struct ChatInfoView: View { contactPreferencesButton() } + if let contactLink = contact.contactLink { + Section { + QRCode(uri: contactLink) + Button { + showShareSheet(items: [contactLink]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + } header: { + Text("Address") + } footer: { + Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.") + } + } + Section("Servers") { networkStatusRow() .onTapGesture { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index ed8dfd9221..efbb3c1320 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -45,8 +45,8 @@ struct GroupChatInfoView: View { Section { if groupInfo.canEdit { editGroupButton() - addOrEditWelcomeMessage() } + addOrEditWelcomeMessage() groupPreferencesButton($groupInfo) } header: { Text("") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 9f367fb472..dd13741141 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -65,6 +65,21 @@ struct GroupMemberInfoView: View { } } + if let contactLink = member.contactLink { + Section { + QRCode(uri: contactLink) + Button { + showShareSheet(items: [contactLink]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + } header: { + Text("Address") + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") + } + } + Section("Member") { infoRow("Group", groupInfo.displayName) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 7aa372d5dc..7b90a4abe7 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -15,43 +15,101 @@ struct GroupWelcomeView: View { var groupId: Int64 @Binding var groupInfo: GroupInfo @State private var welcomeText: String = "" + @State private var editMode = true @FocusState private var keyboardVisible: Bool @State private var showSaveDialog = false var body: some View { - List { - Section { - TextEditor(text: $welcomeText) - .focused($keyboardVisible) - .padding(.horizontal, -5) - .padding(.top, -8) - .frame(height: 90, alignment: .topLeading) - .frame(maxWidth: .infinity, alignment: .leading) - } - Section { - saveButton() + VStack { + if groupInfo.canEdit { + editorView() + .modifier(BackButton { + if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) { + dismiss() + } else { + showSaveDialog = true + } + }) + .confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) { + Button("Save and update group profile") { + save() + dismiss() + } + Button("Exit without saving") { dismiss() } + } + } else { + List { + Section { + textPreview() + copyButton() + } + } } } .onAppear { welcomeText = groupInfo.groupProfile.description ?? "" - } - .modifier(BackButton { - if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) { - dismiss() - } else { - showSaveDialog = true - } - }) - .confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) { - Button("Save and update group profile") { - save() - dismiss() - } - Button("Exit without saving") { dismiss() } + keyboardVisible = true } } - @ViewBuilder private func saveButton() -> some View { + private func textPreview() -> some View { + messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil) + .allowsHitTesting(false) + } + + private func editorView() -> some View { + List { + Section { + if editMode { + ZStack { + Group { + if welcomeText.isEmpty { + TextEditor(text: Binding.constant(NSLocalizedString("Enter welcome message…", comment: "placeholder"))) + .foregroundColor(.secondary) + .disabled(true) + } + TextEditor(text: $welcomeText) + .focused($keyboardVisible) + } + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 90, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } else { + textPreview() + .frame(height: 90, alignment: .topLeading) + } + + Button { + editMode = !editMode + keyboardVisible = editMode + } label: { + if editMode { + Label ("Preview", systemImage: "character") + } else { + Label ("Edit", systemImage: "pencil") + } + } + .disabled(welcomeText.isEmpty) + copyButton() + } + + Section { + saveButton() + } + } + } + + private func copyButton() -> some View { + Button { + UIPasteboard.general.string = welcomeText + } label: { + Label ("Copy", systemImage: "doc.on.doc") + } + } + + private func saveButton() -> some View { Button("Save and update group profile") { save() } diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 72be57b3f7..7d850719d6 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -39,6 +39,11 @@ struct ContactConnectionInfo: View { .padding(.bottom, 16) Text(contactConnectionText(contactConnection)) + .padding(.bottom, 16) + + if let connReqInv = contactConnection.connReqInv { + OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInv) + } } .listRowBackground(Color.clear) .listRowSeparator(.hidden) @@ -47,11 +52,7 @@ struct ContactConnectionInfo: View { Section { if contactConnection.groupLinkId == nil { - HStack(spacing: 20) { - Image(systemName: "pencil") - .foregroundColor(.secondary) - .padding(.leading, 6) - .onTapGesture { aliasTextFieldFocused = true } + settingsRow("pencil") { TextField("Set contact name…", text: $localAlias) .autocapitalization(.none) .autocorrectionDisabled(true) @@ -59,20 +60,18 @@ struct ContactConnectionInfo: View { .submitLabel(.done) .onSubmit(setConnectionAlias) } + .onTapGesture { aliasTextFieldFocused = true } } if contactConnection.initiated, let connReqInv = contactConnection.connReqInv { - NavigationLink { - AddContactView(contactConnection: contactConnection, connReqInvitation: connReqInv) - .navigationTitle(CreateLinkTab.oneTime.title) - .navigationBarTitleDisplayMode(.large) - } label: { - Label("Show QR code", systemImage: "qrcode") - .foregroundColor(contactConnection.incognito ? .indigo : .accentColor) - } + oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInv) + } else { + oneTimeLinkLearnMoreButton() } + } + Section { Button(role: .destructive) { alert = .deleteInvitationAlert } label: { @@ -81,7 +80,6 @@ struct ContactConnectionInfo: View { } } } - .listStyle(.insetGrouped) } .alert(item: $alert) { _alert in switch _alert { @@ -96,7 +94,6 @@ struct ContactConnectionInfo: View { } .onAppear { localAlias = contactConnection.localAlias - aliasTextFieldFocused = true } } diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift new file mode 100644 index 0000000000..c746bca825 --- /dev/null +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -0,0 +1,28 @@ +// +// AddContactLearnMore.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 27.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct AddContactLearnMore: View { + var body: some View { + List { + VStack(alignment: .leading, spacing: 18) { + Text("To connect, your contact can scan QR code or use the link in the app.") + Text("If you can't meet in person, show QR code in a video call, or share the link.") + Text("Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/README.md#connect-to-friends).") + } + .listRowBackground(Color.clear) + } + } +} + +struct AddContactLearnMore_Previews: PreviewProvider { + static var previews: some View { + AddContactLearnMore() + } +} diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index fb3011de51..44de3c4987 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -16,50 +16,73 @@ struct AddContactView: View { var connReqInvitation: String var body: some View { - ScrollView { - VStack(alignment: .leading) { - Text("Your contact can scan it from the app.") - .padding(.bottom, 4) - if (contactConnection?.incognito ?? chatModel.incognito) { - HStack { - Image(systemName: "theatermasks").foregroundColor(.indigo).font(.footnote) - Spacer().frame(width: 8) - Text("A random profile will be sent to your contact").font(.footnote) - } - .padding(.bottom) - } else { - HStack { - Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote) - Spacer().frame(width: 8) - Text("Your chat profile will be sent to your contact").font(.footnote) - } - .padding(.bottom) - } - if connReqInvitation != "" { - QRCode(uri: connReqInvitation).padding(.bottom) - } else { - ProgressView() - .progressViewStyle(.circular) - .scaleEffect(2) - .frame(maxWidth: .infinity) - .padding(.vertical) - } - Text("If you can't meet in person, **show QR code in the video call**, or share the link.") - .padding(.bottom) - Button { - showShareSheet(items: [connReqInvitation]) - } label: { - Label("Share invitation link", systemImage: "square.and.arrow.up") - } - .frame(maxWidth: .infinity, alignment: .center) + List { + OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInvitation) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + + Section("1-time link") { + oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInvitation) } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } .onAppear { chatModel.connReqInv = connReqInvitation } } } +@ViewBuilder func oneTimeLinkSection(contactConnection: PendingContactConnection? = nil, connReqInvitation: String) -> some View { + if connReqInvitation != "" { + QRCode(uri: connReqInvitation) + } else { + ProgressView() + .progressViewStyle(.circular) + .scaleEffect(2) + .frame(maxWidth: .infinity) + .padding(.vertical) + } + shareLinkButton(connReqInvitation) + oneTimeLinkLearnMoreButton() +} + +private func shareLinkButton(_ connReqInvitation: String) -> some View { + Button { + showShareSheet(items: [connReqInvitation]) + } label: { + settingsRow("square.and.arrow.up") { + Text("Share 1-time link") + } + } +} + +func oneTimeLinkLearnMoreButton() -> some View { + NavigationLink { + AddContactLearnMore() + .navigationTitle("One-time invitation link") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("info.circle") { + Text("Learn more") + } + } +} + +struct OneTimeLinkProfileText: View { + @EnvironmentObject private var chatModel: ChatModel + var contactConnection: PendingContactConnection? = nil + var connReqInvitation: String + + var body: some View { + HStack { + if (contactConnection?.incognito ?? chatModel.incognito) { + Image(systemName: "theatermasks").foregroundColor(.indigo) + Text("A random profile will be sent to your contact") + } else { + Image(systemName: "info.circle").foregroundColor(.secondary) + Text("Your chat profile will be sent to your contact") + } + } + } +} + struct AddContactView_Previews: PreviewProvider { static var previews: some View { AddContactView(connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D") diff --git a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift index 4504361bd7..71daf88a8c 100644 --- a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift +++ b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift @@ -15,7 +15,7 @@ enum CreateLinkTab { var title: LocalizedStringKey { switch self { case .oneTime: return "One-time invitation link" - case .longTerm: return "Your contact address" + case .longTerm: return "Your SimpleX address" } } } @@ -49,9 +49,9 @@ struct CreateLinkView: View { ) } .tag(CreateLinkTab.oneTime) - UserAddress() + UserAddressView(viaCreateLinkView: true) .tabItem { - Label("Your contact address", systemImage: "infinity.circle") + Label("Your SimpleX address", systemImage: "infinity.circle") } .tag(CreateLinkTab.longTerm) } diff --git a/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift b/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift deleted file mode 100644 index 0e61658c93..0000000000 --- a/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// AcceptRequestsView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 23/10/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct AcceptRequestsView: View { - @EnvironmentObject private var m: ChatModel - @State var contactLink: UserContactLink - @State private var a = AutoAcceptState() - @State private var saved = AutoAcceptState() - @FocusState private var keyboardVisible: Bool - - var body: some View { - List { - Section { - settingsRow("checkmark") { - Toggle("Automatically", isOn: $a.enable) - } - if a.enable { - settingsRow( - a.incognito ? "theatermasks.fill" : "theatermasks", - color: a.incognito ? .indigo : .secondary - ) { - Toggle("Incognito", isOn: $a.incognito) - } - } - } header: { - Text("Accept requests") - } footer: { - saveButtons() - } - if a.enable { - Section { - TextEditor(text: $a.welcomeText) - .focused($keyboardVisible) - .padding(.horizontal, -5) - .padding(.top, -8) - .frame(height: 90, alignment: .topLeading) - .frame(maxWidth: .infinity, alignment: .leading) - } header: { - Text("Welcome message") - } - } - } - .onAppear { - a = AutoAcceptState(contactLink: contactLink) - saved = a - } - .onChange(of: a.enable) { _ in - if !a.enable { a = AutoAcceptState() } - } - } - - @ViewBuilder private func saveButtons() -> some View { - HStack { - Button { - a = saved - } label: { - Label("Cancel", systemImage: "arrow.counterclockwise") - } - Spacer() - Button { - Task { - do { - if let link = try await userAddressAutoAccept(a.autoAccept) { - contactLink = link - m.userAddress = link - saved = a - } - } catch let error { - logger.error("userAddressAutoAccept error: \(responseError(error))") - } - } - } label: { - Label("Save", systemImage: "checkmark") - } - } - .font(.body) - .disabled(a == saved) - } - - private struct AutoAcceptState: Equatable { - var enable = false - var incognito = false - var welcomeText = "" - - init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { - self.enable = enable - self.incognito = incognito - self.welcomeText = welcomeText - } - - init(contactLink: UserContactLink) { - if let aa = contactLink.autoAccept { - enable = true - incognito = aa.acceptIncognito - if let msg = aa.autoReply { - welcomeText = msg.text - } else { - welcomeText = "" - } - } else { - enable = false - incognito = false - welcomeText = "" - } - } - - var autoAccept: AutoAccept? { - if enable { - var autoReply: MsgContent? = nil - let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) - if s != "" { autoReply = .text(s) } - return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) - } - return nil - } - } -} - -struct AcceptRequestsView_Previews: PreviewProvider { - static var previews: some View { - AcceptRequestsView(contactLink: UserContactLink(connReqContact: "")) - } -} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index d25ea45f49..4f8bb4f503 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -147,10 +147,11 @@ struct SettingsView: View { incognitoRow() NavigationLink { - CreateLinkView(selection: .longTerm, viaNavLink: true) - .navigationBarTitleDisplayMode(.inline) + UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) } label: { - settingsRow("qrcode") { Text("Your SimpleX contact address") } + settingsRow("qrcode") { Text("Your SimpleX address") } } NavigationLink { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift deleted file mode 100644 index 7564bea358..0000000000 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// UserAddress.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 31/01/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct UserAddress: View { - @EnvironmentObject private var chatModel: ChatModel - @State private var alert: UserAddressAlert? - @State private var showAcceptRequests = false - - private enum UserAddressAlert: Identifiable { - case deleteAddress - case error(title: LocalizedStringKey, error: LocalizedStringKey = "") - - var id: String { - switch self { - case .deleteAddress: return "deleteAddress" - case let .error(title, _): return "error \(title)" - } - } - } - - var body: some View { - ScrollView { - VStack (alignment: .leading) { - Text("You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it.") - .padding(.bottom) - if let userAdress = chatModel.userAddress { - QRCode(uri: userAdress.connReqContact) - HStack { - Button { - showShareSheet(items: [userAdress.connReqContact]) - } label: { - HStack { - Image(systemName: "square.and.arrow.up") - Text("Share link") - } - } - .padding() - NavigationLink { - if let contactLink = chatModel.userAddress { - AcceptRequestsView(contactLink: contactLink) - .navigationTitle("Contact requests") - .navigationBarTitleDisplayMode(.large) - } - } label: { - HStack { - Text("Contact requests") - Image(systemName: "chevron.right") - } - } - .padding() - } - .frame(maxWidth: .infinity) - Button(role: .destructive) { alert = .deleteAddress } label: { - Label("Delete address", systemImage: "trash") - } - .frame(maxWidth: .infinity) - } else { - Button { - Task { - do { - let connReqContact = try await apiCreateUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connReqContact: connReqContact) - } - } catch let error { - logger.error("UserAddress apiCreateUserAddress: \(responseError(error))") - let a = getErrorAlert(error, "Error creating address") - alert = .error(title: a.title, error: a.message) - } - } - } label: { Label("Create address", systemImage: "qrcode") } - .frame(maxWidth: .infinity) - } - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .sheet(isPresented: $showAcceptRequests) { - if let contactLink = chatModel.userAddress { - AcceptRequestsView(contactLink: contactLink) - } - } - .alert(item: $alert) { alert in - switch alert { - case .deleteAddress: - return Alert( - title: Text("Delete address?"), - message: Text("All your contacts will remain connected"), - primaryButton: .destructive(Text("Delete")) { - Task { - do { - try await apiDeleteUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = nil - } - } catch let error { - logger.error("UserAddress apiDeleteUserAddress: \(responseError(error))") - } - } - }, secondaryButton: .cancel() - ) - case let .error(title, error): - return Alert(title: Text(title), message: Text(error)) - } - } - } - } -} - -struct UserAddress_Previews: PreviewProvider { - static var previews: some View { - let chatModel = ChatModel() - chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") - return Group { - UserAddress() - .environmentObject(chatModel) - UserAddress() - .environmentObject(ChatModel()) - } - } -} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift new file mode 100644 index 0000000000..e944dcec95 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -0,0 +1,29 @@ +// +// UserAddressLearnMore.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 27.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct UserAddressLearnMore: View { + var body: some View { + List { + VStack(alignment: .leading, spacing: 18) { + Text("You can share your address as a link or QR code - anybody can connect to you.") + Text("You won't lose your contacts if you later delete your address.") + Text("When people request to connect, you can accept or reject it.") + Text("Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address).") + } + .listRowBackground(Color.clear) + } + } +} + +struct UserAddressLearnMore_Previews: PreviewProvider { + static var previews: some View { + UserAddressLearnMore() + } +} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift new file mode 100644 index 0000000000..802cf4d4a5 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -0,0 +1,414 @@ +// +// UserAddressView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 26.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct UserAddressView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject private var chatModel: ChatModel + @State var viaCreateLinkView = false + @State var shareViaProfile = false + @State private var aas = AutoAcceptState() + @State private var savedAAS = AutoAcceptState() + @State private var ignoreShareViaProfileChange = false + @State private var alert: UserAddressAlert? + @State private var showSaveDialogue = false + @State private var progressIndicator = false + @FocusState private var keyboardVisible: Bool + + private enum UserAddressAlert: Identifiable { + case deleteAddress + case profileAddress(on: Bool) + case shareOnCreate + case error(title: LocalizedStringKey, error: LocalizedStringKey = "") + + var id: String { + switch self { + case .deleteAddress: return "deleteAddress" + case let .profileAddress(on): return "profileAddress \(on)" + case .shareOnCreate: return "shareOnCreate" + case let .error(title, _): return "error \(title)" + } + } + } + + var body: some View { + ZStack { + if viaCreateLinkView { + userAddressScrollView() + } else { + userAddressScrollView() + .modifier(BackButton { + if savedAAS == aas { + dismiss() + } else { + keyboardVisible = false + showSaveDialogue = true + } + }) + .confirmationDialog("Save settings?", isPresented: $showSaveDialogue) { + Button("Save auto-accept settings") { + saveAAS() + dismiss() + } + Button("Exit without saving") { dismiss() } + } + } + if progressIndicator { + ZStack { + if chatModel.userAddress != nil { + Circle() + .fill(.white) + .opacity(0.7) + .frame(width: 56, height: 56) + } + ProgressView().scaleEffect(2) + } + } + } + } + + @Namespace private var bottomID + + private func userAddressScrollView() -> some View { + ScrollViewReader { proxy in + userAddressView() + .onChange(of: keyboardVisible) { _ in + if keyboardVisible { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + withAnimation { + proxy.scrollTo(bottomID, anchor: .top) + } + } + } + } + } + } + + private func userAddressView() -> some View { + List { + if let userAddress = chatModel.userAddress { + existingAddressView(userAddress) + .onAppear { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } + .onChange(of: aas.enable) { _ in + if !aas.enable { aas = AutoAcceptState() } + } + } else { + Section { + createAddressButton() + } footer: { + Text("Create an address to let people connect with you.") + } + + Section { + learnMoreButton() + } + } + } + .alert(item: $alert) { alert in + switch alert { + case .deleteAddress: + return Alert( + title: Text("Delete address?"), + message: + shareViaProfile + ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") + : Text("All your contacts will remain connected."), + primaryButton: .destructive(Text("Delete")) { + progressIndicator = true + Task { + do { + if let u = try await apiDeleteUserAddress() { + DispatchQueue.main.async { + chatModel.userAddress = nil + chatModel.updateUser(u) + if shareViaProfile { + ignoreShareViaProfileChange = true + shareViaProfile = false + } + } + } + await MainActor.run { progressIndicator = false } + } catch let error { + logger.error("UserAddressView apiDeleteUserAddress: \(responseError(error))") + await MainActor.run { progressIndicator = false } + } + } + }, secondaryButton: .cancel() + ) + case let .profileAddress(on): + if on { + return Alert( + title: Text("Share address with contacts?"), + message: Text("Profile update will be sent to your contacts."), + primaryButton: .default(Text("Share")) { + setProfileAddress(on) + }, secondaryButton: .cancel() { + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ) + } else { + return Alert( + title: Text("Stop sharing address?"), + message: Text("Profile update will be sent to your contacts."), + primaryButton: .default(Text("Stop sharing")) { + setProfileAddress(on) + }, secondaryButton: .cancel() { + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ) + } + case .shareOnCreate: + return Alert( + title: Text("Share address with contacts?"), + message: Text("Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts."), + primaryButton: .default(Text("Share")) { + setProfileAddress(true) + ignoreShareViaProfileChange = true + shareViaProfile = true + }, secondaryButton: .cancel() + ) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + } + } + } + + @ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View { + Section { + QRCode(uri: userAddress.connReqContact) + shareQRCodeButton(userAddress) + shareWithContactsButton() + autoAcceptToggle() + learnMoreButton() + } header: { + Text("Address") + } + + if aas.enable { + autoAcceptSection() + } + + Section { + deleteAddressButton() + } footer: { + Text("Your contacts will remain connected.") + } + .id(bottomID) + } + + private func createAddressButton() -> some View { + Button { + progressIndicator = true + Task { + do { + let connReqContact = try await apiCreateUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = UserContactLink(connReqContact: connReqContact) + alert = .shareOnCreate + progressIndicator = false + } + } catch let error { + logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") + let a = getErrorAlert(error, "Error creating address") + alert = .error(title: a.title, error: a.message) + await MainActor.run { progressIndicator = false } + } + } + } label: { + Label("Create SimpleX address", systemImage: "qrcode") + } + } + + private func deleteAddressButton() -> some View { + Button(role: .destructive) { + alert = .deleteAddress + } label: { + Label("Delete address", systemImage: "trash") + .foregroundColor(Color.red) + } + } + + private func shareQRCodeButton(_ userAdress: UserContactLink) -> some View { + Button { + showShareSheet(items: [userAdress.connReqContact]) + } label: { + settingsRow("square.and.arrow.up") { + Text("Share address") + } + } + } + + private func autoAcceptToggle() -> some View { + settingsRow("checkmark") { + Toggle("Auto-accept", isOn: $aas.enable) + .onChange(of: aas.enable) { _ in + saveAAS() + } + } + } + + private func learnMoreButton() -> some View { + NavigationLink { + UserAddressLearnMore() + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("info.circle") { + Text("About SimpleX address") + } + } + } + + private func shareWithContactsButton() -> some View { + settingsRow("person") { + Toggle("Share with contacts", isOn: $shareViaProfile) + .onChange(of: shareViaProfile) { on in + if ignoreShareViaProfileChange { + ignoreShareViaProfileChange = false + } else { + alert = .profileAddress(on: on) + } + } + } + } + + private func setProfileAddress(_ on: Bool) { + progressIndicator = true + Task { + do { + if let u = try await apiSetProfileAddress(on: on) { + DispatchQueue.main.async { + chatModel.updateUser(u) + } + } + await MainActor.run { progressIndicator = false } + } catch let error { + logger.error("UserAddressView apiSetProfileAddress: \(responseError(error))") + await MainActor.run { progressIndicator = false } + } + } + } + + private struct AutoAcceptState: Equatable { + var enable = false + var incognito = false + var welcomeText = "" + + init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { + self.enable = enable + self.incognito = incognito + self.welcomeText = welcomeText + } + + init(userAddress: UserContactLink) { + if let aa = userAddress.autoAccept { + enable = true + incognito = aa.acceptIncognito + if let msg = aa.autoReply { + welcomeText = msg.text + } else { + welcomeText = "" + } + } else { + enable = false + incognito = false + welcomeText = "" + } + } + + var autoAccept: AutoAccept? { + if enable { + var autoReply: MsgContent? = nil + let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) + if s != "" { autoReply = .text(s) } + return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) + } + return nil + } + } + + @ViewBuilder private func autoAcceptSection() -> some View { + Section { + acceptIncognitoToggle() + welcomeMessageEditor() + saveAASButton() + .disabled(aas == savedAAS) + } header: { + Text("Auto-accept") + } + } + + private func acceptIncognitoToggle() -> some View { + settingsRow( + aas.incognito ? "theatermasks.fill" : "theatermasks", + color: aas.incognito ? .indigo : .secondary + ) { + Toggle("Accept incognito", isOn: $aas.incognito) + } + } + + private func welcomeMessageEditor() -> some View { + ZStack { + Group { + if aas.welcomeText.isEmpty { + TextEditor(text: Binding.constant(NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"))) + .foregroundColor(.secondary) + .disabled(true) + } + TextEditor(text: $aas.welcomeText) + .focused($keyboardVisible) + } + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 90, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + + private func saveAASButton() -> some View { + Button { + keyboardVisible = false + saveAAS() + } label: { + Text("Save") + } + } + + private func saveAAS() { + Task { + do { + if let address = try await userAddressAutoAccept(aas.autoAccept) { + chatModel.userAddress = address + savedAAS = aas + } + } catch let error { + logger.error("userAddressAutoAccept error: \(responseError(error))") + } + } + } +} + +struct UserAddressView_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + return Group { + UserAddressView() + .environmentObject(chatModel) + UserAddressView() + .environmentObject(ChatModel()) + } + } +} diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 775e7c754d..0980517f78 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -361,6 +361,10 @@ Dostupné ve verzi 5.1 O SimpleX chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Zbarvení @@ -387,9 +391,8 @@ Dostupné ve verzi 5.1 Přijmout inkognito No comment provided by engineer. - - Accept requests - Přijímat žádosti + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Dostupné ve verzi 5.1 Přidat uvítací zprávu No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Správci mohou vytvářet odkazy pro připojení ke skupinám. @@ -447,9 +454,12 @@ Dostupné ve verzi 5.1 Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás. No comment provided by engineer. - - All your contacts will remain connected - Všechny vaše kontakty zůstanou připojeny + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Dostupné ve verzi 5.1 Ověřování není k dispozici No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Automatické přijímání žádostí o kontakt @@ -622,11 +636,6 @@ Dostupné ve verzi 5.1 Automaticky přijímat obrázky No comment provided by engineer. - - Automatically - Automaticky - No comment provided by engineer. - Back Zpět @@ -992,11 +1001,6 @@ Dostupné ve verzi 5.1 Předvolby kontaktů No comment provided by engineer. - - Contact requests - Žádosti o kontakt - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Kontakty mohou označit zprávy ke smazání; vy je budete moci zobrazit. @@ -1017,9 +1021,12 @@ Dostupné ve verzi 5.1 Vytvořit No comment provided by engineer. - - Create address - Vytvořit adresu + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Dostupné ve verzi 5.1 Zadejte server ručně No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Chyba @@ -2343,6 +2354,10 @@ Dostupné ve verzi 5.1 Velký soubor! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Opustit @@ -2987,6 +3002,10 @@ Dostupné ve verzi 5.1 Heslo profilu No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Zákaz audio/video hovorů. @@ -3247,6 +3266,10 @@ Dostupné ve verzi 5.1 Uložit archiv No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Uložení profilu skupiny @@ -3282,6 +3305,10 @@ Dostupné ve verzi 5.1 Uložit servery? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Uložit uvítací zprávu? @@ -3472,6 +3499,14 @@ Dostupné ve verzi 5.1 Sdílet chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Sdílet odkaz na pozvánku @@ -3487,6 +3522,10 @@ Dostupné ve verzi 5.1 Jednorázový zvací odkaz No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Zobrazit QR kód @@ -3537,6 +3576,10 @@ Dostupné ve verzi 5.1 Zapnutý zámek SimpleX Lock No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX kontaktní adresa @@ -3632,6 +3675,14 @@ Dostupné ve verzi 5.1 Zastavit odesílání souboru? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Odeslat @@ -4228,6 +4279,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Když je k dispozici No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve. @@ -4320,9 +4375,13 @@ SimpleX zámek musí být povolen. Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. O své kontakty nepřijdete, pokud ji později smažete. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX zámek musí být povolen. Přestanete dostávat zprávy z této skupiny. Historie chatu bude zachována. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + O své kontakty nepřijdete, pokud ji později smažete. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Snažíte se pozvat kontakt, se kterým jste sdíleli inkognito profil, do skupiny, ve které používáte svůj hlavní profil @@ -4462,7 +4526,7 @@ SimpleX zámek musí být povolen. Your SimpleX contact address - Vaše kontaktní adresa SimpleX + Vaše adresa SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Toto připojení můžete zrušit a kontakt odebrat (a zkusit to později s nov Vaše kontakty mohou povolit úplné mazání zpráv. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 14b40892cf..68ccbbb7a3 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -361,6 +361,10 @@ Verfügbar ab v5.1 Über SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Akzentfarbe @@ -387,9 +391,8 @@ Verfügbar ab v5.1 Inkognito akzeptieren No comment provided by engineer. - - Accept requests - Anfragen annehmen + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Verfügbar ab v5.1 Begrüßungsmeldung hinzufügen No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Administratoren können Links für den Beitritt zu Gruppen erzeugen. @@ -447,9 +454,12 @@ Verfügbar ab v5.1 Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. No comment provided by engineer. - - All your contacts will remain connected - Alle Ihre Kontakte bleiben verbunden + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Verfügbar ab v5.1 Authentifizierung nicht verfügbar No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Kontaktanfragen automatisch annehmen @@ -622,11 +636,6 @@ Verfügbar ab v5.1 Bilder automatisch akzeptieren No comment provided by engineer. - - Automatically - Automatisch - No comment provided by engineer. - Back Zurück @@ -992,11 +1001,6 @@ Verfügbar ab v5.1 Kontakt Präferenzen No comment provided by engineer. - - Contact requests - Kontaktanfragen - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen. @@ -1017,9 +1021,12 @@ Verfügbar ab v5.1 Erstellen No comment provided by engineer. - - Create address - Adresse erstellen + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Verfügbar ab v5.1 Geben Sie den Server manuell ein No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Fehler @@ -2343,6 +2354,10 @@ Verfügbar ab v5.1 Große Datei! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Verlassen @@ -2987,6 +3002,10 @@ Verfügbar ab v5.1 Passwort für Profil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Audio/Video Anrufe nicht erlauben. @@ -3247,6 +3266,10 @@ Verfügbar ab v5.1 Archiv speichern No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Gruppenprofil speichern @@ -3282,6 +3305,10 @@ Verfügbar ab v5.1 Alle Server speichern? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Begrüßungsmeldung speichern? @@ -3472,6 +3499,14 @@ Verfügbar ab v5.1 Teilen chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Einladungslink teilen @@ -3487,6 +3522,10 @@ Verfügbar ab v5.1 Einmal-Einladungslink teilen No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code QR-Code anzeigen @@ -3537,6 +3576,10 @@ Verfügbar ab v5.1 SimpleX Sperre aktiviert No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX Kontaktadressen-Link @@ -3632,6 +3675,14 @@ Verfügbar ab v5.1 Das Senden der Datei beenden? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Übermitteln @@ -4228,6 +4279,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wenn verfügbar No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden. @@ -4320,9 +4375,13 @@ Dafür muss die SimpleX Sperre aktiviert sein. Sie können diesen Link oder QR-Code teilen - Damit kann jede Person der Gruppe beitreten. Wenn Sie den Link später löschen, werden Sie keine Gruppenmitglieder verlieren, die der Gruppe darüber beigetreten sind. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. No comment provided by engineer. @@ -4435,6 +4494,11 @@ Dafür muss die SimpleX Sperre aktiviert sein. Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Chatverlauf wird beibehalten. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden @@ -4532,6 +4596,10 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index c0a0c7cfc1..d7c662591e 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -361,6 +361,11 @@ Available in v5.1 About SimpleX Chat No comment provided by engineer. + + About SimpleX address + About SimpleX address + No comment provided by engineer. + Accent color Accent color @@ -387,9 +392,9 @@ Available in v5.1 Accept incognito No comment provided by engineer. - - Accept requests - Accept requests + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +427,11 @@ Available in v5.1 Add welcome message No comment provided by engineer. + + Address + Address + No comment provided by engineer. + Admins can create the links to join groups. Admins can create the links to join groups. @@ -447,9 +457,14 @@ Available in v5.1 All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. No comment provided by engineer. - - All your contacts will remain connected - All your contacts will remain connected + + All your contacts will remain connected. + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +627,11 @@ Available in v5.1 Authentication unavailable No comment provided by engineer. + + Auto-accept + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Auto-accept contact requests @@ -622,11 +642,6 @@ Available in v5.1 Auto-accept images No comment provided by engineer. - - Automatically - Automatically - No comment provided by engineer. - Back Back @@ -992,11 +1007,6 @@ Available in v5.1 Contact preferences No comment provided by engineer. - - Contact requests - Contact requests - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Contacts can mark messages for deletion; you will be able to view them. @@ -1017,9 +1027,14 @@ Available in v5.1 Create No comment provided by engineer. - - Create address - Create address + + Create SimpleX address + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1605,11 @@ Available in v5.1 Enter server manually No comment provided by engineer. + + Enter welcome message… (optional) + Enter welcome message… (optional) + placeholder + Error Error @@ -2343,6 +2363,11 @@ Available in v5.1 Large file! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Leave @@ -2987,6 +3012,11 @@ Available in v5.1 Profile password No comment provided by engineer. + + Profile update will be sent to your contacts. + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Prohibit audio/video calls. @@ -3247,6 +3277,11 @@ Available in v5.1 Save archive No comment provided by engineer. + + Save auto-accept settings + Save auto-accept settings + No comment provided by engineer. + Save group profile Save group profile @@ -3282,6 +3317,11 @@ Available in v5.1 Save servers? No comment provided by engineer. + + Save settings? + Save settings? + No comment provided by engineer. + Save welcome message? Save welcome message? @@ -3472,6 +3512,16 @@ Available in v5.1 Share chat item action + + Share address + Share address + No comment provided by engineer. + + + Share address with contacts? + Share address with contacts? + No comment provided by engineer. + Share invitation link Share invitation link @@ -3487,6 +3537,11 @@ Available in v5.1 Share one-time invitation link No comment provided by engineer. + + Share with contacts + Share with contacts + No comment provided by engineer. + Show QR code Show QR code @@ -3537,6 +3592,11 @@ Available in v5.1 SimpleX Lock turned on No comment provided by engineer. + + SimpleX address + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX contact address @@ -3632,6 +3692,16 @@ Available in v5.1 Stop sending file? No comment provided by engineer. + + Stop sharing + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + Stop sharing address? + No comment provided by engineer. + Submit Submit @@ -4228,6 +4298,11 @@ To connect, please ask your contact to create another connection link and check When available No comment provided by engineer. + + When people request to connect, you can accept or reject it. + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. @@ -4320,9 +4395,14 @@ SimpleX Lock must be enabled. You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. + + You can share this address with your contacts to let them connect with **%@**. + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + You can share your address as a link or QR code - anybody can connect to you. No comment provided by engineer. @@ -4435,6 +4515,11 @@ SimpleX Lock must be enabled. You will stop receiving messages from this group. Chat history will be preserved. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + You won't lose your contacts if you later delete your address. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile @@ -4460,9 +4545,9 @@ SimpleX Lock must be enabled. Your SMP servers No comment provided by engineer. - - Your SimpleX contact address - Your SimpleX contact address + + Your SimpleX address + Your SimpleX address No comment provided by engineer. @@ -4532,6 +4617,11 @@ You can cancel this connection and remove the contact (and try later with a new Your contacts can allow full message deletion. No comment provided by engineer. + + Your contacts will remain connected. + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Your current chat database will be DELETED and REPLACED with the imported one. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index b87a02742f..148a814c74 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -361,6 +361,10 @@ Disponible en v5.1 Acerca de SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Color @@ -387,9 +391,8 @@ Disponible en v5.1 Aceptar incógnito No comment provided by engineer. - - Accept requests - Aceptar solicitudes + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponible en v5.1 Agregar mensaje de bienvenida No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Los administradores pueden crear los enlaces para unirse a los grupos. @@ -447,9 +454,12 @@ Disponible en v5.1 Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse! No comment provided by engineer. - - All your contacts will remain connected - Todos tus contactos permanecerán conectados + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponible en v5.1 Autenticación no disponible No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Aceptar automáticamente solicitudes del contacto @@ -622,11 +636,6 @@ Disponible en v5.1 Aceptar automáticamente imágenes No comment provided by engineer. - - Automatically - Automáticamente - No comment provided by engineer. - Back Volver @@ -992,11 +1001,6 @@ Disponible en v5.1 Preferencias de contacto No comment provided by engineer. - - Contact requests - Solicitud del contacto - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. El contacto solo puede marcar los mensajes para eliminar. Tu podrás verlos. @@ -1017,9 +1021,12 @@ Disponible en v5.1 Crear No comment provided by engineer. - - Create address - Crear dirección + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponible en v5.1 Introduce el servidor manualmente No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Error @@ -2343,6 +2354,10 @@ Disponible en v5.1 ¡Archivo grande! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Salir @@ -2987,6 +3002,10 @@ Disponible en v5.1 Contraseña del perfil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Prohibir las llamadas/videollamadas. @@ -3247,6 +3266,10 @@ Disponible en v5.1 Guardar archivo No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Guardar perfil de grupo @@ -3282,6 +3305,10 @@ Disponible en v5.1 ¿Guardar servidores? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? ¿Guardar mensaje de bienvenida? @@ -3472,6 +3499,14 @@ Disponible en v5.1 Compartir chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Compartir enlace de invitación @@ -3487,6 +3522,10 @@ Disponible en v5.1 Compartir enlace de invitación de un uso No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Mostrar código QR @@ -3537,6 +3576,10 @@ Disponible en v5.1 Bloqueo SimpleX activado No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Dirección de contacto SimpleX @@ -3632,6 +3675,14 @@ Disponible en v5.1 ¿Dejar de enviar el archivo? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Enviar @@ -4229,6 +4280,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Cuando esté disponible No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. @@ -4321,9 +4376,13 @@ Bloqueo SimpleX debe estar activado. Puedes compartir un enlace o un código QR: cualquiera podrá unirse al grupo. Si lo eliminas más tarde los miembros del grupo no se perderán. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. Si lo eliminas más tarde tus contactos no se perderán. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. No comment provided by engineer. @@ -4436,6 +4495,11 @@ Bloqueo SimpleX debe estar activado. Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Si lo eliminas más tarde tus contactos no se perderán. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Estás intentando invitar a un contacto con el que compartes un perfil incógnito a un grupo en el que usas tu perfil principal @@ -4463,7 +4527,7 @@ Bloqueo SimpleX debe estar activado. Your SimpleX contact address - Mi dirección de contacto SimpleX + Mi dirección SimpleX No comment provided by engineer. @@ -4533,6 +4597,10 @@ Puedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde c Tus contactos pueden permitir la eliminación completa de mensajes. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. La base de datos actual será ELIMINADA y SUSTITUIDA por la importada. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 88d5bca63d..a5c44c9ff4 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -361,6 +361,10 @@ Disponible dans la v5.1 À propos de SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Couleur principale @@ -387,9 +391,8 @@ Disponible dans la v5.1 Accepter en incognito No comment provided by engineer. - - Accept requests - Accepter les demandes + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponible dans la v5.1 Ajouter un message d'accueil No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Les admins peuvent créer les liens qui permettent de rejoindre les groupes. @@ -447,9 +454,12 @@ Disponible dans la v5.1 Tous les messages seront supprimés - impossible de revenir en arrière ! Les messages seront supprimés UNIQUEMENT pour vous. No comment provided by engineer. - - All your contacts will remain connected - Tous vos contacts resteront connectés + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponible dans la v5.1 Authentification indisponible No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Demandes de contact auto-acceptées @@ -622,11 +636,6 @@ Disponible dans la v5.1 Images auto-acceptées No comment provided by engineer. - - Automatically - Automatiquement - No comment provided by engineer. - Back Retour @@ -992,11 +1001,6 @@ Disponible dans la v5.1 Préférences de contact No comment provided by engineer. - - Contact requests - Demandes de contact - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter. @@ -1017,9 +1021,12 @@ Disponible dans la v5.1 Créer No comment provided by engineer. - - Create address - Créer une adresse + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponible dans la v5.1 Entrer un serveur manuellement No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Erreur @@ -2343,6 +2354,10 @@ Disponible dans la v5.1 Fichier trop lourd ! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Quitter @@ -2987,6 +3002,10 @@ Disponible dans la v5.1 Mot de passe de profil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Interdire les appels audio/vidéo. @@ -3247,6 +3266,10 @@ Disponible dans la v5.1 Sauvegarder l'archive No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Sauvegarder le profil du groupe @@ -3282,6 +3305,10 @@ Disponible dans la v5.1 Sauvegarder les serveurs ? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Sauvegarder le message d'accueil ? @@ -3472,6 +3499,14 @@ Disponible dans la v5.1 Partager chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Partager le lien d'invitation @@ -3487,6 +3522,10 @@ Disponible dans la v5.1 Partager un lien d'invitation unique No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Afficher le code QR @@ -3537,6 +3576,10 @@ Disponible dans la v5.1 SimpleX Lock activé No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Adresse de contact SimpleX @@ -3632,6 +3675,14 @@ Disponible dans la v5.1 Arrêter l'envoi du fichier ? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Soumettre @@ -4228,6 +4279,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Quand disponible No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite. @@ -4320,9 +4375,13 @@ SimpleX Lock doit être activé. Vous pouvez partager un lien ou un code QR - n'importe qui pourra rejoindre le groupe. Vous ne perdrez pas les membres du groupe si vous le supprimez par la suite. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock doit être activé. Vous ne recevrez plus de messages de ce groupe. L'historique du chat sera conservé. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Vous ne perdrez pas vos contacts si vous la supprimez par la suite. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Vous essayez d'inviter un contact avec lequel vous avez partagé un profil incognito à rejoindre le groupe dans lequel vous utilisez votre profil principal @@ -4462,7 +4526,7 @@ SimpleX Lock doit être activé. Your SimpleX contact address - Votre adresse de contact SimpleX + Votre adresse SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Vous pouvez annuler la connexion et supprimer le contact (et réessayer plus tar Vos contacts peuvent autoriser la suppression complète des messages. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 6c0ee81008..bb549d4e9f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -361,6 +361,10 @@ Disponibile nella v5.1 Riguardo SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Colore principale @@ -387,9 +391,8 @@ Disponibile nella v5.1 Accetta in incognito No comment provided by engineer. - - Accept requests - Accetta le richieste + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponibile nella v5.1 Aggiungi messaggio di benvenuto No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Gli amministratori possono creare i link per entrare nei gruppi. @@ -447,9 +454,12 @@ Disponibile nella v5.1 Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te. No comment provided by engineer. - - All your contacts will remain connected - Tutti i tuoi contatti resteranno connessi + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponibile nella v5.1 Autenticazione non disponibile No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Auto-accetta richieste di contatto @@ -622,11 +636,6 @@ Disponibile nella v5.1 Auto-accetta immagini No comment provided by engineer. - - Automatically - Automaticamente - No comment provided by engineer. - Back Indietro @@ -992,11 +1001,6 @@ Disponibile nella v5.1 Preferenze del contatto No comment provided by engineer. - - Contact requests - Richieste del contatto - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. I contatti possono contrassegnare i messaggi per l'eliminazione; potrai vederli. @@ -1017,9 +1021,12 @@ Disponibile nella v5.1 Crea No comment provided by engineer. - - Create address - Crea indirizzo + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponibile nella v5.1 Inserisci il server a mano No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Errore @@ -2343,6 +2354,10 @@ Disponibile nella v5.1 File grande! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Esci @@ -2987,6 +3002,10 @@ Disponibile nella v5.1 Password del profilo No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Proibisci le chiamate audio/video. @@ -3247,6 +3266,10 @@ Disponibile nella v5.1 Salva archivio No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Salva il profilo del gruppo @@ -3282,6 +3305,10 @@ Disponibile nella v5.1 Salvare i server? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Salvare il messaggio di benvenuto? @@ -3472,6 +3499,14 @@ Disponibile nella v5.1 Condividi chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Condividi link di invito @@ -3487,6 +3522,10 @@ Disponibile nella v5.1 Condividi link di invito una tantum No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Mostra codice QR @@ -3537,6 +3576,10 @@ Disponibile nella v5.1 SimpleX Lock attivato No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Indirizzo del contatto SimpleX @@ -3632,6 +3675,14 @@ Disponibile nella v5.1 Fermare l'invio del file? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Invia @@ -4228,6 +4279,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando disponibili No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano. @@ -4320,9 +4375,13 @@ SimpleX Lock deve essere attivato. Puoi condividere un link o un codice QR: chiunque potrà unirsi al gruppo. Non perderai i membri del gruppo se in seguito lo elimini. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. Non perderai i tuoi contatti se in seguito lo elimini. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock deve essere attivato. Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Non perderai i tuoi contatti se in seguito lo elimini. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Stai tentando di invitare un contatto con cui hai condiviso un profilo in incognito nel gruppo in cui stai usando il tuo profilo principale @@ -4462,7 +4526,7 @@ SimpleX Lock deve essere attivato. Your SimpleX contact address - Il tuo indirizzo di contatto SimpleX + Il tuo indirizzo SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Puoi annullare questa connessione e rimuovere il contatto (e riprovare più tard I tuoi contatti possono consentire l'eliminazione completa dei messaggi. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Il tuo attuale database della chat verrà ELIMINATO e SOSTITUITO con quello importato. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index a144792be3..ff753124e3 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -361,6 +361,10 @@ Beschikbaar in v5.1 Over SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Accent kleur @@ -387,9 +391,8 @@ Beschikbaar in v5.1 Accepteer incognito No comment provided by engineer. - - Accept requests - Verzoeken accepteren + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Beschikbaar in v5.1 Welkomst bericht toevoegen No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Beheerders kunnen de uitnodiging links naar groepen aanmaken. @@ -447,9 +454,12 @@ Beschikbaar in v5.1 Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. No comment provided by engineer. - - All your contacts will remain connected - Al uw contacten blijven verbonden + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Beschikbaar in v5.1 Verificatie niet beschikbaar No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Contact verzoeken automatisch accepteren @@ -622,11 +636,6 @@ Beschikbaar in v5.1 Afbeeldingen automatisch accepteren No comment provided by engineer. - - Automatically - Automatisch - No comment provided by engineer. - Back Terug @@ -992,11 +1001,6 @@ Beschikbaar in v5.1 Contact voorkeuren No comment provided by engineer. - - Contact requests - Contact verzoeken - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Contact personen kunnen berichten markeren voor verwijdering; u kunt ze wel bekijken. @@ -1017,9 +1021,12 @@ Beschikbaar in v5.1 Maak No comment provided by engineer. - - Create address - Adres aanmaken + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Beschikbaar in v5.1 Voer de server handmatig in No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Fout @@ -2343,6 +2354,10 @@ Beschikbaar in v5.1 Groot bestand! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Verlaten @@ -2987,6 +3002,10 @@ Beschikbaar in v5.1 Profiel wachtwoord No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Audio/video gesprekken verbieden. @@ -3247,6 +3266,10 @@ Beschikbaar in v5.1 Bewaar archief No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Groep profiel opslaan @@ -3282,6 +3305,10 @@ Beschikbaar in v5.1 Servers opslaan? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Welkomst bericht opslaan? @@ -3472,6 +3499,14 @@ Beschikbaar in v5.1 Deel chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Uitnodiging link delen @@ -3487,6 +3522,10 @@ Beschikbaar in v5.1 Eenmalige uitnodiging link delen No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Toon QR-code @@ -3537,6 +3576,10 @@ Beschikbaar in v5.1 SimpleX Vergrendelen ingeschakeld No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX contact adres @@ -3632,6 +3675,14 @@ Beschikbaar in v5.1 Bestand verzenden stoppen? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Indienen @@ -4228,6 +4279,10 @@ Om verbinding te maken, vraagt u uw contactpersoon om een andere verbinding link Wanneer beschikbaar No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. @@ -4320,9 +4375,13 @@ SimpleX Lock moet ingeschakeld zijn. U kunt een link of een QR-code delen. Iedereen kan lid worden van de groep. U verliest geen leden van de groep als u deze later verwijdert. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. U verliest uw contacten niet als u deze later verwijdert. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock moet ingeschakeld zijn. Je ontvangt geen berichten meer van deze groep. Je gesprek geschiedenis blijft behouden. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + U verliest uw contacten niet als u deze later verwijdert. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Je probeert een contact met wie je een incognito profiel hebt gedeeld, uit te nodigen voor de groep waarin je je hoofdprofiel gebruikt @@ -4462,7 +4526,7 @@ SimpleX Lock moet ingeschakeld zijn. Your SimpleX contact address - Uw SimpleX contact adres + Uw SimpleX adres No comment provided by engineer. @@ -4532,6 +4596,10 @@ U kunt deze verbinding verbreken en het contact verwijderen (en later proberen m Uw contacten kunnen volledige verwijdering van berichten toestaan. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Uw huidige chat database wordt VERWIJDERD en VERVANGEN door de geïmporteerde. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 78156e1239..3e71f25887 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -361,6 +361,10 @@ Dostępny w v5.1 O SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Kolor akcentu @@ -387,9 +391,8 @@ Dostępny w v5.1 Akceptuj incognito No comment provided by engineer. - - Accept requests - Akceptuj prośby + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Dostępny w v5.1 Dodaj wiadomość powitalną No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Administratorzy mogą tworzyć linki do dołączania do grup. @@ -447,9 +454,12 @@ Dostępny w v5.1 Wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Wiadomości zostaną usunięte TYLKO dla Ciebie. No comment provided by engineer. - - All your contacts will remain connected - Wszystkie Twoje kontakty pozostaną połączone + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Dostępny w v5.1 Uwierzytelnianie niedostępne No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Automatyczne akceptowanie próśb o kontakt @@ -622,11 +636,6 @@ Dostępny w v5.1 Automatyczne akceptowanie obrazów No comment provided by engineer. - - Automatically - Automatycznie - No comment provided by engineer. - Back Wstecz @@ -992,11 +1001,6 @@ Dostępny w v5.1 Preferencje kontaktu No comment provided by engineer. - - Contact requests - Prośby kontaktu - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Kontakty mogą oznaczać wiadomości do usunięcia; będziesz mógł je zobaczyć. @@ -1017,9 +1021,12 @@ Dostępny w v5.1 Utwórz No comment provided by engineer. - - Create address - Utwórz adres + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Dostępny w v5.1 Wprowadź serwer ręcznie No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Błąd @@ -2343,6 +2354,10 @@ Dostępny w v5.1 Duży plik! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Opuść @@ -2987,6 +3002,10 @@ Dostępny w v5.1 Hasło profilu No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Zabroń połączeń audio/wideo. @@ -3247,6 +3266,10 @@ Dostępny w v5.1 Zapisz archiwum No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Zapisz profil grupy @@ -3282,6 +3305,10 @@ Dostępny w v5.1 Zapisać serwery? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Zapisać wiadomość powitalną? @@ -3472,6 +3499,14 @@ Dostępny w v5.1 Udostępnij chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Udostępnij link zaproszenia @@ -3487,6 +3522,10 @@ Dostępny w v5.1 Jednorazowy link zaproszenia No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Pokaż kod QR @@ -3537,6 +3576,10 @@ Dostępny w v5.1 Blokada SimpleX włączona No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Adres kontaktowy SimpleX @@ -3632,6 +3675,14 @@ Dostępny w v5.1 Przestać wysyłać plik? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Zatwierdź @@ -4228,6 +4279,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Gdy dostępny No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi. @@ -4320,9 +4375,13 @@ Funkcja blokady SimpleX musi być włączona. Możesz udostępnić link lub kod QR - każdy będzie mógł dołączyć do grupy. Nie stracisz członków grupy, jeśli później ją usuniesz. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. Nie stracisz swoich kontaktów, jeśli później go usuniesz. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. No comment provided by engineer. @@ -4435,6 +4494,11 @@ Funkcja blokady SimpleX musi być włączona. Przestaniesz otrzymywać wiadomości od tej grupy. Historia czatu zostanie zachowana. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Nie stracisz swoich kontaktów, jeśli później go usuniesz. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Próbujesz zaprosić osobę, z którą masz wspólny profil incognito do grupy, w której używasz swojego głównego profilu @@ -4462,7 +4526,7 @@ Funkcja blokady SimpleX musi być włączona. Your SimpleX contact address - Twój adres kontaktowy SimpleX + Twój adres SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Możesz anulować to połączenie i usunąć kontakt (i spróbować później z Twoje kontakty mogą zezwolić na pełne usunięcie wiadomości. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Twoja obecna baza danych czatu zostanie usunięta i zastąpiona zaimportowaną. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 6bb8229a32..a0e4c600b1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -361,6 +361,10 @@ Available in v5.1 Информация о SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Основной цвет @@ -387,9 +391,8 @@ Available in v5.1 Принять инкогнито No comment provided by engineer. - - Accept requests - Принимать запросы + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Available in v5.1 Добавить приветственное сообщение No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Админы могут создать ссылки для вступления в группу. @@ -447,9 +454,12 @@ Available in v5.1 Все сообщения будут удалены - это действие нельзя отменить! Сообщения будут удалены только для Вас. No comment provided by engineer. - - All your contacts will remain connected - Все контакты, которые соединились через этот адрес, сохранятся. + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Available in v5.1 Аутентификация недоступна No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Автоматически принимать запросы контактов @@ -622,11 +636,6 @@ Available in v5.1 Автоприем изображений No comment provided by engineer. - - Automatically - Автоматически - No comment provided by engineer. - Back Назад @@ -992,11 +1001,6 @@ Available in v5.1 Предпочтения контакта No comment provided by engineer. - - Contact requests - Запросы контактов - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Контакты могут помечать сообщения для удаления; Вы сможете просмотреть их. @@ -1017,9 +1021,12 @@ Available in v5.1 Создать No comment provided by engineer. - - Create address - Создать адрес + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Available in v5.1 Ввести сервер вручную No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Ошибка @@ -2343,6 +2354,10 @@ Available in v5.1 Большой файл! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Выйти @@ -2987,6 +3002,10 @@ Available in v5.1 Пароль профиля No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Запретить аудио/видео звонки. @@ -3247,6 +3266,10 @@ Available in v5.1 Сохранить архив No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Сохранить профиль группы @@ -3282,6 +3305,10 @@ Available in v5.1 Сохранить серверы? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Сохранить приветственное сообщение? @@ -3472,6 +3499,14 @@ Available in v5.1 Поделиться chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Поделиться ссылкой @@ -3487,6 +3522,10 @@ Available in v5.1 Поделиться ссылкой-приглашением No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Показать QR код @@ -3537,6 +3576,10 @@ Available in v5.1 Блокировка SimpleX включена No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX ссылка-контакт @@ -3632,6 +3675,14 @@ Available in v5.1 Остановить отправку файла? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Продолжить @@ -4228,6 +4279,10 @@ To connect, please ask your contact to create another connection link and check Когда возможно No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом. @@ -4320,9 +4375,13 @@ SimpleX Lock must be enabled. Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. Вы сможете удалить адрес, сохранив контакты, которые через него соединились. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock must be enabled. Вы перестанете получать сообщения от этой группы. История чата будет сохранена. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Вы сможете удалить адрес, сохранив контакты, которые через него соединились. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль @@ -4532,6 +4596,10 @@ You can cancel this connection and remove the contact (and try later with a new Ваши контакты могут разрешить окончательное удаление сообщений. No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index a518d5f499..1a525f62b6 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -361,6 +361,10 @@ Available in v5.1 关于SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color 色调 @@ -387,9 +391,8 @@ Available in v5.1 接受隐身聊天 No comment provided by engineer. - - Accept requests - 接受请求 + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Available in v5.1 添加欢迎信息 No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. 管理员可以创建链接以加入群组。 @@ -447,9 +454,12 @@ Available in v5.1 所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。 No comment provided by engineer. - - All your contacts will remain connected - 您的所有联系人将保持连接 + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Available in v5.1 身份验证不可用 No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests 自动接受联系人请求 @@ -622,11 +636,6 @@ Available in v5.1 自动接受图片 No comment provided by engineer. - - Automatically - 自动 - No comment provided by engineer. - Back 返回 @@ -992,11 +1001,6 @@ Available in v5.1 联系人偏好设置 No comment provided by engineer. - - Contact requests - 联系人请求 - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. 联系人可以将信息标记为删除;您将可以查看这些信息。 @@ -1017,9 +1021,12 @@ Available in v5.1 创建 No comment provided by engineer. - - Create address - 创建地址 + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Available in v5.1 手动输入服务器 No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error 错误 @@ -2343,6 +2354,10 @@ Available in v5.1 大文件! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave 离开 @@ -2987,6 +3002,10 @@ Available in v5.1 个人资料密码 No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. 禁止音频/视频通话。 @@ -3247,6 +3266,10 @@ Available in v5.1 保存存档 No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile 保存群组资料 @@ -3282,6 +3305,10 @@ Available in v5.1 保存服务器? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? 保存欢迎信息? @@ -3472,6 +3499,14 @@ Available in v5.1 分享 chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link 分享邀请链接 @@ -3487,6 +3522,10 @@ Available in v5.1 分享一次性邀请链接 No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code 显示二维码 @@ -3537,6 +3576,10 @@ Available in v5.1 已开启 SimpleX 锁定 No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX 联系地址 @@ -3632,6 +3675,14 @@ Available in v5.1 停止发送文件? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit 提交 @@ -4228,6 +4279,10 @@ To connect, please ask your contact to create another connection link and check 当可用时 No comment provided by engineer. + + When people request to connect, you can accept or reject it. + No comment provided by engineer. + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. 当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。 @@ -4320,9 +4375,13 @@ SimpleX Lock must be enabled. 您可以共享链接或二维码——任何人都可以加入该群组。如果您稍后将其删除,您不会失去该组的成员。 No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 如果您以后删除它,您不会丢失您的联系人。 + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock must be enabled. 您将停止接收来自该群组的消息。聊天记录将被保留。 No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + 如果您以后删除它,您不会丢失您的联系人。 + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile 您正在尝试邀请与您共享隐身个人资料的联系人加入您使用主要个人资料的群组 @@ -4532,6 +4596,10 @@ You can cancel this connection and remove the contact (and try later with a new 您的联系人可以允许完全删除消息。 No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. 您当前的聊天数据库将被删除并替换为导入的数据库。 diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8bed72bb71..3d666c7578 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -104,7 +104,6 @@ 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */; }; 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; }; - 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; }; 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; }; 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */; }; @@ -116,7 +115,6 @@ 5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; }; 5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; }; 5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; }; - 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */; }; 5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; }; 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; }; 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; }; @@ -172,6 +170,9 @@ 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; + 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; + 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; + 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; }; 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; @@ -368,7 +369,6 @@ 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; - 5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = ""; }; 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListNavLink.swift; sourceTree = ""; }; 5CBD285529565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 5CBD285629565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -384,7 +384,6 @@ 5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = ""; }; - 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = ""; }; 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = ""; }; 5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = ""; }; @@ -439,6 +438,9 @@ 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; + 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; + 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; + 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactLearnMore.swift; sourceTree = ""; }; 64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; @@ -694,6 +696,7 @@ 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */, 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */, 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */, + 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */, ); path = NewChat; sourceTree = ""; @@ -709,8 +712,6 @@ 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */, 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, - 5CB924E327A8683A00ACCCDD /* UserAddress.swift */, - 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */, 5CB924E027A867BA00ACCCDD /* UserProfile.swift */, 5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, @@ -722,6 +723,8 @@ 18415845648CA4F5A8BCA272 /* UserProfilesView.swift */, 5C65F341297D3F3600B67AF3 /* VersionView.swift */, 5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */, + 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */, + 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */, ); path = UserSettings; sourceTree = ""; @@ -1069,7 +1072,6 @@ 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */, 5C029EAA283942EA004A9677 /* CallController.swift in Sources */, - 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */, 5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */, 5CC036E029C488D500C0EF20 /* HiddenProfileView.swift in Sources */, 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */, @@ -1088,8 +1090,8 @@ 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */, D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */, 5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */, + 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, - 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */, 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */, 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */, 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */, @@ -1126,6 +1128,7 @@ 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */, + 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */, 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */, 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */, 6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */, @@ -1137,6 +1140,7 @@ 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */, 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, + 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */, 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index b1021c5130..53ae7e8849 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -85,6 +85,7 @@ public enum ChatCommand { case apiCreateMyAddress(userId: Int64) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) + case apiSetProfileAddress(userId: Int64, on: Bool) case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) case apiAcceptContact(contactReqId: Int64) case apiRejectContact(contactReqId: Int64) @@ -189,6 +190,7 @@ public enum ChatCommand { case let .apiCreateMyAddress(userId): return "/_address \(userId)" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)" case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" @@ -290,6 +292,7 @@ public enum ChatCommand { case .apiCreateMyAddress: return "apiCreateMyAddress" case .apiDeleteMyAddress: return "apiDeleteMyAddress" case .apiShowMyAddress: return "apiShowMyAddress" + case .apiSetProfileAddress: return "apiSetProfileAddress" case .apiAddressAutoAccept: return "apiAddressAutoAccept" case .apiAcceptContact: return "apiAcceptContact" case .apiRejectContact: return "apiRejectContact" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a449af1c3c..f82bb844bc 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -33,6 +33,10 @@ public struct User: Decodable, NamedChat, Identifiable { activeUser || showNtfs } + public var addressShared: Bool { + profile.contactLink != nil + } + public static let sampleData = User( userId: 1, userContactId: 1, @@ -71,16 +75,24 @@ public typealias ContactName = String public typealias GroupName = String public struct Profile: Codable, NamedChat { - public init(displayName: String, fullName: String, image: String? = nil, preferences: Preferences? = nil) { + public init( + displayName: String, + fullName: String, + image: String? = nil, + contactLink: String? = nil, + preferences: Preferences? = nil + ) { self.displayName = displayName self.fullName = fullName self.image = image + self.contactLink = contactLink self.preferences = preferences } public var displayName: String public var fullName: String public var image: String? + public var contactLink: String? public var preferences: Preferences? public var localAlias: String { get { "" } } @@ -95,11 +107,20 @@ public struct Profile: Codable, NamedChat { } public struct LocalProfile: Codable, NamedChat { - public init(profileId: Int64, displayName: String, fullName: String, image: String? = nil, preferences: Preferences? = nil, localAlias: String) { + public init( + profileId: Int64, + displayName: String, + fullName: String, + image: String? = nil, + contactLink: String? = nil, + preferences: Preferences? = nil, + localAlias: String + ) { self.profileId = profileId self.displayName = displayName self.fullName = fullName self.image = image + self.contactLink = contactLink self.preferences = preferences self.localAlias = localAlias } @@ -108,6 +129,7 @@ public struct LocalProfile: Codable, NamedChat { public var displayName: String public var fullName: String public var image: String? + public var contactLink: String? public var preferences: Preferences? public var localAlias: String @@ -127,11 +149,11 @@ public struct LocalProfile: Codable, NamedChat { } public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias: String) -> LocalProfile { - LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences, localAlias: localAlias) + LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences, localAlias: localAlias) } public func fromLocalProfile (_ profile: LocalProfile) -> Profile { - Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences) + Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences) } public enum ChatType: String { @@ -1164,6 +1186,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } public var image: String? { get { profile.image } } + public var contactLink: String? { get { profile.contactLink } } public var localAlias: String { profile.localAlias } public var verified: Bool { activeConn.connectionCode != nil } @@ -1508,6 +1531,7 @@ public struct GroupMember: Identifiable, Decodable { } public var fullName: String { get { memberProfile.fullName } } public var image: String? { get { memberProfile.image } } + public var contactLink: String? { get { memberProfile.contactLink } } public var verified: Bool { activeConn?.connectionCode != nil } var directChatId: ChatId? { diff --git a/docs/ANDROID.md b/docs/ANDROID.md index 2b86ea9c34..55e6ef976f 100644 --- a/docs/ANDROID.md +++ b/docs/ANDROID.md @@ -1,3 +1,8 @@ +--- +title: Accessing files in Android app +revision: 07.02.2023 +--- + | 07.02.2023 | EN, [CZ](/docs/lang/cs/ANDROID.md), [FR](/docs/lang/fr/ANDROID.md) | # Accessing files in Android app diff --git a/docs/CLI.md b/docs/CLI.md index 52a5f01471..65de29e601 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,3 +1,8 @@ +--- +title: Terminal CLI +revision: 31.01.2023 +--- + | Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/CLI.md), [CZ](/docs/lang/cs/CLI.md) | # SimpleX Chat terminal (console) app for Linux/MacOS/Windows diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 2694396b62..ef58bfec04 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,3 +1,8 @@ +--- +title: Contributing guide +revision: 31.01.2023 +--- + | Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/CONTRIBUTING.md), [CZ](/docs/lang/cs/CONTRIBUTING.md) | # Contributing guide diff --git a/docs/SERVER.md b/docs/SERVER.md index 5c53e9add8..07594ca10c 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -1,3 +1,8 @@ +--- +title: Hosting your own SMP Server +revision: 31.01.2023 +--- + | Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) | # Hosting your own SMP Server diff --git a/docs/SIMPLEX.md b/docs/SIMPLEX.md index c00adafd20..7ed01efa3c 100644 --- a/docs/SIMPLEX.md +++ b/docs/SIMPLEX.md @@ -1,3 +1,8 @@ +--- +title: SimpleX platform +revision: 07.02.2023 +--- + | Updated 07.02.2023 | Languages: EN, [FR](/docs/lang/fr/SIMPLEX.md), [CZ](/docs/lang/cs/SIMPLEX.md) | # SimpleX platform - motivation and comparison diff --git a/docs/SQL.md b/docs/SQL.md index d320e36042..d5b469050d 100644 --- a/docs/SQL.md +++ b/docs/SQL.md @@ -1,3 +1,8 @@ +--- +title: Accessing messages in the database +revision: 31.01.2023 +--- + | Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/SQL.md), [CZ](/docs/lang/cs/SQL.md) | # Accessing messages in the database diff --git a/docs/TRANSLATIONS.md b/docs/TRANSLATIONS.md index e231610382..bb8764b148 100644 --- a/docs/TRANSLATIONS.md +++ b/docs/TRANSLATIONS.md @@ -1,3 +1,8 @@ +--- +title: Contributing translations to SimpleX Chat +revision: 19.03.2023 +--- + | 19.03.2023 | EN, [CZ](/docs/lang/cs/TRANSLATIONS.md), [FR](/docs/lang/fr/TRANSLATIONS.md) | # Contributing translations to SimpleX Chat diff --git a/docs/WEBRTC.md b/docs/WEBRTC.md index 850a288347..304f29d812 100644 --- a/docs/WEBRTC.md +++ b/docs/WEBRTC.md @@ -1,3 +1,8 @@ +--- +title: Using custom WebRTC ICE servers in SimpleX Chat +revision: 31.01.2023 +--- + | Updated 31.01.2023 | Languages: EN, [FR](/docs/lang/fr/WEBRTC.md), [CZ](/docs/lang/cs/WEBRTC.md) | # Using custom WebRTC ICE servers in SimpleX Chat diff --git a/docs/XFTP-SERVER.md b/docs/XFTP-SERVER.md index 1359616ed1..567ae63b01 100644 --- a/docs/XFTP-SERVER.md +++ b/docs/XFTP-SERVER.md @@ -1,3 +1,7 @@ +--- +title: Hosting your own XFTP Server +revision: 21.04.2023 +--- # Hosting your own XFTP Server ## Overview diff --git a/docs/guide/README.md b/docs/guide/README.md index 7836b0b406..04e7538968 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -1,3 +1,6 @@ +--- +title: Quick start +--- # SimpleX Chat User Guide The first messaging platform that has no user identifiers of any kind — 100% private by design. diff --git a/docs/guide/app-settings.md b/docs/guide/app-settings.md index 90adf335bf..26817a20f2 100644 --- a/docs/guide/app-settings.md +++ b/docs/guide/app-settings.md @@ -1,3 +1,6 @@ +--- +title: App settings +--- # App settings ## Opening the app settings diff --git a/docs/guide/audio-video-calls.md b/docs/guide/audio-video-calls.md index 23101d2d63..8fc2bf95b2 100644 --- a/docs/guide/audio-video-calls.md +++ b/docs/guide/audio-video-calls.md @@ -1,3 +1,6 @@ +--- +title: Audio & video Calls +--- # Audio and Video Calls SimpleX Chat allows you to make end-to-end encrypted audio and video calls with your contacts via WebRTC. Note: Group calls are not supported at this time. diff --git a/docs/guide/chat-profiles.md b/docs/guide/chat-profiles.md index 9cede5b8c6..d20790037a 100644 --- a/docs/guide/chat-profiles.md +++ b/docs/guide/chat-profiles.md @@ -1,3 +1,6 @@ +--- +title: Chat profiles +--- # Your chat profiles ## Creating additional chat profiles diff --git a/docs/guide/making-connections.md b/docs/guide/making-connections.md index 55465f1f60..0cf46aecde 100644 --- a/docs/guide/making-connections.md +++ b/docs/guide/making-connections.md @@ -1,3 +1,6 @@ +--- +title: Making connections +--- # Connect to somebody in the chat _Work in progress_ diff --git a/docs/guide/managing-data.md b/docs/guide/managing-data.md index 3324189cf7..c524825746 100644 --- a/docs/guide/managing-data.md +++ b/docs/guide/managing-data.md @@ -1,3 +1,6 @@ +--- +title: Managing data +--- # Managing Your Data ## Automatic message deletion diff --git a/docs/guide/privacy-security.md b/docs/guide/privacy-security.md index 43ece140ad..849a17f65d 100644 --- a/docs/guide/privacy-security.md +++ b/docs/guide/privacy-security.md @@ -1,3 +1,6 @@ +--- +title: Privacy & security +--- # Privacy and Security SimpleX Chat default configuration aims to balance privacy, security and convenience. You may want to change the default options. diff --git a/docs/guide/secret-groups.md b/docs/guide/secret-groups.md index 9ecdc7e4c5..5cdbcf86da 100644 --- a/docs/guide/secret-groups.md +++ b/docs/guide/secret-groups.md @@ -1,3 +1,6 @@ +--- +title: Secret groups +--- # Secret groups Secret groups are anonymous and private, they are designed to be hard to track by outsiders. Every message and file gets sent separately to every member in the group, which makes secret groups only fit for smaller groups. diff --git a/docs/guide/send-messages.md b/docs/guide/send-messages.md index 9762e86f43..8292bd5747 100644 --- a/docs/guide/send-messages.md +++ b/docs/guide/send-messages.md @@ -1,3 +1,6 @@ +--- +title: Sending messages +--- # Sending, editing and deleting messages Sending text messages on SimpleX Chat is pretty straightforward, tap on your contact and then tap on text area, type in your message and tap on "Send" button. diff --git a/docs/lang/cs/ANDROID.md b/docs/lang/cs/ANDROID.md index c0fb859cac..38dd105bb5 100644 --- a/docs/lang/cs/ANDROID.md +++ b/docs/lang/cs/ANDROID.md @@ -1,3 +1,7 @@ +--- +title: Přístup k souborům v aplikaci Android +revision: 07.02.2023 +--- | Aktualizováno 07.02.2023 | Jazyky: CZ, [EN](/docs/ANDROID.md) | # Přístup k souborům v aplikaci Android diff --git a/docs/lang/cs/CLI.md b/docs/lang/cs/CLI.md index f6b9dbe0ed..731e236661 100644 --- a/docs/lang/cs/CLI.md +++ b/docs/lang/cs/CLI.md @@ -1,3 +1,7 @@ +--- +title: SimpleX Chat terminálová +revision: 31.01.2023 +--- | Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/CLI.md), [FR](/docs/lang/fr/CLI.md) | # SimpleX Chat terminálová (konzolová) aplikace pro Linux/MacOS/Windows @@ -174,7 +178,7 @@ Po spuštění chatu budete vyzváni k zadání svého "zobrazovacího jména" a Následující schéma ukazuje, jak se připojit ke kontaktu a poslat mu zprávu:
- +
Po nastavení místního profilu zadejte `/c` (pro `/connect`) pro vytvoření nového spojení a vygenerování pozvánky. Tuto pozvánku odešlete svému kontaktu prostřednictvím jakéhokoli jiného kanálu. @@ -193,7 +197,7 @@ Seznam dostupných příkazů zobrazíte pomocí `/help` v chatu. Skupinu vytvoříte příkazem `/g ` a kontakty do ní přidáte příkazem `/a `. Do skupiny pak můžete posílat zprávy zadáním `# `. Pro další příkazy použijte `/help groups`. -![simplex-chat](../images/groups.gif) +![simplex-chat](/images/groups.gif) > **Upozornění**: skupiny nejsou uloženy na žádném serveru, jsou vedeny jako seznam členů v databázi aplikace, kterým budou zprávy zasílány. @@ -201,7 +205,7 @@ Skupinu vytvoříte příkazem `/g ` a kontakty do ní přidáte příkaz Soubor můžete odeslat kontaktu pomocí `/f @ ` - příjemce jej bude muset před odesláním přijmout. Pro další příkazy použijte `/help files`. -![simplex-chat](../images/files.gif) +![simplex-chat](/images/files.gif) Soubory můžete posílat skupině pomocí `/f # `. @@ -215,7 +219,7 @@ Uživatelská adresa je "dlouhodobá" v tom smyslu, že se jedná o odkaz pro v Pro ostatní příkazy použijte `/help address`. -![simplex-chat](../images/user-addresses.gif) +![simplex-chat](/images/user-addresses.gif) ### Přístup k historii chatu diff --git a/docs/lang/cs/CONTRIBUTING.md b/docs/lang/cs/CONTRIBUTING.md index 7fc14f5a9c..26c746e7d2 100644 --- a/docs/lang/cs/CONTRIBUTING.md +++ b/docs/lang/cs/CONTRIBUTING.md @@ -1,3 +1,7 @@ +--- +title: Průvodce přispíváním +revision: 31.01.2023 +--- | Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/CONTRIBUTING.md), [FR](/docs/lang/fr/CONTRIBUTING.md) | # Průvodce přispíváním diff --git a/docs/lang/cs/SERVER.md b/docs/lang/cs/SERVER.md index ba7dc3efd7..1620ec2e1a 100644 --- a/docs/lang/cs/SERVER.md +++ b/docs/lang/cs/SERVER.md @@ -1,3 +1,7 @@ +--- +title: Hostování vlastního serveru SMP +revision: 31.01.2023 +--- | Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/SERVER.md), [FR](/docs/lang/fr/SERVER.md) | # Hostování vlastního serveru SMP @@ -327,4 +331,4 @@ Adresu svého serveru můžete také sdílet se svými přáteli tak, že je nec _Upozornění_: pro podporu hesla je třeba mít SMP server verze 4.0. Pokud již máte nasazený server, můžete heslo přidat přidáním do souboru INI serveru. -       +       diff --git a/docs/lang/cs/SIMPLEX.md b/docs/lang/cs/SIMPLEX.md index 2727d2b9d9..33d45cec65 100644 --- a/docs/lang/cs/SIMPLEX.md +++ b/docs/lang/cs/SIMPLEX.md @@ -1,3 +1,7 @@ +--- +title: Platforma SimpleX +revision: 07.02.2023 +--- | Aktualizováno 07.02.2023 | Jazyky: CZ, [EN](/docs/SIMPLEX.md), [FR](/docs/lang/fr/SIMPLEX.md) | # Platforma SimpleX - motivace a srovnání diff --git a/docs/lang/cs/SQL.md b/docs/lang/cs/SQL.md index d9ea8f2365..71ef0007c1 100644 --- a/docs/lang/cs/SQL.md +++ b/docs/lang/cs/SQL.md @@ -1,3 +1,7 @@ +--- +title: Přístup ke zprávám v databázi +revision: 31.01.2023 +--- | Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/SQL.md), [FR](/docs/lang/fr/SQL.md) | # Přístup ke zprávám v databázi diff --git a/docs/lang/cs/TRANSLATIONS.md b/docs/lang/cs/TRANSLATIONS.md index 0cdd13a2b0..b260bd12a6 100644 --- a/docs/lang/cs/TRANSLATIONS.md +++ b/docs/lang/cs/TRANSLATIONS.md @@ -1,3 +1,7 @@ +--- +title: Přispívání překladů do SimpleX Chat +revision: 07.02.2023 +--- | Aktualizováno 07.02.2023 | Jazyky: CZ, [EN](/docs/TRANSLATIONS.md) | # Přispívání překladů do SimpleX Chat @@ -22,7 +26,7 @@ Tento dokument vznikl proto, abychom tento proces urychlili, a podělili se s v 2. Některé řetězce není třeba překládat, ale přesto je třeba je překopírovat - v uživatelském rozhraní Weblate je k tomu tlačítko: -weblate: zkopírovat zdroj do překladu +weblate: zkopírovat zdroj do překladu 3. Weblate má také automatické návrhy, které mohou proces urychlit. Někdy je lze použít tak, jak jsou, jindy je třeba je upravit - kliknutím je použijete v překladu. @@ -30,7 +34,7 @@ Tento dokument vznikl proto, abychom tento proces urychlili, a podělili se s v 5. Při překladu [aplikace pro iOS](https://hosted.weblate.org/projects/simplex-chat/ios/) je velká část řetězců naprosto stejná - lze je jedním kliknutím překopírovat do sekce glosář. Vizuální nápovědou, že to lze provést, je to, že celý zdrojový řetězec je zvýrazněn žlutě. Mnoho dalších řetězců je velmi podobných, liší se pouze syntaxí interpolace nebo způsobem použití tučného písma - vyžadují minimální úpravy. Existují některé řetězce, které jsou jedinečné pro platformu iOS - ty je třeba přeložit zvlášť. -weblate: automatické návrhy +weblate: automatické návrhy ## Po dokončení překladu diff --git a/docs/lang/cs/WEBRTC.md b/docs/lang/cs/WEBRTC.md index 3a9dc30a86..64ebc91093 100644 --- a/docs/lang/cs/WEBRTC.md +++ b/docs/lang/cs/WEBRTC.md @@ -1,3 +1,7 @@ +--- +title: Použití vlastních serverů WebRTC ICE v SimpleX Chat +revision: 31.01.2023 +--- | Aktualizováno 31.01.2023 | Jazyky: CZ, [EN](/docs/WEBRTC.md), [FR](/docs/lang/fr/WEBRTC.md) | # Použití vlastních serverů WebRTC ICE v SimpleX Chat @@ -124,7 +128,7 @@ To je vše - nyní můžete uskutečňovat audio a video hovory prostřednictví 2. Do části **Sestavit seznam serverů ICE** přidejte: - . + . - `STUN: stun::` a stiskněte `Add STUN`. - `TURN: turn::`, `Username: `, `Credential: ` a stiskněte `Add TURN` @@ -133,10 +137,10 @@ To je vše - nyní můžete uskutečňovat audio a video hovory prostřednictví 3. Měli byste vidět své servery v sekci **ICE server list**. Pokud je vše správně nastaveno, stiskněte `Start test`: - + 4. V části **Výsledky** byste měli vidět něco takového: - + Pokud výsledky zobrazují kandidáty `srflx` a `relay`, je vše nastaveno správně! diff --git a/docs/lang/fr/ANDROID.md b/docs/lang/fr/ANDROID.md index 6af2de5ee9..905af50152 100644 --- a/docs/lang/fr/ANDROID.md +++ b/docs/lang/fr/ANDROID.md @@ -1,3 +1,7 @@ +--- +title: Accès aux fichiers dans l'application Android +revision: 07.02.2023 +--- | 07.02.2023 | FR, [EN](/docs/ANDROID.md), [CZ](/docs/lang/cs/ANDROID.md) | # Accès aux fichiers dans l'application Android diff --git a/docs/lang/fr/CLI.md b/docs/lang/fr/CLI.md index 289e9d9722..2c4a88e2d7 100644 --- a/docs/lang/fr/CLI.md +++ b/docs/lang/fr/CLI.md @@ -1,3 +1,7 @@ +--- +title: Application de terminal +revision: 31.01.2023 +--- | 31.01.2023 | FR, [EN](/docs/CLI.md), [CZ](/docs/lang/cs/CLI.md) | # Application de terminal (console) SimpleX Chat pour Linux/MacOS/Windows diff --git a/docs/lang/fr/CONTRIBUTING.md b/docs/lang/fr/CONTRIBUTING.md index c9965e0240..81515b09b2 100644 --- a/docs/lang/fr/CONTRIBUTING.md +++ b/docs/lang/fr/CONTRIBUTING.md @@ -1,3 +1,7 @@ +--- +title: Guide pour contribuer +revision: 31.01.2023 +--- | 31.01.2023 | FR, [EN](/docs/CONTRIBUTING.md), [CZ](/docs/lang/cs/CONTRIBUTING.md) | # Guide pour contribuer diff --git a/docs/lang/fr/SERVER.md b/docs/lang/fr/SERVER.md index 7bbffd772d..6f9c7c5990 100644 --- a/docs/lang/fr/SERVER.md +++ b/docs/lang/fr/SERVER.md @@ -1,3 +1,7 @@ +--- +title: Héberger votre propre serveur SMP +revision: 31.01.2023 +--- | 31.01.2023 | FR, [EN](/docs/SERVER.md), [CZ](/docs/lang/cs/SERVER.md) | # Héberger votre propre serveur SMP diff --git a/docs/lang/fr/SIMPLEX.md b/docs/lang/fr/SIMPLEX.md index 10c55ee05b..a7134205d0 100644 --- a/docs/lang/fr/SIMPLEX.md +++ b/docs/lang/fr/SIMPLEX.md @@ -1,3 +1,7 @@ +--- +title: Plateforme SimpleX +revision: 07.02.2023 +--- | 07.02.2023 | FR, [EN](/docs/SIMPLEX.md), [CZ](/docs/lang/cs/SIMPLEX.md) | # Plateforme SimpleX - motivation et comparaison diff --git a/docs/lang/fr/SQL.md b/docs/lang/fr/SQL.md index d1f19abd58..d141153d65 100644 --- a/docs/lang/fr/SQL.md +++ b/docs/lang/fr/SQL.md @@ -1,3 +1,7 @@ +--- +title: Accès aux messages de la base de données +revision: 31.01.2023 +--- | 31.01.2023 | FR, [EN](/docs/SQL.md), [CZ](/docs/lang/cs/SQL.md) | # Accès aux messages de la base de données diff --git a/docs/lang/fr/TRANSLATIONS.md b/docs/lang/fr/TRANSLATIONS.md index 7eb7969daa..e85a4a8513 100644 --- a/docs/lang/fr/TRANSLATIONS.md +++ b/docs/lang/fr/TRANSLATIONS.md @@ -1,3 +1,7 @@ +--- +title: Contribuer aux traductions de SimpleX Chat +revision: 19.03.2023 +--- | 19.03.2023 | FR, [EN](/docs/TRANSLATIONS.md), [CZ](/docs/lang/cs/TRANSLATIONS.md) | # Contribuer aux traductions de SimpleX Chat diff --git a/docs/lang/fr/WEBRTC.md b/docs/lang/fr/WEBRTC.md index 48f529d6ba..e06ca46c18 100644 --- a/docs/lang/fr/WEBRTC.md +++ b/docs/lang/fr/WEBRTC.md @@ -1,3 +1,7 @@ +--- +title: Utilisation de serveurs WebRTC ICE personnalisés dans SimpleX Chat +revision: 31.01.2023 +--- | 31.01.2023 | FR, [EN](/docs/WEBRTC.md), [CZ](/docs/lang/cs/WEBRTC.md) | # Utilisation de serveurs WebRTC ICE personnalisés dans SimpleX Chat diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index 0dd61d2921..95069c794c 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -1,3 +1,7 @@ +--- +title: SimpleX Chat Protocol +revision: 08.08.2022 +--- DRAFT Revision 0.1, 2022-08-08 Evgeny Poberezkin diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7cfe9ead06..995f5c3cb6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -92,6 +92,7 @@ library Simplex.Chat.Migrations.M20230402_protocol_servers Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions Simplex.Chat.Migrations.M20230420_rcv_files_to_receive + Simplex.Chat.Migrations.M20230422_profile_contact_links Simplex.Chat.Mobile Simplex.Chat.Mobile.WebRTC Simplex.Chat.Options diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 12e7c63455..2cf37abb54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -453,7 +453,9 @@ processChatCommand = \case pure $ CRApiChat user (AChat SCTGroup groupChat) CTContactRequest -> pure $ chatCmdError (Just user) "not implemented" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" - APIGetChatItems _pagination -> pure $ chatCmdError Nothing "not implemented" + APIGetChatItems pagination search -> withUser $ \user -> do + chatItems <- withStore $ \db -> getAllChatItems db user pagination search + pure $ CRChatItems user chatItems APISendMessage (ChatRef cType chatId) live (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user@User {userId} -> withChatLock "sendMessage" $ case cType of CTDirect -> do ct@Contact {contactId, localDisplayName = c, contactUsed} <- withStore $ \db -> getContact db user chatId @@ -1121,18 +1123,32 @@ processChatCommand = \case pure $ CRUserContactLinkCreated user cReq CreateMyAddress -> withUser $ \User {userId} -> processChatCommand $ APICreateMyAddress userId - APIDeleteMyAddress userId -> withUserId userId $ \user -> withChatLock "deleteMyAddress" $ do + APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do conns <- withStore (`getUserAddressConnections` user) - procCmd $ do + withChatLock "deleteMyAddress" $ do deleteAgentConnectionsAsync user $ map aConnId conns withStore' (`deleteUserAddress` user) - pure $ CRUserContactLinkDeleted user + let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} + r <- updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user Nothing + let user' = case r of + CRUserProfileUpdated u' _ _ -> u' + _ -> user + pure $ CRUserContactLinkDeleted user' DeleteMyAddress -> withUser $ \User {userId} -> processChatCommand $ APIDeleteMyAddress userId APIShowMyAddress userId -> withUserId userId $ \user -> CRUserContactLink user <$> withStore (`getUserAddress` user) ShowMyAddress -> withUser $ \User {userId} -> processChatCommand $ APIShowMyAddress userId + APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do + let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} + updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user Nothing + APISetProfileAddress userId True -> withUserId userId $ \user@User {profile = p} -> do + ucl@UserContactLink {connReqContact} <- withStore (`getUserAddress` user) + let p' = (fromLocalProfile p :: Profile) {contactLink = Just connReqContact} + updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user $ Just ucl + SetProfileAddress onOff -> withUser $ \User {userId} -> + processChatCommand $ APISetProfileAddress userId onOff APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do contactLink <- withStore (\db -> updateUserAddressAutoAccept db user autoAccept_) pure $ CRUserContactLinkUpdated user contactLink @@ -1611,7 +1627,9 @@ processChatCommand = \case | chunks <= sendChunks && chunks * n <= totalSendChunks && isVoice mc = Just IFMSent | otherwise = Just IFMOffer updateProfile :: User -> Profile -> m ChatResponse - updateProfile user@User {profile = p} p' + updateProfile user p' = updateProfile_ user p' $ withStore $ \db -> updateUserProfile db user p' + updateProfile_ :: User -> Profile -> m User -> m ChatResponse + updateProfile_ user@User {profile = p} p' updateUser | p' == fromLocalProfile p = pure $ CRUserProfileNoChange user | otherwise = do -- read contacts before user update to correctly merge preferences @@ -1619,7 +1637,7 @@ processChatCommand = \case contacts <- filter (\ct -> isReady ct && not (contactConnIncognito ct)) <$> withStore' (`getUserContacts` user) - user' <- withStore $ \db -> updateUserProfile db user p' + user' <- updateUser asks currentUser >>= atomically . (`writeTVar` Just user') withChatLock "updateProfile" . procCmd $ do forM_ contacts $ \ct -> do @@ -4405,7 +4423,7 @@ getCreateActiveUser st = do loop = do displayName <- getContactName fullName <- T.pack <$> getWithPrompt "full name (optional)" - withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, preferences = Nothing} True) >>= \case + withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} True) >>= \case Left SEDuplicateName -> do putStrLn "chosen display name is already used by another profile on this device, choose another one" loop @@ -4565,7 +4583,7 @@ chatCommandP = "/sql agent " *> (ExecAgentStoreSQL <$> textP), "/_get chats " *> (APIGetChats <$> A.decimal <*> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)), "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP <*> optional (" search=" *> stringP)), - "/_get items count=" *> (APIGetChatItems <$> A.decimal), + "/_get items " *> (APIGetChatItems <$> chatPaginationP <*> optional (" search=" *> stringP)), "/_send " *> (APISendMessage <$> chatRefP <*> liveMessageP <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), "/_update item " *> (APIUpdateChatItem <$> chatRefP <* A.space <*> A.decimal <*> liveMessageP <* A.space <*> msgContentP), "/_delete item " *> (APIDeleteChatItem <$> chatRefP <* A.space <*> A.decimal <* A.space <*> ciDeleteMode), @@ -4711,6 +4729,8 @@ chatCommandP = ("/delete_address" <|> "/da") $> DeleteMyAddress, "/_show_address " *> (APIShowMyAddress <$> A.decimal), ("/show_address" <|> "/sa") $> ShowMyAddress, + "/_profile_address " *> (APISetProfileAddress <$> A.decimal <* A.space <*> onOffP), + ("/profile_address " <|> "/pa ") *> (SetProfileAddress <$> onOffP), "/_auto_accept " *> (APIAddressAutoAccept <$> A.decimal <* A.space <*> autoAcceptP), "/auto_accept " *> (AddressAutoAccept <$> autoAcceptP), ("/accept " <|> "/ac ") *> char_ '@' *> (AcceptContact <$> displayName), @@ -4764,7 +4784,7 @@ chatCommandP = pure (cName, fullName) userProfile = do (cName, fullName) <- userNames - pure Profile {displayName = cName, fullName, image = Nothing, preferences = Nothing} + pure Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} jsonP :: J.FromJSON a => Parser a jsonP = J.eitherDecodeStrict' <$?> A.takeByteString groupProfile = do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 03f04166ed..54a780384e 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -212,7 +212,7 @@ data ChatCommand | ExecAgentStoreSQL Text | APIGetChats {userId :: UserId, pendingConnections :: Bool} | APIGetChat ChatRef ChatPagination (Maybe String) - | APIGetChatItems Int + | APIGetChatItems ChatPagination (Maybe String) | APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, composedMessage :: ComposedMessage} | APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent} | APIDeleteChatItem ChatRef ChatItemId CIDeleteMode @@ -304,6 +304,8 @@ data ChatCommand | DeleteMyAddress | APIShowMyAddress UserId | ShowMyAddress + | APISetProfileAddress UserId Bool + | SetProfileAddress Bool | APIAddressAutoAccept UserId (Maybe AutoAccept) | AddressAutoAccept (Maybe AutoAccept) | AcceptContact ContactName diff --git a/src/Simplex/Chat/Help.hs b/src/Simplex/Chat/Help.hs index 37770d1bd1..1f77617af1 100644 --- a/src/Simplex/Chat/Help.hs +++ b/src/Simplex/Chat/Help.hs @@ -201,6 +201,7 @@ myAddressHelpInfo = styleMarkdown [ green "Your contact address commands:", indent <> highlight "/address " <> " - create your address", + indent <> highlight "/profile_address on/off " <> " - share address with your contacts (it will be added to your profile)", indent <> highlight "/delete_address " <> " - delete your address (accepted contacts will remain connected)", indent <> highlight "/show_address " <> " - show your address", indent <> highlight "/accept " <> " - accept contact request", diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 7ef3534cbe..584840cd1b 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -30,7 +30,10 @@ import Data.Time.LocalTime (TimeZone, ZonedTime, utcToZonedTime) import Data.Type.Equality import Data.Typeable (Typeable) import Data.Word (Word32) -import Database.SQLite.Simple.FromField (FromField (..)) +import Database.SQLite.Simple (ResultError (..), SQLData (..)) +import Database.SQLite.Simple.FromField (Field, FromField (..), returnError) +import Database.SQLite.Simple.Internal (Field (..)) +import Database.SQLite.Simple.Ok import Database.SQLite.Simple.ToField (ToField (..)) import GHC.Generics (Generic) import Simplex.Chat.Markdown @@ -299,6 +302,9 @@ aChatItems (AChat ct Chat {chatInfo, chatItems}) = map aChatItem chatItems aChatItemId :: AChatItem -> Int64 aChatItemId (AChatItem _ _ _ ci) = chatItemId' ci +aChatItemTs :: AChatItem -> UTCTime +aChatItemTs (AChatItem _ _ _ ci) = chatItemTs' ci + updateFileStatus :: forall c d. ChatItem c d -> CIFileStatus d -> ChatItem c d updateFileStatus ci@ChatItem {file} status = case file of Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}} @@ -620,7 +626,7 @@ instance StrEncoding ACIStatus where "snd_new" -> pure $ ACIStatus SMDSnd CISSndNew "snd_sent" -> pure $ ACIStatus SMDSnd CISSndSent "snd_error_auth" -> pure $ ACIStatus SMDSnd CISSndErrorAuth - "snd_error " -> ACIStatus SMDSnd . CISSndError . T.unpack . safeDecodeUtf8 <$> A.takeByteString + "snd_error" -> ACIStatus SMDSnd . CISSndError . T.unpack . safeDecodeUtf8 <$> (A.space *> A.takeByteString) "rcv_new" -> pure $ ACIStatus SMDRcv CISRcvNew "rcv_read" -> pure $ ACIStatus SMDRcv CISRcvRead _ -> fail "bad status" @@ -743,6 +749,7 @@ data CIContent (d :: MsgDirection) where CIRcvGroupFeatureRejected :: GroupFeature -> CIContent 'MDRcv CISndModerated :: CIContent 'MDSnd CIRcvModerated :: CIContent 'MDRcv + CIInvalidJSON :: Text -> CIContent d -- ^ This type is used both in API and in DB, so we use different JSON encodings for the database and for the API -- ! ^ Nested sum types also have to use different encodings for database and API -- ! ^ to avoid breaking cross-platform compatibility, see RcvGroupEvent and SndGroupEvent @@ -787,6 +794,7 @@ ciRequiresAttention content = case msgDirection @d of CIRcvChatFeatureRejected _ -> True CIRcvGroupFeatureRejected _ -> True CIRcvModerated -> True + CIInvalidJSON _ -> False ciCreateStatus :: forall d. MsgDirectionI d => CIContent d -> CIStatus d ciCreateStatus content = case msgDirection @d of @@ -949,6 +957,7 @@ ciContentToText = \case CIRcvGroupFeatureRejected feature -> groupFeatureNameText feature <> ": received, prohibited" CISndModerated -> ciModeratedText CIRcvModerated -> ciModeratedText + CIInvalidJSON _ -> "invalid content JSON" msgIntegrityError :: MsgErrorType -> Text msgIntegrityError = \case @@ -976,11 +985,11 @@ ciModeratedText :: Text ciModeratedText = "moderated" -- platform independent -instance ToField (CIContent d) where +instance MsgDirectionI d => ToField (CIContent d) where toField = toField . encodeJSON . dbJsonCIContent -- platform specific -instance ToJSON (CIContent d) where +instance MsgDirectionI d => ToJSON (CIContent d) where toJSON = J.toJSON . jsonCIContent toEncoding = J.toEncoding . jsonCIContent @@ -988,13 +997,14 @@ data ACIContent = forall d. MsgDirectionI d => ACIContent (SMsgDirection d) (CIC deriving instance Show ACIContent +-- platform independent +dbParseACIContent :: Text -> Either String ACIContent +dbParseACIContent = fmap aciContentDBJSON . J.eitherDecodeStrict' . encodeUtf8 + -- platform specific instance FromJSON ACIContent where parseJSON = fmap aciContentJSON . J.parseJSON --- platform independent -instance FromField ACIContent where fromField = fromTextField_ $ fmap aciContentDBJSON . decodeJSON - -- platform specific data JSONCIContent = JCISndMsgContent {msgContent :: MsgContent} @@ -1021,6 +1031,7 @@ data JSONCIContent | JCIRcvGroupFeatureRejected {groupFeature :: GroupFeature} | JCISndModerated | JCIRcvModerated + | JCIInvalidJSON {direction :: MsgDirection, json :: Text} deriving (Generic) instance FromJSON JSONCIContent where @@ -1030,7 +1041,7 @@ instance ToJSON JSONCIContent where toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCI" toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCI" -jsonCIContent :: CIContent d -> JSONCIContent +jsonCIContent :: forall d. MsgDirectionI d => CIContent d -> JSONCIContent jsonCIContent = \case CISndMsgContent mc -> JCISndMsgContent mc CIRcvMsgContent mc -> JCIRcvMsgContent mc @@ -1056,6 +1067,7 @@ jsonCIContent = \case CIRcvGroupFeatureRejected groupFeature -> JCIRcvGroupFeatureRejected {groupFeature} CISndModerated -> JCISndModerated CIRcvModerated -> JCISndModerated + CIInvalidJSON json -> JCIInvalidJSON (toMsgDirection $ msgDirection @d) json aciContentJSON :: JSONCIContent -> ACIContent aciContentJSON = \case @@ -1083,6 +1095,8 @@ aciContentJSON = \case JCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature JCISndModerated -> ACIContent SMDSnd CISndModerated JCIRcvModerated -> ACIContent SMDRcv CIRcvModerated + JCIInvalidJSON dir json -> case fromMsgDirection dir of + AMsgDirection d -> ACIContent d $ CIInvalidJSON json -- platform independent data DBJSONCIContent @@ -1110,6 +1124,7 @@ data DBJSONCIContent | DBJCIRcvGroupFeatureRejected {groupFeature :: GroupFeature} | DBJCISndModerated | DBJCIRcvModerated + | DBJCIInvalidJSON {direction :: MsgDirection, json :: Text} deriving (Generic) instance FromJSON DBJSONCIContent where @@ -1119,7 +1134,7 @@ instance ToJSON DBJSONCIContent where toJSON = J.genericToJSON . singleFieldJSON $ dropPrefix "DBJCI" toEncoding = J.genericToEncoding . singleFieldJSON $ dropPrefix "DBJCI" -dbJsonCIContent :: CIContent d -> DBJSONCIContent +dbJsonCIContent :: forall d. MsgDirectionI d => CIContent d -> DBJSONCIContent dbJsonCIContent = \case CISndMsgContent mc -> DBJCISndMsgContent mc CIRcvMsgContent mc -> DBJCIRcvMsgContent mc @@ -1145,6 +1160,7 @@ dbJsonCIContent = \case CIRcvGroupFeatureRejected groupFeature -> DBJCIRcvGroupFeatureRejected {groupFeature} CISndModerated -> DBJCISndModerated CIRcvModerated -> DBJCIRcvModerated + CIInvalidJSON json -> DBJCIInvalidJSON (toMsgDirection $ msgDirection @d) json aciContentDBJSON :: DBJSONCIContent -> ACIContent aciContentDBJSON = \case @@ -1172,6 +1188,8 @@ aciContentDBJSON = \case DBJCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature DBJCISndModerated -> ACIContent SMDSnd CISndModerated DBJCIRcvModerated -> ACIContent SMDRcv CIRcvModerated + DBJCIInvalidJSON dir json -> case fromMsgDirection dir of + AMsgDirection d -> ACIContent d $ CIInvalidJSON json data CICallStatus = CISCallPending @@ -1264,8 +1282,18 @@ instance ToJSON MsgDirection where toJSON = J.genericToJSON . enumJSON $ dropPrefix "MD" toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "MD" +instance FromField AMsgDirection where fromField = fromIntField_ $ fmap fromMsgDirection . msgDirectionIntP + instance ToField MsgDirection where toField = toField . msgDirectionInt +fromIntField_ :: (Typeable a) => (Int64 -> Maybe a) -> Field -> Ok a +fromIntField_ fromInt = \case + f@(Field (SQLInteger i) _) -> + case fromInt i of + Just x -> Ok x + _ -> returnError ConversionFailed f ("invalid integer: " <> show i) + f -> returnError ConversionFailed f "expecting SQLInteger column type" + data SMsgDirection (d :: MsgDirection) where SMDRcv :: SMsgDirection 'MDRcv SMDSnd :: SMsgDirection 'MDSnd diff --git a/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs b/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs new file mode 100644 index 0000000000..ee7ff053d5 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20230422_profile_contact_links where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20230422_profile_contact_links :: Query +m20230422_profile_contact_links = + [sql| +ALTER TABLE contact_profiles ADD COLUMN contact_link BLOB; +|] + +down_m20230422_profile_contact_links :: Query +down_m20230422_profile_contact_links = + [sql| +ALTER TABLE contact_profiles DROP COLUMN contact_link; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 5f24c8a3c3..c14ed3d211 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -17,7 +17,8 @@ CREATE TABLE contact_profiles( user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE, incognito INTEGER, local_alias TEXT DEFAULT '' CHECK(local_alias NOT NULL), - preferences TEXT + preferences TEXT, + contact_link BLOB ); CREATE INDEX contact_profiles_index ON contact_profiles( display_name, diff --git a/src/Simplex/Chat/ProfileGenerator.hs b/src/Simplex/Chat/ProfileGenerator.hs index 59c6b8a10c..55a051ad80 100644 --- a/src/Simplex/Chat/ProfileGenerator.hs +++ b/src/Simplex/Chat/ProfileGenerator.hs @@ -10,7 +10,7 @@ generateRandomProfile :: IO Profile generateRandomProfile = do adjective <- pick adjectives noun <- pickNoun adjective 2 - pure $ Profile {displayName = adjective <> noun, fullName = "", image = Nothing, preferences = Nothing} + pure $ Profile {displayName = adjective <> noun, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} where pick :: [a] -> IO a pick xs = (xs !!) <$> randomRIO (0, length xs - 1) diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 051a04450f..489a7a266d 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -55,6 +55,7 @@ module Simplex.Chat.Store getContact, getContactIdByName, updateUserProfile, + setUserProfileContactLink, updateContactProfile, updateContactUserPreferences, updateContactAlias, @@ -292,7 +293,7 @@ import qualified Data.Aeson as J import Data.Bifunctor (first) import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) -import Data.Either (rights) +import Data.Either (fromRight, rights) import Data.Functor (($>)) import Data.Int (Int64) import Data.List (sortOn) @@ -373,6 +374,7 @@ import Simplex.Chat.Migrations.M20230328_files_protocol import Simplex.Chat.Migrations.M20230402_protocol_servers import Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions import Simplex.Chat.Migrations.M20230420_rcv_files_to_receive +import Simplex.Chat.Migrations.M20230422_profile_contact_links import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Util (week) @@ -447,7 +449,8 @@ schemaMigrations = ("20230328_files_protocol", m20230328_files_protocol, Just down_m20230328_files_protocol), ("20230402_protocol_servers", m20230402_protocol_servers, Just down_m20230402_protocol_servers), ("20230411_extra_xftp_file_descriptions", m20230411_extra_xftp_file_descriptions, Just down_m20230411_extra_xftp_file_descriptions), - ("20230420_rcv_files_to_receive", m20230420_rcv_files_to_receive, Just down_m20230420_rcv_files_to_receive) + ("20230420_rcv_files_to_receive", m20230420_rcv_files_to_receive, Just down_m20230420_rcv_files_to_receive), + ("20230422_profile_contact_links", m20230422_profile_contact_links, Just down_m20230422_profile_contact_links) ] -- | The list of migrations in ascending order by date @@ -501,7 +504,7 @@ createUserRecord db (AgentUserId auId) Profile {displayName, fullName, image, pr (profileId, displayName, userId, True, currentTs, currentTs) contactId <- insertedRowId db DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId) - pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, userPreferences, True) :. (Nothing, Nothing) + pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, Nothing, userPreferences, True) :. (Nothing, Nothing) getUsersInfo :: DB.Connection -> IO [UserInfo] getUsersInfo db = getUsers db >>= mapM getUserInfo @@ -539,17 +542,17 @@ getUsers db = userQuery :: Query userQuery = [sql| - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.local_display_name, ucp.full_name, ucp.image, ucp.preferences, u.show_ntfs, u.view_pwd_hash, u.view_pwd_salt + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.view_pwd_hash, u.view_pwd_salt FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe Preferences, Bool) :. (Maybe B64UrlByteString, Maybe B64UrlByteString) -> User -toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, userPreferences, showNtfs) :. (viewPwdHash_, viewPwdSalt_)) = +toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences, Bool) :. (Maybe B64UrlByteString, Maybe B64UrlByteString) -> User +toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, contactLink, userPreferences, showNtfs) :. (viewPwdHash_, viewPwdSalt_)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences, showNtfs, viewPwdHash} where - profile = LocalProfile {profileId, displayName, fullName, image, preferences = userPreferences, localAlias = ""} + profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} fullPreferences = mergePreferences Nothing userPreferences viewPwdHash = UserPwdHash <$> viewPwdHash_ <*> viewPwdSalt_ @@ -672,7 +675,7 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -724,15 +727,15 @@ getProfileById db userId profileId = DB.query db [sql| - SELECT cp.display_name, cp.full_name, cp.image, cp.local_alias, cp.preferences -- , ct.user_preferences + SELECT cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences FROM contact_profiles cp -- JOIN contacts ct ON cp.contact_profile_id = ct.contact_profile_id WHERE cp.user_id = ? AND cp.contact_profile_id = ? |] (userId, profileId) where - toProfile :: (ContactName, Text, Maybe ImageData, LocalAlias, Maybe Preferences) -> LocalProfile - toProfile (displayName, fullName, image, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile + toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> IO Connection createConnection_ db userId connType entityId acId viaContact viaUserContactLink customUserProfileId connLevel currentTs = do @@ -765,12 +768,12 @@ createDirectContact db user@User {userId} activeConn@Connection {connId, localAl pure $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt, updatedAt = createdAt, chatTs = Just createdAt} createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> LocalAlias -> Maybe Int64 -> UTCTime -> Maybe UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) -createContact_ db userId connId Profile {displayName, fullName, image, preferences} localAlias viaGroup currentTs chatTs = +createContact_ db userId connId Profile {displayName, fullName, image, contactLink, preferences} localAlias viaGroup currentTs chatTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, localAlias, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, localAlias, preferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -872,6 +875,23 @@ updateUserProfile db user p' profile = toLocalProfile profileId p' localAlias fullPreferences = mergePreferences Nothing preferences +setUserProfileContactLink :: DB.Connection -> User -> Maybe UserContactLink -> IO User +setUserProfileContactLink db user@User {userId, profile = p@LocalProfile {profileId}} ucl_ = do + ts <- getCurrentTime + DB.execute + db + [sql| + UPDATE contact_profiles + SET contact_link = ?, updated_at = ? + WHERE user_id = ? AND contact_profile_id = ? + |] + (connReqContact_, ts, userId, profileId) + pure (user :: User) {profile = p {contactLink = connReqContact_}} + where + connReqContact_ = case ucl_ of + Just UserContactLink {connReqContact} -> Just connReqContact + _ -> Nothing + updateContactProfile :: DB.Connection -> User -> Contact -> Profile -> ExceptT StoreError IO Contact updateContactProfile db user@User {userId} c p' | displayName == newName = do @@ -964,15 +984,15 @@ updateContactProfile_ db userId profileId profile = do updateContactProfile_' db userId profileId profile currentTs updateContactProfile_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO () -updateContactProfile_' db userId profileId Profile {displayName, fullName, image, preferences} updatedAt = do +updateContactProfile_' db userId profileId Profile {displayName, fullName, image, contactLink, preferences} updatedAt = do DB.execute db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (displayName, fullName, image, preferences, updatedAt, userId, profileId) + (displayName, fullName, image, contactLink, preferences, updatedAt, userId, profileId) updateContact_ :: DB.Connection -> UserId -> Int64 -> ContactName -> ContactName -> UTCTime -> IO () updateContact_ db userId contactId displayName newName updatedAt = do @@ -986,19 +1006,19 @@ updateContact_ db userId contactId displayName newName updatedAt = do (newName, updatedAt, userId, contactId) DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (displayName, userId) -type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) +type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) toContact :: User -> ContactRow :. ConnectionRow -> Contact -toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} activeConn = toConnection connRow chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} toContactOrError :: User -> ContactRow :. MaybeConnectionRow -> Either StoreError Contact -toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} in case toMaybeConnection connRow of Just activeConn -> @@ -1023,14 +1043,14 @@ getUserContactProfiles db User {userId} = <$> DB.query db [sql| - SELECT display_name, full_name, image, preferences + SELECT display_name, full_name, image, contact_link, preferences FROM contact_profiles WHERE user_id = ? |] (Only userId) where - toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe Preferences) -> (Profile) - toContactProfile (displayName, fullName, image, preferences) = Profile {displayName, fullName, image, preferences} + toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) -> (Profile) + toContactProfile (displayName, fullName, image, contactLink, preferences) = Profile {displayName, fullName, image, contactLink, preferences} createUserContactLink :: DB.Connection -> User -> ConnId -> ConnReqContact -> ExceptT StoreError IO () createUserContactLink db User {userId} agentConnId cReq = @@ -1081,7 +1101,7 @@ getUserContactLinks db User {userId} = toUserContactConnection (connRow :. (userContactLinkId, connReqContact, groupId)) = (toConnection connRow, UserContact {userContactLinkId, connReqContact, groupId}) deleteUserAddress :: DB.Connection -> User -> IO () -deleteUserAddress db User {userId} = do +deleteUserAddress db user@User {userId} = do DB.execute db [sql| @@ -1118,6 +1138,7 @@ deleteUserAddress db User {userId} = do ) |] [":user_id" := userId] + void $ setUserProfileContactLink db user Nothing DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL" (Only userId) data UserContactLink = UserContactLink @@ -1266,7 +1287,7 @@ setGroupLinkMemberRole db User {userId} userContactLinkId memberRole = DB.execute db "UPDATE user_contact_links SET group_link_member_role = ? WHERE user_id = ? AND user_contact_link_id = ?" (memberRole, userId, userContactLinkId) createOrUpdateContactRequest :: DB.Connection -> User -> Int64 -> InvitationId -> Profile -> Maybe XContactId -> ExceptT StoreError IO ContactOrRequest -createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profile {displayName, fullName, image, preferences} xContactId_ = +createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profile {displayName, fullName, image, contactLink, preferences} xContactId_ = liftIO (maybeM getContact' xContactId_) >>= \case Just contact -> pure $ CORContact contact Nothing -> CORRequest <$> createOrUpdate_ @@ -1288,8 +1309,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi createContactRequest_ currentTs ldn = do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -1308,7 +1329,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -1329,7 +1350,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c USING (user_contact_link_id) JOIN contact_profiles p USING (contact_profile_id) @@ -1357,6 +1378,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi SET display_name = ?, full_name = ?, image = ?, + contact_link = ?, updated_at = ? WHERE contact_profile_id IN ( SELECT contact_profile_id @@ -1365,7 +1387,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi AND contact_request_id = ? ) |] - (displayName, fullName, image, currentTs, userId, cReqId) + (displayName, fullName, image, contactLink, currentTs, userId, cReqId) getContactRequest' :: DB.Connection -> Int64 -> ExceptT StoreError IO (User, UserContactRequest) getContactRequest' db contactRequestId = do @@ -1380,7 +1402,7 @@ getContactRequest db User {userId} contactRequestId = [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c USING (user_contact_link_id) JOIN contact_profiles p USING (contact_profile_id) @@ -1389,11 +1411,11 @@ getContactRequest db User {userId} contactRequestId = |] (userId, contactRequestId) -type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime) toContactRequest :: ContactRequestRow -> UserContactRequest -toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image) :. (xContactId, preferences, createdAt, updatedAt)) = do - let profile = Profile {displayName, fullName, image, preferences} +toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, preferences, createdAt, updatedAt)) = do + let profile = Profile {displayName, fullName, image, contactLink, preferences} in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, localDisplayName, profileId, profile, xContactId, createdAt, updatedAt} getContactRequestIdByName :: DB.Connection -> UserId -> ContactName -> ExceptT StoreError IO Int64 @@ -1754,16 +1776,16 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do db [sql| SELECT - c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, + c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id WHERE c.user_id = ? AND c.contact_id = ? |] (userId, contactId) - toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact - toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)] = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact + toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)] = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} @@ -1781,10 +1803,10 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) JOIN groups g ON g.group_id = m.group_id @@ -1884,10 +1906,10 @@ getGroupAndMember db User {userId, userContactId} groupMemberId = mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2120,7 +2142,7 @@ getUserGroupDetails db User {userId, userContactId} = [sql| SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, mu.group_member_id, g.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, - mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences + mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g JOIN group_profiles gp USING (group_profile_id) JOIN group_members mu USING (group_id) @@ -2167,7 +2189,7 @@ groupMemberQuery = [sql| SELECT m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2239,20 +2261,20 @@ getGroupInvitation db user groupId = firstRow fromOnly (SEGroupNotFound groupId) $ DB.query db "SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id = ?" (groupId, userId) -type GroupMemberRow = ((Int64, Int64, MemberId, GroupMemberRole, GroupMemberCategory, GroupMemberStatus) :. (Maybe Int64, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, LocalAlias, Maybe Preferences)) +type GroupMemberRow = ((Int64, Int64, MemberId, GroupMemberRole, GroupMemberCategory, GroupMemberStatus) :. (Maybe Int64, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) -type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus) :. (Maybe Int64, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe LocalAlias, Maybe Preferences)) +type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus) :. (Maybe Int64, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, localAlias, preferences)) = - let memberProfile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = + let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} invitedBy = toInvitedBy userContactId invitedById activeConn = Nothing in GroupMember {..} toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just memberRole, Just memberCategory, Just memberStatus) :. (invitedById, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, Just localAlias, contactPreferences)) = - Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, localAlias, contactPreferences)) +toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just memberRole, Just memberCategory, Just memberStatus) :. (invitedById, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) = + Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences)) toMaybeGroupMember _ _ = Nothing createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> ExceptT StoreError IO GroupMember @@ -2325,7 +2347,7 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} = [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -2380,13 +2402,13 @@ updateGroupMemberStatusById db userId groupMemberId memStatus = do -- | add new member with profile createNewGroupMember :: DB.Connection -> User -> GroupInfo -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember -createNewGroupMember db user@User {userId} gInfo memInfo@(MemberInfo _ _ Profile {displayName, fullName, image, preferences}) memCategory memStatus = +createNewGroupMember db user@User {userId} gInfo memInfo@(MemberInfo _ _ Profile {displayName, fullName, image, contactLink, preferences}) memCategory memStatus = ExceptT . withLocalDisplayName db userId displayName $ \localDisplayName -> do currentTs <- getCurrentTime DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) memProfileId <- insertedRowId db let newMember = NewGroupMember @@ -2629,10 +2651,10 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} = mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- via GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2664,7 +2686,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = db [sql| SELECT - ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter @@ -2681,9 +2703,9 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = |] (userId, groupMemberId) where - toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)) :. ConnectionRow -> Contact - toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)) :. ConnectionRow -> Contact + toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} activeConn = toConnection connRow mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn @@ -3698,7 +3720,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe -- GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN contacts c ON m.contact_id = c.contact_id @@ -3738,7 +3760,7 @@ getDirectChatPreviews_ db user@User {userId} = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -3746,7 +3768,7 @@ getDirectChatPreviews_ db user@User {userId} = do -- ChatStats COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat, -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- DirectQuote @@ -3807,27 +3829,27 @@ getGroupChatPreviews_ db User {userId, userContactId} = do -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- ChatStats COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat, -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- Maybe GroupMember - sender m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, -- quoted ChatItem ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.local_alias, dbp.preferences + dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences FROM groups g JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_members mu ON mu.group_id = g.group_id @@ -3873,7 +3895,7 @@ getContactRequestChatPreviews_ db User {userId} = [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id @@ -3977,7 +3999,7 @@ getDirectChatItemsLast db User {userId} contactId count search = ExceptT $ do [sql| SELECT -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- DirectQuote @@ -4008,7 +4030,7 @@ getDirectChatAfter_ db user@User {userId} contactId afterChatItemId count search [sql| SELECT -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- DirectQuote @@ -4040,7 +4062,7 @@ getDirectChatBefore_ db user@User {userId} contactId beforeChatItemId count sear [sql| SELECT -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- DirectQuote @@ -4068,7 +4090,7 @@ getContact db user@User {userId} contactId = [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -4197,7 +4219,7 @@ getGroupInfo db User {userId, userContactId} groupId = -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_members mu ON mu.group_id = g.group_id @@ -4241,18 +4263,17 @@ updateGroupProfile db User {userId} g@GroupInfo {groupId, localDisplayName, grou DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId) getAllChatItems :: DB.Connection -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem] -getAllChatItems db user pagination search_ = do - let search = fromMaybe "" search_ - case pagination of - CPLast count -> getAllChatItemsLast_ db user count search - CPAfter _afterId _count -> throwError $ SEInternalError "not implemented" - CPBefore _beforeId _count -> throwError $ SEInternalError "not implemented" - -getAllChatItemsLast_ :: DB.Connection -> User -> Int -> String -> ExceptT StoreError IO [AChatItem] -getAllChatItemsLast_ db user@User {userId} count search = do +getAllChatItems db user@User {userId} pagination search_ = do itemRefs <- - liftIO $ - reverse . rights . map toChatItemRef + rights . map toChatItemRef <$> case pagination of + CPLast count -> liftIO $ getAllChatItemsLast_ count + CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem db user afterId + CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem db user beforeId + mapM (uncurry $ getAChatItem_ db user) itemRefs + where + search = fromMaybe "" search_ + getAllChatItemsLast_ count = + reverse <$> DB.query db [sql| @@ -4263,7 +4284,31 @@ getAllChatItemsLast_ db user@User {userId} count search = do LIMIT ? |] (userId, search, count) - mapM (uncurry $ getAChatItem_ db user) itemRefs + getAllChatItemsAfter_ afterId count afterTs = + DB.query + db + [sql| + SELECT chat_item_id, contact_id, group_id + FROM chat_items + WHERE user_id = ? AND item_text LIKE '%' || ? || '%' + AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) + ORDER BY item_ts ASC, chat_item_id ASC + LIMIT ? + |] + (userId, search, afterTs, afterTs, afterId, count) + getAllChatItemsBefore_ beforeId count beforeTs = + reverse + <$> DB.query + db + [sql| + SELECT chat_item_id, contact_id, group_id + FROM chat_items + WHERE user_id = ? AND item_text LIKE '%' || ? || '%' + AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) + ORDER BY item_ts DESC, chat_item_id DESC + LIMIT ? + |] + (userId, search, beforeTs, beforeTs, beforeId, count) getGroupIdByName :: DB.Connection -> User -> GroupName -> ExceptT StoreError IO GroupId getGroupIdByName db User {userId} gName = @@ -4423,7 +4468,7 @@ getDirectChatItem db User {userId} contactId itemId = ExceptT $ do [sql| SELECT -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- DirectQuote @@ -4566,23 +4611,23 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do [sql| SELECT -- ChatItem - i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, + i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, -- CIFile f.file_id, f.file_name, f.file_size, f.file_path, f.ci_file_status, f.protocol, -- GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, -- quoted ChatItem ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.local_alias, dbp.preferences + dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN group_members m ON m.group_member_id = i.group_member_id @@ -4826,9 +4871,9 @@ type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe Bool) -type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, ACIStatus, Maybe SharedMsgId, Bool, Maybe Bool, UTCTime, UTCTime) :. ChatItemModeRow :. MaybeCIFIleRow +type ChatItemRow = (Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe SharedMsgId, Bool, Maybe Bool, UTCTime, UTCTime) :. ChatItemModeRow :. MaybeCIFIleRow -type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe ACIStatus, Maybe SharedMsgId, Maybe Bool, Maybe Bool, Maybe UTCTime, Maybe UTCTime) :. ChatItemModeRow :. MaybeCIFIleRow +type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe AMsgDirection, Maybe Text, Maybe Text, Maybe ACIStatus, Maybe SharedMsgId, Maybe Bool, Maybe Bool, Maybe UTCTime, Maybe UTCTime) :. ChatItemModeRow :. MaybeCIFIleRow type QuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool) @@ -4841,19 +4886,22 @@ toQuote :: QuoteRow -> Maybe (CIQDirection c) -> Maybe (CIQuote c) toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir = CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent) +-- this function can be changed so it never fails, not only avoid failure on invalid json toDirectChatItem :: TimeZone -> UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect) -toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_, fileProtocol_)) :. quoteRow) = - case (itemContent, itemStatus, fileStatus_) of - (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, Just (AFS SMDSnd fileStatus)) -> - Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent (maybeCIFile fileStatus) - (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, Nothing) -> - Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent Nothing - (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just (AFS SMDRcv fileStatus)) -> - Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent (maybeCIFile fileStatus) - (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Nothing) -> - Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent Nothing - _ -> badItem +toDirectChatItem tz currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_, fileProtocol_)) :. quoteRow) = + chatItem $ fromRight invalid $ dbParseACIContent itemContentText where + invalid = ACIContent msgDir $ CIInvalidJSON itemContentText + chatItem itemContent = case (itemContent, itemStatus, fileStatus_) of + (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, Just (AFS SMDSnd fileStatus)) -> + Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent (maybeCIFile fileStatus) + (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, Nothing) -> + Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent Nothing + (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just (AFS SMDRcv fileStatus)) -> + Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent (maybeCIFile fileStatus) + (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Nothing) -> + Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent Nothing + _ -> badItem maybeCIFile :: CIFileStatus d -> Maybe (CIFile d) maybeCIFile fileStatus = case (fileId_, fileName_, fileSize_, fileProtocol_) of @@ -4872,8 +4920,8 @@ toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStat ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} toDirectChatItemList :: TimeZone -> UTCTime -> MaybeChatItemRow :. QuoteRow -> [CChatItem 'CTDirect] -toDirectChatItemList tz currentTs (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow) = - either (const []) (: []) $ toDirectChatItem tz currentTs (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow) +toDirectChatItemList tz currentTs (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow) = + either (const []) (: []) $ toDirectChatItem tz currentTs (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow) toDirectChatItemList _ _ _ = [] type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow @@ -4888,33 +4936,36 @@ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction direction (Just False) Nothing = Just $ CIQGroupRcv Nothing direction _ _ = Nothing +-- this function can be changed so it never fails, not only avoid failure on invalid json toGroupChatItem :: TimeZone -> UTCTime -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup) -toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do - let member_ = toMaybeGroupMember userContactId memberRow_ - quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_ - deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_ - case (itemContent, itemStatus, member_, fileStatus_) of - (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) -> - Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent quotedMember_ (maybeCIFile fileStatus) deletedByGroupMember_ - (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) -> - Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent quotedMember_ Nothing deletedByGroupMember_ - (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) -> - Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent quotedMember_ (maybeCIFile fileStatus) deletedByGroupMember_ - (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) -> - Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent quotedMember_ Nothing deletedByGroupMember_ - _ -> badItem +toGroupChatItem tz currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do + chatItem $ fromRight invalid $ dbParseACIContent itemContentText where + member_ = toMaybeGroupMember userContactId memberRow_ + quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_ + deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_ + invalid = ACIContent msgDir $ CIInvalidJSON itemContentText + chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of + (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) -> + Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent (maybeCIFile fileStatus) + (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) -> + Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent Nothing + (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) -> + Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent (maybeCIFile fileStatus) + (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) -> + Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent Nothing + _ -> badItem maybeCIFile :: CIFileStatus d -> Maybe (CIFile d) maybeCIFile fileStatus = case (fileId_, fileName_, fileSize_, fileProtocol_) of (Just fileId, Just fileName, Just fileSize, Just fileProtocol) -> Just CIFile {fileId, fileName, fileSize, filePath, fileStatus, fileProtocol} _ -> Nothing - cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe GroupMember -> Maybe (CIFile d) -> Maybe GroupMember -> CChatItem 'CTGroup - cItem d chatDir ciStatus content quotedMember_ file deletedByGroupMember_ = - CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus deletedByGroupMember_, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, file} + cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTGroup + cItem d chatDir ciStatus content file = + CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, file} badItem = Left $ SEBadChatItem itemId - ciMeta :: CIContent d -> CIStatus d -> Maybe GroupMember -> CIMeta 'CTGroup d - ciMeta content status deletedByGroupMember_ = + ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTGroup d + ciMeta content status = let itemDeleted' = if itemDeleted then Just (maybe (CIDeleted @'CTGroup) CIModerated deletedByGroupMember_) @@ -4925,8 +4976,8 @@ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemT ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} toGroupChatItemList :: TimeZone -> UTCTime -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup] -toGroupChatItemList tz currentTs userContactId (((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = - either (const []) (: []) $ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) +toGroupChatItemList tz currentTs userContactId (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just itemDeleted, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = + either (const []) (: []) $ toGroupChatItem tz currentTs userContactId (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId, itemDeleted, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) toGroupChatItemList _ _ _ _ = [] getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> IO [ServerCfg p] diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 22b0041281..de89599f2d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1071,6 +1071,7 @@ data Profile = Profile { displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, + contactLink :: Maybe ConnReqContact, preferences :: Maybe Preferences -- fields that should not be read into this data type to prevent sending them as part of profile to contacts: -- - contact_profile_id @@ -1099,6 +1100,7 @@ data LocalProfile = LocalProfile displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, + contactLink :: Maybe ConnReqContact, preferences :: Maybe Preferences, localAlias :: LocalAlias } @@ -1112,12 +1114,12 @@ localProfileId :: LocalProfile -> ProfileId localProfileId = profileId toLocalProfile :: ProfileId -> Profile -> LocalAlias -> LocalProfile -toLocalProfile profileId Profile {displayName, fullName, image, preferences} localAlias = - LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toLocalProfile profileId Profile {displayName, fullName, image, contactLink, preferences} localAlias = + LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} fromLocalProfile :: LocalProfile -> Profile -fromLocalProfile LocalProfile {displayName, fullName, image, preferences} = - Profile {displayName, fullName, image, preferences} +fromLocalProfile LocalProfile {displayName, fullName, image, contactLink, preferences} = + Profile {displayName, fullName, image, contactLink, preferences} data GroupProfile = GroupProfile { displayName :: GroupName, diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5eedb8e638..f4ae01c863 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -811,8 +811,9 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} = ] viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString] -viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias}} stats incognitoProfile = +viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}} stats incognitoProfile = ["contact ID: " <> sShow contactId] <> viewConnectionStats stats + <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) l]) contactLink <> maybe ["you've shared main profile with this contact"] (\p -> ["you've shared incognito profile with this contact: " <> incognitoProfile' p]) @@ -872,11 +873,12 @@ viewSwitchPhase SPCompleted = "changed address" viewSwitchPhase phase = plain (strEncode phase) <> " changing address" viewUserProfileUpdated :: Profile -> Profile -> [StyledString] -viewUserProfileUpdated Profile {displayName = n, fullName, image, preferences} Profile {displayName = n', fullName = fullName', image = image', preferences = prefs'} = +viewUserProfileUpdated Profile {displayName = n, fullName, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', image = image', contactLink = contactLink', preferences = prefs'} = profileUpdated <> viewPrefsUpdated preferences prefs' where profileUpdated - | n == n' && fullName == fullName' && image == image' = [] + | n == n' && fullName == fullName' && image == image' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"] | n == n' && fullName == fullName' = [if isNothing image' then "profile image removed" else "profile image updated"] | n == n' = ["user full name " <> (if T.null fullName' || fullName' == n' then "removed" else "changed to " <> plain fullName') <> notified] | otherwise = ["user profile is changed to " <> ttyFullName n' fullName' <> notified] @@ -980,9 +982,13 @@ viewConnectionAliasUpdated PendingContactConnection {pccConnId, localAlias} viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated - Contact {localDisplayName = n, profile = LocalProfile {fullName}} - Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName'}} - | n == n' && fullName == fullName' = [] + Contact {localDisplayName = n, profile = LocalProfile {fullName, contactLink}} + Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', contactLink = contactLink'}} + | n == n' && fullName == fullName' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' = + if isNothing contactLink' + then [ttyContact n <> " removed contact address"] + else [ttyContact n <> " set new contact address, use " <> highlight ("/info " <> n) <> " to view"] | n == n' = ["contact " <> ttyContact n <> fullNameUpdate] | otherwise = [ "contact " <> ttyContact n <> " changed to " <> ttyFullName n' fullName', diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index e4d120d1bc..e54bddb386 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1544,7 +1544,9 @@ testUserPrivacy = showActiveUser alice "alisa" -- connect using second user connectUsers alice bob + threadDelay 1000000 alice #> "@bob hello" + threadDelay 1000000 bob <# "alisa> hello" bob #> "@alisa hey" alice <# "bob> hey" @@ -1576,12 +1578,19 @@ testUserPrivacy = alice <## "alisa (active, hidden, muted)" -- hidden message is saved alice ##> "/tail" + alice <##? chatHistory + alice ##> "/_get items count=10" + alice <##? chatHistory + alice ##> "/_get items before=9 count=10" alice <##? [ "bob> Disappearing messages: allowed", "bob> Full deletion: off", "bob> Voice messages: enabled", - "bob> Audio/video calls: enabled", - "@bob hello", + "bob> Audio/video calls: enabled" + ] + alice ##> "/_get items after=8 count=10" + alice + <##? [ "@bob hello", "bob> hey", "bob> hello again", "bob> this won't show" @@ -1634,6 +1643,16 @@ testUserPrivacy = alice <## (current <> "user alisa:") alice <## "messages are shown" alice <## "profile is visible" + chatHistory = + [ "bob> Disappearing messages: allowed", + "bob> Full deletion: off", + "bob> Voice messages: enabled", + "bob> Audio/video calls: enabled", + "@bob hello", + "bob> hey", + "bob> hello again", + "bob> this won't show" + ] testSetChatItemTTL :: HasCallStack => FilePath -> IO () testSetChatItemTTL = diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index cba818295f..a5dc083f71 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -19,6 +19,7 @@ chatProfileTests = do it "update user profile with image" testUpdateProfileImage describe "user contact link" $ do describe "create and connect via contact link" testUserContactLink + it "add contact link to profile" testProfileLink it "auto accept contact requests" testUserContactLinkAutoAccept it "deduplicate contact requests" testDeduplicateContactRequests it "deduplicate contact requests with profile change" testDeduplicateContactRequestsProfileChange @@ -134,6 +135,83 @@ testUserContactLink = versionTestMatrix3 $ \alice bob cath -> do alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")] alice <##> cath +testProfileLink :: HasCallStack => FilePath -> IO () +testProfileLink = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + alice ##> "/ad" + cLink <- getContactLink alice True + + bob ##> ("/c " <> cLink) + alice <#? bob + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request..." + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice <##> bob + + alice ##> "/pa on" + alice <## "new contact address set" + + bob <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink bob cLink + + cath ##> ("/c " <> cLink) + alice <#? cath + alice ##> "/ac cath" + alice <## "cath (Catherine): accepting contact request..." + concurrently_ + (cath <## "alice (Alice): contact is connected") + (alice <## "cath (Catherine): contact is connected") + alice <##> cath + + checkAliceProfileLink cath cLink + + alice ##> "/pa off" + alice <## "contact address removed" + + bob <## "alice removed contact address" + checkAliceNoProfileLink bob + + cath <## "alice removed contact address" + checkAliceNoProfileLink cath + + alice ##> "/pa on" + alice <## "new contact address set" + + bob <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink bob cLink + + cath <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink cath cLink + + alice ##> "/da" + alice <## "Your chat address is deleted - accepted contacts will remain connected." + alice <## "To create a new chat address use /ad" + + bob <## "alice removed contact address" + checkAliceNoProfileLink bob + + cath <## "alice removed contact address" + checkAliceNoProfileLink cath + where + checkAliceProfileLink cc cLink = do + cc ##> "/info alice" + cc <## "contact ID: 2" + cc <##. "receiving messages via" + cc <##. "sending messages via" + cc <## ("contact address: " <> cLink) + cc <## "you've shared main profile with this contact" + cc <## "connection not verified, use /code command to see security code" + checkAliceNoProfileLink cc = do + cc ##> "/info alice" + cc <## "contact ID: 2" + cc <##. "receiving messages via" + cc <##. "sending messages via" + cc <## "you've shared main profile with this contact" + cc <## "connection not verified, use /code command to see security code" + testUserContactLinkAutoAccept :: HasCallStack => FilePath -> IO () testUserContactLinkAutoAccept = testChat4 aliceProfile bobProfile cathProfile danProfile $ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index a373b7cea6..18866fe2d1 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -31,16 +31,16 @@ defaultPrefs :: Maybe Preferences defaultPrefs = Just $ toChatPrefs defaultChatPrefs aliceProfile :: Profile -aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, preferences = defaultPrefs} +aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} bobProfile :: Profile -bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAKHGlDQ1BJQ0MgUHJvZmlsZQAASImFVgdUVNcWve9Nb7QZeu9NehtAem/Sq6gMQ28OQxWxgAQjEFFEREARNFQFg1KjiIhiIQgoYA9IEFBisCAq6OQNJNH4//r/zDpz9ttzz7n73ffWmg0A6QCDxYqD+QCIT0hmezlYywQEBsngngEYCAIy0AC6DGYSy8rDwxUg8Xf9d7wbAxC33tHgzvrP3/9nCISFJzEBgIIRTGey2MkILkawT1oyi4tnEUxjI6IQvMLFkauYqxjQQtewwuoaHy8bBNMBwJMZDHYkAERbhJdJZUYic4hhCNZOCItOQDB3vjkzioFwxLsIXhcRl5IOAImrRzs+fivCk7QRrIL0shAcwNUW+tX8yH/tFfrPXgxG5D84Pi6F+dc9ck+HHJ7g641UMSQlQATQBHEgBaQDGcACbLAVYaIRJhx5Dv+9j77aZ4OsZIFtSEc0iARRIBnpt/9qlvfqpGSQBhjImnCEcUU+NtxnujZy4fbqVEiU/wuXdQyA9S0cDqfzC+e2F4DzyLkSB79wyi0A8KoBcL2GmcJOXePQ3C8MIAJeQAOiQArIAxXuWwMMgSmwBHbAGbgDHxAINgMmojceUZUGMkEWyAX54AA4DMpAJTgJ6sAZ0ALawQVwGVwDt8AQGAUPwQSYBi/AAngHliEIwkEUiAqJQtKQIqQO6UJ0yByyg1whLygQCoEioQQoBcqE9kD5UBFUBlVB9dBPUCd0GboBDUP3oUloDnoNfYRRMBmmwZKwEqwF02Er2AX2gTfBkXAinAHnwPvhUrgaPg23wZfhW/AoPAG/gBdRAEVCCaFkURooOsoG5Y4KQkWg2KidqDxUCaoa1YTqQvWj7qAmUPOoD2gsmoqWQWugTdGOaF80E52I3okuQJeh69Bt6D70HfQkegH9GUPBSGDUMSYYJ0wAJhKThsnFlGBqMK2Yq5hRzDTmHRaLFcIqY42wjthAbAx2O7YAewzbjO3BDmOnsIs4HE4Up44zw7njGLhkXC7uKO407hJuBDeNe48n4aXxunh7fBA+AZ+NL8E34LvxI/gZ/DKBj6BIMCG4E8II2wiFhFOELsJtwjRhmchPVCaaEX2IMcQsYimxiXiV+Ij4hkQiyZGMSZ6kaNJuUinpLOk6aZL0gSxAViPbkIPJKeT95FpyD/k++Q2FQlGiWFKCKMmU/ZR6yhXKE8p7HiqPJo8TTxjPLp5ynjaeEZ6XvAReRV4r3s28GbwlvOd4b/PO8xH4lPhs+Bh8O/nK+Tr5xvkW+an8Ovzu/PH8BfwN/Df4ZwVwAkoCdgJhAjkCJwWuCExRUVR5qg2VSd1DPUW9Sp2mYWnKNCdaDC2fdoY2SFsQFBDUF/QTTBcsF7woOCGEElISchKKEyoUahEaE/ooLClsJRwuvE+4SXhEeElEXMRSJFwkT6RZZFTko6iMqJ1orOhB0XbRx2JoMTUxT7E0seNiV8XmxWnipuJM8TzxFvEHErCEmoSXxHaJkxIDEouSUpIOkizJo5JXJOelhKQspWKkiqW6peakqdLm0tHSxdKXpJ/LCMpYycTJlMr0ySzISsg6yqbIVskOyi7LKcv5ymXLNcs9lifK0+Uj5Ivle+UXFKQV3BQyFRoVHigSFOmKUYpHFPsVl5SUlfyV9iq1K80qiyg7KWcoNyo/UqGoWKgkqlSr3FXFqtJVY1WPqQ6pwWoGalFq5Wq31WF1Q/Vo9WPqw+sw64zXJayrXjeuQdaw0kjVaNSY1BTSdNXM1mzXfKmloBWkdVCrX+uztoF2nPYp7Yc6AjrOOtk6XTqvddV0mbrlunf1KHr2erv0OvRe6avrh+sf179nQDVwM9hr0GvwydDIkG3YZDhnpGAUYlRhNE6n0T3oBfTrxhhja+NdxheMP5gYmiSbtJj8YaphGmvaYDq7Xnl9+PpT66fM5MwYZlVmE+Yy5iHmJ8wnLGQtGBbVFk8t5S3DLGssZ6xUrWKsTlu9tNa2Zlu3Wi/ZmNjssOmxRdk62ObZDtoJ2Pnaldk9sZezj7RvtF9wMHDY7tDjiHF0cTzoOO4k6cR0qndacDZy3uHc50J28XYpc3nqqubKdu1yg92c3Q65PdqguCFhQ7s7cHdyP+T+2EPZI9HjZ0+sp4dnueczLx2vTK9+b6r3Fu8G73c+1j6FPg99VXxTfHv9eP2C/er9lvxt/Yv8JwK0AnYE3AoUC4wO7AjCBfkF1QQtbrTbeHjjdLBBcG7w2CblTembbmwW2xy3+eIW3i2MLedCMCH+IQ0hKwx3RjVjMdQptCJ0gWnDPMJ8EWYZVhw2F24WXhQ+E2EWURQxG2kWeShyLsoiqiRqPtomuiz6VYxjTGXMUqx7bG0sJ84/rjkeHx8S35kgkBCb0LdVamv61mGWOiuXNZFokng4cYHtwq5JgpI2JXUk05A/0oEUlZTvUiZTzVPLU9+n+aWdS+dPT0gf2Ka2bd+2mQz7jB+3o7czt/dmymZmZU7usNpRtRPaGbqzd5f8rpxd07sddtdlEbNis37J1s4uyn67x39PV45kzu6cqe8cvmvM5cll547vNd1b+T36++jvB/fp7Tu673NeWN7NfO38kvyVAmbBzR90fij9gbM/Yv9goWHh8QPYAwkHxg5aHKwr4i/KKJo65HaorVimOK/47eEth2+U6JdUHiEeSTkyUepa2nFU4eiBoytlUWWj5dblzRUSFfsqlo6FHRs5bnm8qVKyMr/y44noE/eqHKraqpWqS05iT6aefHbK71T/j/Qf62vEavJrPtUm1E7UedX11RvV1zdINBQ2wo0pjXOng08PnbE909Gk0VTVLNScfxacTTn7/KeQn8ZaXFp6z9HPNZ1XPF/RSm3Na4PatrUttEe1T3QEdgx3Onf2dpl2tf6s+XPtBdkL5RcFLxZ2E7tzujmXMi4t9rB65i9HXp7q3dL78ErAlbt9nn2DV12uXr9mf+1Kv1X/petm1y/cMLnReZN+s/2W4a22AYOB1l8MfmkdNBxsu210u2PIeKhreP1w94jFyOU7tneu3XW6e2t0w+jwmO/YvfHg8Yl7Yfdm78fdf/Ug9cHyw92PMI/yHvM9Lnki8aT6V9VfmycMJy5O2k4OPPV++nCKOfXit6TfVqZznlGelcxIz9TP6s5emLOfG3q+8fn0C9aL5fnc3/l/r3ip8vL8H5Z/DCwELEy/Yr/ivC54I/qm9q3+295Fj8Un7+LfLS/lvRd9X/eB/qH/o//HmeW0FdxK6SfVT12fXT4/4sRzOCwGm7FqBVBIwhERALyuBYASCAB1CPEPG9f8119+BvrK2fyNwVndL5jhvubRVsMQgCakeCFp04OsQ1LJEgAe5NodqT6WANbT+yf/iqQIPd21PXgaAcDJcjivtwJAQHLFgcNZ9uBwPlUgYhHf1z37f7V9g9e8ITewiP88wfWIYET6HPg21nzjV2fybQVcxfrg2/onng/F50lD/ccAAAA4ZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAKgAgAEAAAAAQAAABigAwAEAAAAAQAAABgAAAAAwf1XlwAAAaNJREFUSA3FlT1LA0EQQBN/gYUYRTksJZVgEbCR/D+7QMr8ABtttBBCsLGzsLG2sxaxED/ie4d77u0dyaE5HHjczn7MzO7M7nU6/yXz+bwLhzCCjTQO+rZhDH3opuNLdRYN4RHe4RIKJ7R34Ro+4AEGSw2mE1iUwT18gpI74WvkGlccu4XNdH0jnYU7cAUacidn37qR23cOxc4aGU0nYUAn7iSWEHkz46w0ocdQu1X6B/AMQZ5o7KfBqNOfwRH8JB7FajGhnmcpKvQe3MEbvILiDm5gPXaCHnZr4vvFGMoEKudKn8YvQIOOe+YzCPop7dwJ3zRfJ7GDuso4YJGRa0yZgg4tUaNXdGrbuZWKKxzYYEJc2xp9AUUjGt8KC2jvgYadF8+10vJyDnNLXwbdiWUZi0fUK01Eoc+AZhCLZVzK4Vq6sDUdz+0dEcbbTTIOJmAyTVhx/WmvrExbv2jtPhWLKodjCtefZiEeZeVZWWSndgwj6fVf3XON8Qwq15++uoqrfYVrow6dGBpCq79ME291jaB0/Q2CPncyht/99MNO/vr9AqW/CGi8sJqbAAAAAElFTkSuQmCC"), preferences = defaultPrefs} +bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAKHGlDQ1BJQ0MgUHJvZmlsZQAASImFVgdUVNcWve9Nb7QZeu9NehtAem/Sq6gMQ28OQxWxgAQjEFFEREARNFQFg1KjiIhiIQgoYA9IEFBisCAq6OQNJNH4//r/zDpz9ttzz7n73ffWmg0A6QCDxYqD+QCIT0hmezlYywQEBsngngEYCAIy0AC6DGYSy8rDwxUg8Xf9d7wbAxC33tHgzvrP3/9nCISFJzEBgIIRTGey2MkILkawT1oyi4tnEUxjI6IQvMLFkauYqxjQQtewwuoaHy8bBNMBwJMZDHYkAERbhJdJZUYic4hhCNZOCItOQDB3vjkzioFwxLsIXhcRl5IOAImrRzs+fivCk7QRrIL0shAcwNUW+tX8yH/tFfrPXgxG5D84Pi6F+dc9ck+HHJ7g641UMSQlQATQBHEgBaQDGcACbLAVYaIRJhx5Dv+9j77aZ4OsZIFtSEc0iARRIBnpt/9qlvfqpGSQBhjImnCEcUU+NtxnujZy4fbqVEiU/wuXdQyA9S0cDqfzC+e2F4DzyLkSB79wyi0A8KoBcL2GmcJOXePQ3C8MIAJeQAOiQArIAxXuWwMMgSmwBHbAGbgDHxAINgMmojceUZUGMkEWyAX54AA4DMpAJTgJ6sAZ0ALawQVwGVwDt8AQGAUPwQSYBi/AAngHliEIwkEUiAqJQtKQIqQO6UJ0yByyg1whLygQCoEioQQoBcqE9kD5UBFUBlVB9dBPUCd0GboBDUP3oUloDnoNfYRRMBmmwZKwEqwF02Er2AX2gTfBkXAinAHnwPvhUrgaPg23wZfhW/AoPAG/gBdRAEVCCaFkURooOsoG5Y4KQkWg2KidqDxUCaoa1YTqQvWj7qAmUPOoD2gsmoqWQWugTdGOaF80E52I3okuQJeh69Bt6D70HfQkegH9GUPBSGDUMSYYJ0wAJhKThsnFlGBqMK2Yq5hRzDTmHRaLFcIqY42wjthAbAx2O7YAewzbjO3BDmOnsIs4HE4Up44zw7njGLhkXC7uKO407hJuBDeNe48n4aXxunh7fBA+AZ+NL8E34LvxI/gZ/DKBj6BIMCG4E8II2wiFhFOELsJtwjRhmchPVCaaEX2IMcQsYimxiXiV+Ij4hkQiyZGMSZ6kaNJuUinpLOk6aZL0gSxAViPbkIPJKeT95FpyD/k++Q2FQlGiWFKCKMmU/ZR6yhXKE8p7HiqPJo8TTxjPLp5ynjaeEZ6XvAReRV4r3s28GbwlvOd4b/PO8xH4lPhs+Bh8O/nK+Tr5xvkW+an8Ovzu/PH8BfwN/Df4ZwVwAkoCdgJhAjkCJwWuCExRUVR5qg2VSd1DPUW9Sp2mYWnKNCdaDC2fdoY2SFsQFBDUF/QTTBcsF7woOCGEElISchKKEyoUahEaE/ooLClsJRwuvE+4SXhEeElEXMRSJFwkT6RZZFTko6iMqJ1orOhB0XbRx2JoMTUxT7E0seNiV8XmxWnipuJM8TzxFvEHErCEmoSXxHaJkxIDEouSUpIOkizJo5JXJOelhKQspWKkiqW6peakqdLm0tHSxdKXpJ/LCMpYycTJlMr0ySzISsg6yqbIVskOyi7LKcv5ymXLNcs9lifK0+Uj5Ivle+UXFKQV3BQyFRoVHigSFOmKUYpHFPsVl5SUlfyV9iq1K80qiyg7KWcoNyo/UqGoWKgkqlSr3FXFqtJVY1WPqQ6pwWoGalFq5Wq31WF1Q/Vo9WPqw+sw64zXJayrXjeuQdaw0kjVaNSY1BTSdNXM1mzXfKmloBWkdVCrX+uztoF2nPYp7Yc6AjrOOtk6XTqvddV0mbrlunf1KHr2erv0OvRe6avrh+sf179nQDVwM9hr0GvwydDIkG3YZDhnpGAUYlRhNE6n0T3oBfTrxhhja+NdxheMP5gYmiSbtJj8YaphGmvaYDq7Xnl9+PpT66fM5MwYZlVmE+Yy5iHmJ8wnLGQtGBbVFk8t5S3DLGssZ6xUrWKsTlu9tNa2Zlu3Wi/ZmNjssOmxRdk62ObZDtoJ2Pnaldk9sZezj7RvtF9wMHDY7tDjiHF0cTzoOO4k6cR0qndacDZy3uHc50J28XYpc3nqqubKdu1yg92c3Q65PdqguCFhQ7s7cHdyP+T+2EPZI9HjZ0+sp4dnueczLx2vTK9+b6r3Fu8G73c+1j6FPg99VXxTfHv9eP2C/er9lvxt/Yv8JwK0AnYE3AoUC4wO7AjCBfkF1QQtbrTbeHjjdLBBcG7w2CblTembbmwW2xy3+eIW3i2MLedCMCH+IQ0hKwx3RjVjMdQptCJ0gWnDPMJ8EWYZVhw2F24WXhQ+E2EWURQxG2kWeShyLsoiqiRqPtomuiz6VYxjTGXMUqx7bG0sJ84/rjkeHx8S35kgkBCb0LdVamv61mGWOiuXNZFokng4cYHtwq5JgpI2JXUk05A/0oEUlZTvUiZTzVPLU9+n+aWdS+dPT0gf2Ka2bd+2mQz7jB+3o7czt/dmymZmZU7usNpRtRPaGbqzd5f8rpxd07sddtdlEbNis37J1s4uyn67x39PV45kzu6cqe8cvmvM5cll547vNd1b+T36++jvB/fp7Tu673NeWN7NfO38kvyVAmbBzR90fij9gbM/Yv9goWHh8QPYAwkHxg5aHKwr4i/KKJo65HaorVimOK/47eEth2+U6JdUHiEeSTkyUepa2nFU4eiBoytlUWWj5dblzRUSFfsqlo6FHRs5bnm8qVKyMr/y44noE/eqHKraqpWqS05iT6aefHbK71T/j/Qf62vEavJrPtUm1E7UedX11RvV1zdINBQ2wo0pjXOng08PnbE909Gk0VTVLNScfxacTTn7/KeQn8ZaXFp6z9HPNZ1XPF/RSm3Na4PatrUttEe1T3QEdgx3Onf2dpl2tf6s+XPtBdkL5RcFLxZ2E7tzujmXMi4t9rB65i9HXp7q3dL78ErAlbt9nn2DV12uXr9mf+1Kv1X/petm1y/cMLnReZN+s/2W4a22AYOB1l8MfmkdNBxsu210u2PIeKhreP1w94jFyOU7tneu3XW6e2t0w+jwmO/YvfHg8Yl7Yfdm78fdf/Ug9cHyw92PMI/yHvM9Lnki8aT6V9VfmycMJy5O2k4OPPV++nCKOfXit6TfVqZznlGelcxIz9TP6s5emLOfG3q+8fn0C9aL5fnc3/l/r3ip8vL8H5Z/DCwELEy/Yr/ivC54I/qm9q3+295Fj8Un7+LfLS/lvRd9X/eB/qH/o//HmeW0FdxK6SfVT12fXT4/4sRzOCwGm7FqBVBIwhERALyuBYASCAB1CPEPG9f8119+BvrK2fyNwVndL5jhvubRVsMQgCakeCFp04OsQ1LJEgAe5NodqT6WANbT+yf/iqQIPd21PXgaAcDJcjivtwJAQHLFgcNZ9uBwPlUgYhHf1z37f7V9g9e8ITewiP88wfWIYET6HPg21nzjV2fybQVcxfrg2/onng/F50lD/ccAAAA4ZVhJZk1NACoAAAAIAAGHaQAEAAAAAQAAABoAAAAAAAKgAgAEAAAAAQAAABigAwAEAAAAAQAAABgAAAAAwf1XlwAAAaNJREFUSA3FlT1LA0EQQBN/gYUYRTksJZVgEbCR/D+7QMr8ABtttBBCsLGzsLG2sxaxED/ie4d77u0dyaE5HHjczn7MzO7M7nU6/yXz+bwLhzCCjTQO+rZhDH3opuNLdRYN4RHe4RIKJ7R34Ro+4AEGSw2mE1iUwT18gpI74WvkGlccu4XNdH0jnYU7cAUacidn37qR23cOxc4aGU0nYUAn7iSWEHkz46w0ocdQu1X6B/AMQZ5o7KfBqNOfwRH8JB7FajGhnmcpKvQe3MEbvILiDm5gPXaCHnZr4vvFGMoEKudKn8YvQIOOe+YzCPop7dwJ3zRfJ7GDuso4YJGRa0yZgg4tUaNXdGrbuZWKKxzYYEJc2xp9AUUjGt8KC2jvgYadF8+10vJyDnNLXwbdiWUZi0fUK01Eoc+AZhCLZVzK4Vq6sDUdz+0dEcbbTTIOJmAyTVhx/WmvrExbv2jtPhWLKodjCtefZiEeZeVZWWSndgwj6fVf3XON8Qwq15++uoqrfYVrow6dGBpCq79ME291jaB0/Q2CPncyht/99MNO/vr9AqW/CGi8sJqbAAAAAElFTkSuQmCC"), contactLink = Nothing, preferences = defaultPrefs} cathProfile :: Profile -cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, preferences = defaultPrefs} +cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} danProfile :: Profile -danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, preferences = defaultPrefs} +danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} xit' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a) xit' = if os == "linux" then xit else it diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 4c1d056bf7..dcc7e88c9f 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -92,7 +92,7 @@ testGroupPreferences :: Maybe GroupPreferences testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, voice = Just VoiceGroupPreference {enable = FEOn}, fullDelete = Nothing} testProfile :: Profile -testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), preferences = testChatPreferences} +testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} testGroupProfile :: GroupProfile testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, image = Nothing, groupPreferences = testGroupPreferences} @@ -198,7 +198,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do #==# XInfo testProfile it "x.info with empty full name" $ "{\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"voice\":{\"allow\":\"yes\"}}}}}" - #==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, preferences = testChatPreferences} + #==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, contactLink = Nothing, preferences = testChatPreferences} it "x.contact with xContactId" $ "{\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"voice\":{\"allow\":\"yes\"}}}}}" #==# XContact testProfile (Just $ XContactId "\1\2\3\4") diff --git a/website/.eleventy.js b/website/.eleventy.js index 7dc872b6aa..07815958e5 100644 --- a/website/.eleventy.js +++ b/website/.eleventy.js @@ -15,7 +15,7 @@ const globalConfig = { } const translationsDirectoryPath = './langs' -const supportedRoutes = ["blog", "contact", "invitation", ""] +const supportedRoutes = ["blog", "contact", "invitation", "docs", ""] let supportedLangs = [] fs.readdir(translationsDirectoryPath, (err, files) => { if (err) { @@ -26,7 +26,7 @@ fs.readdir(translationsDirectoryPath, (err, files) => { return file.endsWith('.json') && fs.statSync(translationsDirectoryPath + '/' + file).isFile() }) supportedLangs = jsonFileNames.map(file => file.replace('.json', '')) -}); +}) const translations = require("./translations.json") @@ -40,18 +40,41 @@ module.exports = function (ty) { return "en" }) - ty.addShortcode("getlangRoute", (path) => { - const lang = path.split("/")[1] - if (supportedRoutes.includes(lang)) return "" - if (supportedLangs.includes(lang)) return `/${lang}` - return "/en" + ty.addFilter("getlang", (path) => { + const urlParts = path.split("/") + if (urlParts[1] === "docs") { + if (urlParts[2] === "lang") { + return urlParts[3] + } + return "en" + } + else { + if (supportedRoutes.includes(urlParts[1])) return "en" + else if (supportedLangs.includes(urlParts[1])) return urlParts[1] + return "en" + } }) ty.addShortcode("completeRoute", (obj) => { const urlParts = obj.url.split("/") + if (supportedRoutes.includes(urlParts[1])) { if (urlParts[1] == "blog") return `/blog` + + else if (urlParts[1] === "docs") { + if (urlParts[2] === "lang") { + if (obj.lang === "en") + return `/docs/${urlParts.slice(4).join('/')}` + return `/docs/lang/${obj.lang}/${urlParts.slice(4).join('/')}` + } + else { + if (obj.lang === "en") + return `${obj.url}` + return `/docs/lang/${obj.lang}/${urlParts.slice(2).join('/')}` + } + } + else if (obj.lang === "en") return `${obj.url}` return `/${obj.lang}${obj.url}` @@ -73,7 +96,7 @@ module.exports = function (ty) { '*': 'en' }, defaultLocale: 'en', - }); + }) // Keeps the same directory structure. ty.addPassthroughCopy("src/assets/") @@ -87,7 +110,10 @@ module.exports = function (ty) { ty.addPassthroughCopy("src/hero-phone") ty.addPassthroughCopy("src/hero-phone-dark") ty.addPassthroughCopy("src/blog/images") - supportedLangs.forEach(lang => ty.addPassthroughCopy(`src/${lang}/blog/images`)) + ty.addPassthroughCopy("src/docs/*.png") + ty.addPassthroughCopy("src/docs/images") + ty.addPassthroughCopy("src/docs/protocol/diagrams") + ty.addPassthroughCopy("src/docs/protocol/*.json") ty.addPassthroughCopy("src/images") ty.addPassthroughCopy("src/CNAME") ty.addPassthroughCopy("src/.well-known") @@ -96,6 +122,83 @@ module.exports = function (ty) { return collection.getFilteredByGlob('src/blog/*.md').reverse() }) + ty.addCollection('docs', function (collection) { + const docs = collection.getFilteredByGlob('src/docs/**/*.md') + .map(doc => { + return { url: doc.url, title: doc.data.title, inputPath: doc.inputPath } + }) + + let referenceContent = fs.readFileSync(path.resolve(__dirname, 'src/_data/docs_sidebar.json'), 'utf-8') + referenceContent = JSON.parse(referenceContent).items + + const newDocs = [] + + referenceContent.forEach(referenceMenu => { + referenceMenu.data.forEach(referenceSubmenu => { + docs.forEach(doc => { + const url = doc.url.replace("/docs/", "") + const urlParts = url.split("/") + + if (doc.inputPath.includes(referenceSubmenu)) { + if (urlParts.length === 1 && urlParts[0] !== "") { + const index = newDocs.findIndex((ele) => ele.lang === 'en' && ele.menu === referenceMenu.menu) + if (index !== -1) { + newDocs[index].data.push(doc) + } + else { + newDocs.push({ + lang: 'en', + menu: referenceMenu.menu, + data: [doc], + }) + } + } + else if (urlParts.length > 1 && urlParts[0] !== "" && urlParts[0] !== "lang") { + const index = newDocs.findIndex((ele) => ele.lang === 'en' && ele.menu === referenceMenu.menu) + if (index !== -1) { + newDocs[index].data.push(doc) + } else { + newDocs.push({ + lang: 'en', + menu: referenceMenu.menu, + data: [doc], + }) + } + } + else if (urlParts.length === 3 && urlParts[0] === "lang" && urlParts[2] !== '') { + const index = newDocs.findIndex((ele) => ele.lang === urlParts[1] && ele.menu === referenceMenu.menu) + if (index !== -1) { + newDocs[index].data.push(doc) + } + else { + newDocs.push({ + lang: urlParts[1], + menu: referenceMenu.menu, + data: [doc], + }) + } + } + else if (urlParts.length > 3 && urlParts[0] === "lang" && urlParts[2] !== '') { + const index = newDocs.findIndex((ele) => ele.lang === urlParts[1] && ele.menu === referenceMenu.menu) + if (index !== -1) { + newDocs[index].data.push(doc) + } + else { + newDocs.push({ + lang: urlParts[1], + menu: referenceMenu.menu, + data: [doc], + }) + } + } + } + }) + }) + }) + + return newDocs + }) + ty.addWatchTarget("src/css") ty.addWatchTarget("markdown/") ty.addWatchTarget("components/Card.js") diff --git a/website/customize_docs_frontmatter.js b/website/customize_docs_frontmatter.js new file mode 100644 index 0000000000..8f2546e168 --- /dev/null +++ b/website/customize_docs_frontmatter.js @@ -0,0 +1,90 @@ +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); + +const directoryPath = path.resolve(__dirname, 'src/docs'); +const langFolder = 'lang'; +const enFiles = {}; + +function traverseDirectory(directory, currentLanguage = 'en', result = {}, callback) { + const filesAndDirectories = fs.readdirSync(directory); + + filesAndDirectories.forEach((fileOrDirectoryName) => { + const fullPath = path.join(directory, fileOrDirectoryName); + + if (fs.statSync(fullPath).isDirectory()) { + // If the subdirectory is inside the 'lang' folder, update the current language + if (directory.endsWith('/lang')) { + currentLanguage = fileOrDirectoryName; + } + + // Recursively traverse the subdirectories + traverseDirectory(fullPath, currentLanguage, result, callback); + } else { + // Process the file only if it has the '.md' extension + if (path.extname(fullPath) === '.md') { + // Add the language to the file's language array or create a new array if it doesn't exist + const fileName = path.basename(fullPath, '.md'); + if (!result[fileName]) { + result[fileName] = []; + } + result[fileName].push(currentLanguage); + } + if (callback) { + callback(fullPath, currentLanguage); + } + } + }); + + return result; +} + +const fileLanguageMapping = traverseDirectory(directoryPath); + +// Update the frontmatter of each Markdown file +Object.entries(fileLanguageMapping).forEach(([fileName, languages]) => { + // Find and update the frontmatter of each Markdown file + traverseDirectory(directoryPath, null, {}, (fullPath, currentLanguage) => { + if (path.basename(fullPath) === `${fileName}.md`) { + // Read the existing frontmatter + const fileContent = fs.readFileSync(fullPath, 'utf-8'); + const parsedMatter = matter(fileContent); + const relativePath = path.relative(directoryPath, fullPath); + + // Calculate the permalink based on the file's location + const linkPath = path.relative(directoryPath, fullPath).replace(/\.md$/, '.html'); + const permalink = `/docs/${linkPath}`.toLowerCase(); + parsedMatter.data.permalink = permalink; + + // Update the frontmatter with the new languages list + parsedMatter.data.supportedLangsForDoc = languages; + + // Add the layout value + parsedMatter.data.layout = 'layouts/doc.html'; + + if (fullPath.startsWith(path.join(directoryPath, langFolder))) { + // Non-English files + const [language, ...rest] = relativePath.split(path.sep).slice(1); + const enFilePath = path.join(directoryPath, ...rest); + + if (enFiles[enFilePath]) { + const enRevision = new Date(enFiles[enFilePath].revision); + const currentRevision = new Date(parsedMatter.data.revision); + + const isOld = currentRevision < enRevision; + // Add the version value + parsedMatter.data.version = isOld ? 'old' : 'new'; + } + } else { + // English files + enFiles[fullPath] = { revision: parsedMatter.data.revision }; + // Add the version value + parsedMatter.data.version = 'new'; + } + + // Save the updated frontmatter and content back to the file + const updatedFileContent = matter.stringify(parsedMatter.content, parsedMatter.data); + fs.writeFileSync(fullPath, updatedFileContent, 'utf-8'); + } + }); +}); diff --git a/website/langs/en.json b/website/langs/en.json index 1a8b0baa85..7b0f35b1c1 100644 --- a/website/langs/en.json +++ b/website/langs/en.json @@ -209,5 +209,27 @@ "comparison-section-list-point-5": "Does not protect users' metadata", "comparison-section-list-point-6": "While P2P are distributed, they are not federated - they operate as a single network", "comparison-section-list-point-7": "P2P networks either have a central authority or the whole network can be compromised", - "see-here": "see here" + "see-here": "see here", + "guide-dropdown-1": "Quick start", + "guide-dropdown-2": "Sending messages", + "guide-dropdown-3": "Secret groups", + "guide-dropdown-4": "Chat profiles", + "guide-dropdown-5": "Managing data", + "guide-dropdown-6": "Audio & video Calls", + "guide-dropdown-7": "Privacy & security", + "guide-dropdown-8": "App settings", + "guide-dropdown-9": "Making connections", + "guide": "Guide", + "docs-dropdown-1": "SimpleX platform", + "docs-dropdown-2": "Accessing Android files", + "docs-dropdown-3": "Accessing chat database", + "docs-dropdown-4": "Host SMP Server", + "docs-dropdown-5": "Host XFTP Server", + "docs-dropdown-6": "WebRTC servers", + "docs-dropdown-7": "Translate SimpleX Chat", + "newer-version-of-eng-msg": "There is a newer version of this page in English.", + "click-to-see": "Click to see", + "menu": "Menu", + "on-this-page": "On this page", + "back-to-top": "Back to top" } \ No newline at end of file diff --git a/website/merge_translations.js b/website/merge_translations.js index 37de897de8..163606d5e1 100644 --- a/website/merge_translations.js +++ b/website/merge_translations.js @@ -9,11 +9,11 @@ const jsonFileNames = files.filter(file => file.endsWith('.json')); supportedLangs = jsonFileNames.map(file => file.replace('.json', '')) // keys of the english language are used as the base keys -const enStrings = require("./langs/en.json") +const enStrings = require(path.resolve(__dirname, "langs/en.json")) const languages = {} for (const lang of supportedLangs) { - languages[lang] = require(`./langs/${lang}.json`) + languages[lang] = require(path.resolve(__dirname, `langs/${lang}.json`)) } // this program generates a combined translations.json file @@ -27,9 +27,7 @@ for (const key in enStrings) { translations[key] = langStrings } -saveFile("translations.json", translations) -// the list in the supported_languages.json file is used as the reference list for displaying available languages on the frontend -saveFile("src/_data/supported_languages.json", {"langs": supportedLangs}) +saveFile(path.resolve(__dirname, "translations.json"), translations) function saveFile(relPath, data) { filePath = path.resolve(__dirname, relPath) diff --git a/website/package.json b/website/package.json index 5560e7bece..c85dd3c00e 100644 --- a/website/package.json +++ b/website/package.json @@ -9,8 +9,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "build:js": "cp ./node_modules/qrcode/build/qrcode.js ./src/js/ && ./copy_call.sh", "build:eleventy": "eleventy", - "build:tailwind": "npx tailwindcss -i ./tailwind.css -o ./_site/css/tailwind.css", - "watch:tailwind": "npx tailwindcss -i ./tailwind.css -o ./_site/css/tailwind.css --watch" + "build:tailwind": "npx tailwindcss -i tailwind.css -o _site/css/tailwind.css", + "watch:tailwind": "npx tailwindcss -i tailwind.css -o _site/css/tailwind.css --watch" }, "keywords": [], "author": "", @@ -25,9 +25,10 @@ "markdown-it-replace-link": "^1.1.0", "qrcode": "1.5.1", "slugify": "^1.6.5", - "tailwindcss": "^3.2.7" + "tailwindcss": "3.3.1" }, "dependencies": { - "eleventy-plugin-i18n": "^0.1.3" + "eleventy-plugin-i18n": "^0.1.3", + "gray-matter": "^4.0.3" } } diff --git a/website/src/_data/docs_dropdown.json b/website/src/_data/docs_dropdown.json new file mode 100644 index 0000000000..ff05242c81 --- /dev/null +++ b/website/src/_data/docs_dropdown.json @@ -0,0 +1,32 @@ +{ + "items": [ + { + "title": "docs-dropdown-1", + "url": "/docs/simplex.html" + }, + { + "title": "docs-dropdown-2", + "url": "/docs/android.html" + }, + { + "title": "docs-dropdown-3", + "url": "/docs/sql.html" + }, + { + "title": "docs-dropdown-4", + "url": "/docs/server.html" + }, + { + "title": "docs-dropdown-5", + "url": "/docs/xftp-server.html" + }, + { + "title": "docs-dropdown-6", + "url": "/docs/webrtc.html" + }, + { + "title": "docs-dropdown-7", + "url": "/docs/translations.html" + } + ] +} \ No newline at end of file diff --git a/website/src/_data/docs_sidebar.json b/website/src/_data/docs_sidebar.json new file mode 100644 index 0000000000..f05ae0ab1a --- /dev/null +++ b/website/src/_data/docs_sidebar.json @@ -0,0 +1,38 @@ +{ + "items": [ + { + "menu": "Guide", + "data": [ + "README.md", + "send-messages.md", + "secret-groups.md", + "chat-profiles.md", + "managing-data.md", + "audio-video-calls.md", + "privacy-security.md", + "app-settings.md", + "making-connections.md" + ] + }, + { + "menu": "Reference", + "data": [ + "SIMPLEX.md", + "ANDROID.md", + "CLI.md", + "SQL.md", + "CONTRIBUTING.md", + "SERVER.md", + "TRANSLATIONS.md", + "WEBRTC.md", + "XFTP-SERVER.md" + ] + }, + { + "menu": "Protocol", + "data": [ + "simplex-chat.md" + ] + } + ] +} \ No newline at end of file diff --git a/website/src/_data/guide_dropdown.json b/website/src/_data/guide_dropdown.json new file mode 100644 index 0000000000..fc6383c709 --- /dev/null +++ b/website/src/_data/guide_dropdown.json @@ -0,0 +1,40 @@ +{ + "items": [ + { + "title": "guide-dropdown-1", + "url": "/docs/guide/readme.html" + }, + { + "title": "guide-dropdown-2", + "url": "/docs/guide/send-messages.html" + }, + { + "title": "guide-dropdown-3", + "url": "/docs/guide/secret-groups.html" + }, + { + "title": "guide-dropdown-4", + "url": "/docs/guide/chat-profiles.html" + }, + { + "title": "guide-dropdown-5", + "url": "/docs/guide/managing-data.html" + }, + { + "title": "guide-dropdown-6", + "url": "/docs/guide/audio-video-calls.html" + }, + { + "title": "guide-dropdown-7", + "url": "/docs/guide/privacy-security.html" + }, + { + "title": "guide-dropdown-8", + "url": "/docs/guide/app-settings.html" + }, + { + "title": "guide-dropdown-9", + "url": "/docs/guide/making-connections.html" + } + ] +} \ No newline at end of file diff --git a/website/src/_includes/footer.html b/website/src/_includes/footer.html index a3b984be38..42d8591fda 100644 --- a/website/src/_includes/footer.html +++ b/website/src/_includes/footer.html @@ -1,38 +1,51 @@ +{% set lang = page.url | getlang %}
- logo - + logo +
- SimpleX Whitepaper + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + SimpleX Whitepaper + + - {{ "simplex-chat-protocol" | i18n({}, lang ) | safe }} + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + {{ "simplex-chat-protocol" | i18n({}, lang ) | safe }} + + - {{ "terms-and-privacy-policy" | i18n({}, lang ) | safe }} + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + {{ "terms-and-privacy-policy" | i18n({}, lang ) | safe }} + + - {{ "donate" | i18n({}, lang ) | safe }} + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + {{ "donate" | i18n({}, lang ) | safe }} + +
- {{ "terminal-cli" | i18n({}, lang ) | safe }} + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + {{ "terminal-cli" | i18n({}, lang ) | safe }} + + - TypeScript SDK + class="text-grey-black dark:text-white text-[14px] font-medium leading-[28px] tracking-[0.01em] mb-3 flex items-center gap-1"> + TypeScript SDK + +
diff --git a/website/src/_includes/hero.html b/website/src/_includes/hero.html index 8b3fff748d..56513701c9 100644 --- a/website/src/_includes/hero.html +++ b/website/src/_includes/hero.html @@ -17,14 +17,39 @@ {{ "hero-overlay-2-textlink" | i18n({}, lang ) | safe }} {{ overlay(hero_overlays.sections[0], lang) }} +
- -
- - - - - + + +
+ +
+ + + + + +
diff --git a/website/src/_includes/layouts/article.html b/website/src/_includes/layouts/article.html index 922d9905a1..783b6ece7c 100644 --- a/website/src/_includes/layouts/article.html +++ b/website/src/_includes/layouts/article.html @@ -25,6 +25,7 @@ + @@ -41,6 +42,7 @@
{% include "footer.html" %} + diff --git a/website/src/_includes/layouts/doc.html b/website/src/_includes/layouts/doc.html new file mode 100644 index 0000000000..091f98bdc7 --- /dev/null +++ b/website/src/_includes/layouts/doc.html @@ -0,0 +1,123 @@ +{% set lang = page.url | getlang %} + + + + + + + + {{ title }} + + + + + + + + + + + +
+ {% include "navbar.html" %} +
+ +
+
+
+ + +
+
+
+ + {% include "footer.html" %} + + + + + + \ No newline at end of file diff --git a/website/src/_includes/navbar.html b/website/src/_includes/navbar.html index a756701416..50fca514b6 100644 --- a/website/src/_includes/navbar.html +++ b/website/src/_includes/navbar.html @@ -1,16 +1,24 @@ +{% set lang = page.url | getlang %} +{% set langName = "aaa" %} +{% for language in languages.languages %} + {% if language.label == page.url | getlang %} + {% set langName = language.name %} + {% endif %} +{% endfor %} +
- - - - + + + +