diff --git a/app/build.gradle b/app/build.gradle index 7e126ba3d6..cb9b24d5d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,9 +12,9 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 34 - versionCode 376 - versionName "0.87.7" - buildConfigField "String", "RELEASE_NOTES_ID", "\"863124b6592359494396c22b917f9d251ad12ac31b09ea29e0265e3c088ce9f8\"" + versionCode 379 + versionName "0.88.2" + buildConfigField "String", "RELEASE_NOTES_ID", "\"2a34cbadd03212c8162e1ff896ba12641821088a2ec8d5e40d54aa80c0510800\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt index b0712305af..d677caaec2 100644 --- a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt +++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt @@ -43,7 +43,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted @@ -60,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckifItNeedsToRequestNoti import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer +import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -79,7 +79,7 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView LoadDistributors { currentDistributor, list, readableListWithExplainer -> if (readableListWithExplainer.size > 1) { SpinnerSelectionDialog( - title = stringResource(id = R.string.select_push_server), + title = stringRes(id = R.string.select_push_server), options = readableListWithExplainer, onSelect = { index -> if (list[index] == "None") { @@ -101,9 +101,9 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView } else { AlertDialog( onDismissRequest = { distributorPresent = true }, - title = { Text(stringResource(R.string.push_server_install_app)) }, + title = { Text(stringRes(R.string.push_server_install_app)) }, text = { - val content = stringResource(R.string.push_server_install_app_description) + val content = stringRes(R.string.push_server_install_app_description) val astNode = remember { @@ -128,7 +128,7 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView sharedPreferencesViewModel.dontShowPushNotificationSelector() }, ) { - Text(stringResource(R.string.quick_action_dont_show_again_button)) + Text(stringRes(R.string.quick_action_dont_show_again_button)) } Button( onClick = { distributorPresent = true }, @@ -142,7 +142,7 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView contentDescription = null, ) Spacer(Modifier.width(8.dp)) - Text(stringResource(R.string.error_dialog_button_ok)) + Text(stringRes(R.string.error_dialog_button_ok)) } } } @@ -168,20 +168,19 @@ fun LoadDistributors(onInner: @Composable (String, ImmutableList, Immuta } val readableListWithExplainer = - PushDistributorHandler.formattedDistributorNames() + PushDistributorHandler + .formattedDistributorNames() .mapIndexed { index, name -> TitleExplainer( name, - stringResource(id = R.string.push_server_uses_app_explainer, list[index]), + stringRes(id = R.string.push_server_uses_app_explainer, list[index]), ) - } - .plus( + }.plus( TitleExplainer( - stringResource(id = R.string.push_server_none), - stringResource(id = R.string.push_server_none_explainer), + stringRes(id = R.string.push_server_none), + stringRes(id = R.string.push_server_none_explainer), ), - ) - .toImmutableList() + ).toImmutableList() onInner( currentDistributor, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 765456d702..3b21ef66d8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -99,14 +99,7 @@ private object PrefKeys { const val WARN_ABOUT_REPORTS = "warn_about_reports" const val FILTER_SPAM_FROM_STRANGERS = "filter_spam_from_strangers" const val LAST_READ_PER_ROUTE = "last_read_route_per_route" - const val AUTOMATICALLY_SHOW_IMAGES = "automatically_show_images" - const val AUTOMATICALLY_START_PLAYBACK = "automatically_start_playback" - const val THEME = "theme" - const val PREFERRED_LANGUAGE = "preferred_Language" - const val AUTOMATICALLY_LOAD_URL_PREVIEW = "automatically_load_url_preview" - const val AUTOMATICALLY_HIDE_NAV_BARS = "automatically_hide_nav_bars" const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer" - const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture" const val SIGNER_PACKAGE_NAME = "signer_package_name" const val HAS_DONATED_IN_VERSION = "has_donated_in_version" const val PENDING_ATTESTATIONS = "pending_attestations" @@ -148,7 +141,7 @@ object LocalPreferences { Event.mapper.readValue>(it) } - if (newSystemOfAccounts != null && newSystemOfAccounts.isNotEmpty()) { + if (!newSystemOfAccounts.isNullOrEmpty()) { savedAccounts = newSystemOfAccounts } else { val oldAccounts = getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(COMMA) ?: listOf() @@ -164,6 +157,8 @@ object LocalPreferences { } savedAccounts = migrated + + edit().apply { putString(PrefKeys.ALL_ACCOUNT_INFO, Event.mapper.writeValueAsString(savedAccounts)) }.apply() } } } @@ -265,9 +260,7 @@ object LocalPreferences { saveToEncryptedStorage(account) } - fun allSavedAccounts(): List { - return savedAccounts() - } + fun allSavedAccounts(): List = savedAccounts() suspend fun saveToEncryptedStorage(account: Account) { Log.d("LocalPreferences", "Saving to encrypted storage") @@ -345,14 +338,11 @@ object LocalPreferences { PrefKeys.PENDING_ATTESTATIONS, Event.mapper.writeValueAsString(account.pendingAttestations.value), ) - } - .apply() + }.apply() } } - suspend fun loadCurrentAccountFromEncryptedStorage(): Account? { - return currentAccount()?.let { loadCurrentAccountFromEncryptedStorage(it) } - } + suspend fun loadCurrentAccountFromEncryptedStorage(): Account? = currentAccount()?.let { loadCurrentAccountFromEncryptedStorage(it) } suspend fun saveSharedSettings( sharedSettings: Settings, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt index 3d5993718f..f1c4332645 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Settings.kt @@ -34,17 +34,20 @@ data class Settings( val automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS, val dontShowPushNotificationSelector: Boolean = false, val dontAskForNotificationPermissions: Boolean = false, - val featureSet: FeatureSetType = FeatureSetType.COMPLETE, + val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED, ) -enum class ThemeType(val screenCode: Int, val resourceId: Int) { +enum class ThemeType( + val screenCode: Int, + val resourceId: Int, +) { SYSTEM(0, R.string.system), LIGHT(1, R.string.light), DARK(2, R.string.dark), } -fun parseThemeType(code: Int?): ThemeType { - return when (code) { +fun parseThemeType(code: Int?): ThemeType = + when (code) { ThemeType.SYSTEM.screenCode -> ThemeType.SYSTEM ThemeType.LIGHT.screenCode -> ThemeType.LIGHT ThemeType.DARK.screenCode -> ThemeType.DARK @@ -52,21 +55,28 @@ fun parseThemeType(code: Int?): ThemeType { ThemeType.SYSTEM } } -} -enum class ConnectivityType(val prefCode: Boolean?, val screenCode: Int, val resourceId: Int) { +enum class ConnectivityType( + val prefCode: Boolean?, + val screenCode: Int, + val resourceId: Int, +) { ALWAYS(null, 0, R.string.connectivity_type_always), WIFI_ONLY(true, 1, R.string.connectivity_type_wifi_only), NEVER(false, 2, R.string.connectivity_type_never), } -enum class FeatureSetType(val screenCode: Int, val resourceId: Int) { +enum class FeatureSetType( + val screenCode: Int, + val resourceId: Int, +) { COMPLETE(0, R.string.ui_feature_set_type_complete), SIMPLIFIED(1, R.string.ui_feature_set_type_simplified), + PERFORMANCE(2, R.string.ui_feature_set_type_performance), } -fun parseConnectivityType(code: Boolean?): ConnectivityType { - return when (code) { +fun parseConnectivityType(code: Boolean?): ConnectivityType = + when (code) { ConnectivityType.ALWAYS.prefCode -> ConnectivityType.ALWAYS ConnectivityType.WIFI_ONLY.prefCode -> ConnectivityType.WIFI_ONLY ConnectivityType.NEVER.prefCode -> ConnectivityType.NEVER @@ -74,10 +84,9 @@ fun parseConnectivityType(code: Boolean?): ConnectivityType { ConnectivityType.ALWAYS } } -} -fun parseConnectivityType(screenCode: Int): ConnectivityType { - return when (screenCode) { +fun parseConnectivityType(screenCode: Int): ConnectivityType = + when (screenCode) { ConnectivityType.ALWAYS.screenCode -> ConnectivityType.ALWAYS ConnectivityType.WIFI_ONLY.screenCode -> ConnectivityType.WIFI_ONLY ConnectivityType.NEVER.screenCode -> ConnectivityType.NEVER @@ -85,39 +94,40 @@ fun parseConnectivityType(screenCode: Int): ConnectivityType { ConnectivityType.ALWAYS } } -} -fun parseFeatureSetType(screenCode: Int): FeatureSetType { - return when (screenCode) { +fun parseFeatureSetType(screenCode: Int): FeatureSetType = + when (screenCode) { FeatureSetType.COMPLETE.screenCode -> FeatureSetType.COMPLETE FeatureSetType.SIMPLIFIED.screenCode -> FeatureSetType.SIMPLIFIED + FeatureSetType.PERFORMANCE.screenCode -> FeatureSetType.PERFORMANCE else -> { FeatureSetType.COMPLETE } } -} -enum class BooleanType(val prefCode: Boolean?, val screenCode: Int, val reourceId: Int) { +enum class BooleanType( + val prefCode: Boolean?, + val screenCode: Int, + val reourceId: Int, +) { ALWAYS(null, 0, R.string.connectivity_type_always), NEVER(false, 1, R.string.connectivity_type_never), } -fun parseBooleanType(code: Boolean?): BooleanType { - return when (code) { +fun parseBooleanType(code: Boolean?): BooleanType = + when (code) { BooleanType.ALWAYS.prefCode -> BooleanType.ALWAYS BooleanType.NEVER.prefCode -> BooleanType.NEVER else -> { BooleanType.ALWAYS } } -} -fun parseBooleanType(screenCode: Int): BooleanType { - return when (screenCode) { +fun parseBooleanType(screenCode: Int): BooleanType = + when (screenCode) { BooleanType.ALWAYS.screenCode -> BooleanType.ALWAYS BooleanType.NEVER.screenCode -> BooleanType.NEVER else -> { BooleanType.ALWAYS } } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index cd11404445..8a7027a667 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -28,13 +28,16 @@ import com.vitorpamplona.quartz.events.ImmutableListOfLists object CachedRichTextParser { val richTextCache = LruCache(50) + fun getCached(content: String): RichTextViewerState? = richTextCache[content] + fun parseText( content: String, tags: ImmutableListOfLists, callbackUri: String? = null, ): RichTextViewerState { - return if (richTextCache[content] != null) { - richTextCache[content] + val cached = richTextCache[content] + return if (cached != null) { + cached } else { val newUrls = RichTextParser().parseText(content, tags, callbackUri) richTextCache.put(content, newUrls) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt index ce3b280c6d..50203703b1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt @@ -28,6 +28,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.components.GenericLoadable +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.Event import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request @@ -46,9 +47,7 @@ data class CashuToken( object CachedCashuProcessor { val cashuCache = LruCache>(20) - fun cached(token: String): GenericLoadable { - return cashuCache[token] ?: GenericLoadable.Loading() - } + fun cached(token: String): GenericLoadable = cashuCache[token] ?: GenericLoadable.Loading() fun parse(token: String): GenericLoadable { if (cashuCache[token] !is GenericLoadable.Loaded) { @@ -150,7 +149,12 @@ class CashuProcessor { val mediaType = "application/json; charset=utf-8".toMediaType() val requestBody = jsonObject.toString().toRequestBody(mediaType) - val request = Request.Builder().url(url).post(requestBody).build() + val request = + Request + .Builder() + .url(url) + .post(requestBody) + .build() client.newCall(request).execute().use { val body = it.body.string() @@ -163,13 +167,19 @@ class CashuProcessor { feeCost, ) } else { - val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null } + val msg = + tree + ?.get("detail") + ?.asText() + ?.split('.') + ?.getOrNull(0) + ?.ifBlank { null } onError( - context.getString(R.string.cashu_failed_redemption), + stringRes(context, R.string.cashu_failed_redemption), if (msg != null) { - context.getString(R.string.cashu_failed_redemption_explainer_error_msg, msg) + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg, msg) } else { - context.getString(R.string.cashu_failed_redemption_explainer_error_msg) + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg) }, ) } @@ -177,8 +187,8 @@ class CashuProcessor { } catch (e: Exception) { if (e is CancellationException) throw e onError( - context.getString(R.string.cashu_successful_redemption), - context.getString(R.string.cashu_failed_redemption_explainer_error_msg, e.message), + stringRes(context, R.string.cashu_successful_redemption), + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg, e.message), ) } } @@ -203,7 +213,12 @@ class CashuProcessor { val mediaType = "application/json; charset=utf-8".toMediaType() val requestBody = jsonObject.toString().toRequestBody(mediaType) - val request = Request.Builder().url(url).post(requestBody).build() + val request = + Request + .Builder() + .url(url) + .post(requestBody) + .build() client.newCall(request).execute().use { val body = it.body.string() @@ -213,21 +228,28 @@ class CashuProcessor { if (successful) { onSuccess( - context.getString(R.string.cashu_successful_redemption), - context.getString( + stringRes(context, R.string.cashu_successful_redemption), + stringRes( + context, R.string.cashu_successful_redemption_explainer, token.totalAmount.toString(), fees.toString(), ), ) } else { - val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null } + val msg = + tree + ?.get("detail") + ?.asText() + ?.split('.') + ?.getOrNull(0) + ?.ifBlank { null } onError( - context.getString(R.string.cashu_failed_redemption), + stringRes(context, R.string.cashu_failed_redemption), if (msg != null) { - context.getString(R.string.cashu_failed_redemption_explainer_error_msg, msg) + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg, msg) } else { - context.getString(R.string.cashu_failed_redemption_explainer_error_msg) + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg) }, ) } @@ -235,8 +257,8 @@ class CashuProcessor { } catch (e: Exception) { if (e is CancellationException) throw e onError( - context.getString(R.string.cashu_successful_redemption), - context.getString(R.string.cashu_failed_redemption_explainer_error_msg, e.message), + stringRes(context, R.string.cashu_successful_redemption), + stringRes(context, R.string.cashu_failed_redemption_explainer_error_msg, e.message), ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt index 674e8fa0f8..1846124976 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt @@ -25,12 +25,9 @@ import com.vitorpamplona.amethyst.BuildConfig import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import okhttp3.Call -import okhttp3.Callback import okhttp3.Request -import okhttp3.Response -class Nip05NostrAddressVerifier() { +class Nip05NostrAddressVerifier { fun assembleUrl(nip05address: String): String? { val parts = nip05address.trim().split("@") @@ -46,7 +43,7 @@ class Nip05NostrAddressVerifier() { suspend fun fetchNip05Json( nip05: String, - onSuccess: (String) -> Unit, + onSuccess: suspend (String) -> Unit, onError: (String) -> Unit, ) = withContext(Dispatchers.IO) { checkNotInMainThread() @@ -60,52 +57,39 @@ class Nip05NostrAddressVerifier() { try { val request = - Request.Builder() + Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(url) .build() - - HttpClientManager.getHttpClient() + // Fetchers MUST ignore any HTTP redirects given by the /.well-known/nostr.json endpoint. + HttpClientManager + .getHttpClient() + .newBuilder() + .followRedirects(false) + .build() .newCall(request) - .enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response, - ) { - checkNotInMainThread() + .execute() + .use { + checkNotInMainThread() - response.use { - if (it.isSuccessful) { - onSuccess(it.body.string()) - } else { - onError( - "Could not resolve $nip05. Error: ${it.code}. Check if the server is up and if the address $nip05 is correct", - ) - } - } - } - - override fun onFailure( - call: Call, - e: java.io.IOException, - ) { - onError( - "Could not resolve $url. Check if the server is up and if the address $nip05 is correct", - ) - e.printStackTrace() - } - }, - ) + if (it.isSuccessful) { + onSuccess(it.body.string()) + } else { + onError( + "Could not resolve $nip05. Error: ${it.code}. Check if the server is up and if the address $nip05 is correct", + ) + } + } } catch (e: Exception) { if (e is CancellationException) throw e - onError("Could not resolve '$url': ${e.message}") + onError("Could not resolve NIP-05 $nip05 as URL $url: ${e.message}") } } suspend fun verifyNip05( nip05: String, - onSuccess: (String) -> Unit, + onSuccess: suspend (String) -> Unit, onError: (String) -> Unit, ) { // check fails on tests diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 7b8761b278..298bcb6b3e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.amethyst.service.relays.EOSETime +import com.vitorpamplona.amethyst.service.relays.EVENT_FINDER_TYPES import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -76,8 +77,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { val latestEOSEs = EOSEAccount() val hasLoadedTheBasics = mutableMapOf() - fun createAccountContactListFilter(): TypedFilter { - return TypedFilter( + fun createAccountContactListFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -86,10 +87,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { limit = 1, ), ) - } - fun createAccountMetadataFilter(): TypedFilter { - return TypedFilter( + fun createAccountMetadataFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -98,10 +98,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { limit = 1, ), ) - } - fun createAccountRelayListFilter(): TypedFilter { - return TypedFilter( + fun createAccountRelayListFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -110,12 +109,11 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { limit = 10, ), ) - } fun createOtherAccountsBaseFilter(): TypedFilter? { if (otherAccounts.isEmpty()) return null return TypedFilter( - types = COMMON_FEED_TYPES, + types = EVENT_FINDER_TYPES, filter = JsonFilter( kinds = @@ -136,8 +134,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { ) } - fun createAccountSettingsFilter(): TypedFilter { - return TypedFilter( + fun createAccountSettingsFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -146,10 +144,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { limit = 100, ), ) - } - fun createAccountReportsFilter(): TypedFilter { - return TypedFilter( + fun createAccountReportsFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -162,10 +159,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { ?.relayList, ), ) - } - fun createAccountLastPostsListFilter(): TypedFilter { - return TypedFilter( + fun createAccountLastPostsListFilter(): TypedFilter = + TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( @@ -173,7 +169,6 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { limit = 400, ), ) - } fun createNotificationFilter(): TypedFilter { val since = @@ -404,8 +399,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { } } - override fun updateChannelFilters() { - return if (hasLoadedTheBasics[account.userProfile()] != null) { + override fun updateChannelFilters() = + if (hasLoadedTheBasics[account.userProfile()] != null) { // gets everything about the user logged in accountChannel.typedFilters = listOfNotNull( @@ -419,8 +414,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { createAccountSettingsFilter(), createAccountLastPostsListFilter(), createOtherAccountsBaseFilter(), - ) - .ifEmpty { null } + ).ifEmpty { null } } else { // just the basics. accountChannel.typedFilters = @@ -429,10 +423,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { createAccountContactListFilter(), createAccountRelayListFilter(), createAccountSettingsFilter(), - ) - .ifEmpty { null } + ).ifEmpty { null } } - } override fun auth( relay: Relay, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt index 6f5ac49465..e1a768992c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt @@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.events.ReportEvent import com.vitorpamplona.quartz.events.StatusEvent object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") { - var usersToWatch = setOf() + private var usersToWatch = setOf() fun createUserMetadataFilter(): List? { if (usersToWatch.isEmpty()) return null @@ -83,8 +83,7 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") { ), ), ) - } - .flatten() + }.flatten() } val userChannel = @@ -110,8 +109,7 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") { listOfNotNull( createUserMetadataFilter(), createUserMetadataStatusReportFilter(), - ) - .flatten() + ).flatten() .ifEmpty { null } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index 9c2db5c657..58ab69b5bb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -96,6 +96,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") { JsonFilter( kinds = listOf(LnZapEvent.KIND), tags = mapOf("p" to listOf(it.pubkeyHex)), + limit = 200, ), ) } @@ -178,7 +179,6 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") { createAcceptedAwardsFilter(), createReceivedAwardsFilter(), createBookmarksFilter(), - ) - .ifEmpty { null } + ).ifEmpty { null } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt index 4839064959..8489374d33 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.screen.loggedIn.collectSuccessfulSigningOperations +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent import com.vitorpamplona.quartz.events.AppDefinitionEvent import com.vitorpamplona.quartz.events.LiveActivitiesEvent @@ -41,7 +42,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.math.round -class ZapPaymentHandler(val account: Account) { +class ZapPaymentHandler( + val account: Account, +) { @Immutable data class Payable( val info: ZapSplitSetup, @@ -79,8 +82,9 @@ class ZapPaymentHandler(val account: Account) { if (lud16.isNullOrBlank()) { onError( - context.getString(R.string.missing_lud16), - context.getString( + stringRes(context, R.string.missing_lud16), + stringRes( + context, R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats, ), ) @@ -94,8 +98,9 @@ class ZapPaymentHandler(val account: Account) { if (lud16.isNullOrBlank()) { onError( - context.getString(R.string.missing_lud16), - context.getString( + stringRes(context, R.string.missing_lud16), + stringRes( + context, R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats, ), ) @@ -132,14 +137,15 @@ class ZapPaymentHandler(val account: Account) { } } else { onPayViaIntent( - it.map { - Payable( - info = it.key.first, - user = it.key.second.user, - amountMilliSats = it.value.zapValue, - invoice = it.value.invoice, - ) - }.toImmutableList(), + it + .map { + Payable( + info = it.key.first, + user = it.key.second.user, + amountMilliSats = it.value.zapValue, + invoice = it.value.invoice, + ) + }.toImmutableList(), ) onProgress(0f) @@ -172,13 +178,16 @@ class ZapPaymentHandler(val account: Account) { onAllDone: suspend (MutableMap) -> Unit, ) { val authorRelayList = - note.author?.pubkeyHex?.let { - ( - LocalCache.getAddressableNoteIfExists( - AdvertisedRelayListEvent.createAddressTag(it), - )?.event as? AdvertisedRelayListEvent? - )?.readRelays() - }?.toSet() + note.author + ?.pubkeyHex + ?.let { + ( + LocalCache + .getAddressableNoteIfExists( + AdvertisedRelayListEvent.createAddressTag(it), + )?.event as? AdvertisedRelayListEvent? + )?.readRelays() + }?.toSet() collectSuccessfulSigningOperations( operationsInput = zapsToSend, @@ -194,9 +203,10 @@ class ZapPaymentHandler(val account: Account) { val userRelayList = ( ( - LocalCache.getAddressableNoteIfExists( - AdvertisedRelayListEvent.createAddressTag(next.lnAddressOrPubKeyHex), - )?.event as? AdvertisedRelayListEvent? + LocalCache + .getAddressableNoteIfExists( + AdvertisedRelayListEvent.createAddressTag(next.lnAddressOrPubKeyHex), + )?.event as? AdvertisedRelayListEvent? )?.readRelays()?.toSet() ?: emptySet() ) + (authorRelayList ?: emptySet()) @@ -272,8 +282,9 @@ class ZapPaymentHandler(val account: Account) { progressAllPayments += 0.5f / invoices.size onProgress(progressAllPayments) onError( - context.getString(R.string.error_dialog_pay_invoice_error), - context.getString( + stringRes(context, R.string.error_dialog_pay_invoice_error), + stringRes( + context, R.string.wallet_connect_pay_invoice_error_error, response.error?.message ?: response.error?.code?.toString() ?: "Error parsing error message", @@ -339,10 +350,12 @@ class ZapPaymentHandler(val account: Account) { } else { if (showErrorIfNoLnAddress) { onError( - context.getString( + stringRes( + context, R.string.missing_lud16, ), - context.getString( + stringRes( + context, R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, user?.toBestDisplayName() ?: splitSetup.lnAddressOrPubKeyHex, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index feaaa76b19..85b19ebce8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.HttpClientManager import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.encoders.LnInvoiceUtil import com.vitorpamplona.quartz.encoders.Lud06 import okhttp3.Request @@ -34,7 +35,7 @@ import java.math.RoundingMode import java.net.URLEncoder import kotlin.coroutines.cancellation.CancellationException -class LightningAddressResolver() { +class LightningAddressResolver { val client = HttpClientManager.getHttpClient() fun assembleUrl(lnaddress: String): String? { @@ -63,8 +64,9 @@ class LightningAddressResolver() { if (url == null) { onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string.could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup, lnaddress, ), @@ -74,7 +76,8 @@ class LightningAddressResolver() { try { val request: Request = - Request.Builder() + Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(url) .build() @@ -84,8 +87,9 @@ class LightningAddressResolver() { onSuccess(it.body.string()) } else { onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string .the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct, url, @@ -99,8 +103,9 @@ class LightningAddressResolver() { if (e is CancellationException) throw e e.printStackTrace() onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string .could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception, url, @@ -133,7 +138,8 @@ class LightningAddressResolver() { } val request: Request = - Request.Builder() + Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(url) .build() @@ -143,8 +149,8 @@ class LightningAddressResolver() { onSuccess(it.body.string()) } else { onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString(R.string.could_not_fetch_invoice_from, lnCallback), + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes(context, R.string.could_not_fetch_invoice_from, lnCallback), ) } } @@ -173,8 +179,9 @@ class LightningAddressResolver() { } catch (t: Throwable) { if (t is CancellationException) throw t onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string.error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user, lnaddress, ), @@ -186,8 +193,9 @@ class LightningAddressResolver() { if (callback == null) { onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string.callback_url_not_found_in_the_user_s_lightning_address_server_configuration_with_user, lnaddress, ), @@ -211,8 +219,9 @@ class LightningAddressResolver() { } catch (t: Throwable) { if (t is CancellationException) throw t onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string .error_parsing_json_from_lightning_address_s_invoice_fetch_check_the_user_s_lightning_setup_with_user, lnaddress, @@ -236,8 +245,9 @@ class LightningAddressResolver() { } else { onProgress(0.0f) onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string.incorrect_invoice_amount_sats_from_it_should_have_been, invoiceAmount.toLong().toString(), lnaddress, @@ -253,8 +263,9 @@ class LightningAddressResolver() { ?.let { reason -> onProgress(0.0f) onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string .unable_to_create_a_lightning_invoice_before_sending_the_zap_the_receiver_s_lightning_wallet_sent_the_following_error_with_user, lnaddress, @@ -265,8 +276,9 @@ class LightningAddressResolver() { ?: run { onProgress(0.0f) onError( - context.getString(R.string.error_unable_to_fetch_invoice), - context.getString( + stringRes(context, R.string.error_unable_to_fetch_invoice), + stringRes( + context, R.string .unable_to_create_a_lightning_invoice_before_sending_the_zap_element_pr_not_found_in_the_resulting_json_with_user, lnaddress, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index ee0bfd54de..4ff65b4742 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification import com.vitorpamplona.amethyst.ui.note.showAmount +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.events.ChatMessageEvent import com.vitorpamplona.quartz.events.ChatroomKey @@ -44,7 +45,9 @@ import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.collections.immutable.persistentSetOf import java.math.BigDecimal -class EventNotificationConsumer(private val applicationContext: Context) { +class EventNotificationConsumer( + private val applicationContext: Context, +) { suspend fun consume(event: GiftWrapEvent) { Log.d("EventNotificationConsumer", "New Notification Arrived") if (!LocalCache.justVerify(event)) return @@ -124,7 +127,8 @@ class EventNotificationConsumer(private val applicationContext: Context) { acc: Account, ) { if ( - event.createdAt > TimeUtils.fifteenMinutesAgo() && // old event being re-broadcasted + event.createdAt > TimeUtils.fifteenMinutesAgo() && + // old event being re-broadcasted event.pubKey != acc.userProfile().pubkeyHex ) { // from the user @@ -137,7 +141,8 @@ class EventNotificationConsumer(private val applicationContext: Context) { ( acc.userProfile().privateChatrooms[chatRoom]?.senderIntersects(followingKeySet) == true || acc.userProfile().hasSentMessagesTo(chatRoom) - ) && !acc.isAllHidden(chatRoom.users) + ) && + !acc.isAllHidden(chatRoom.users) if (isKnownRoom) { val content = chatNote.event?.content() ?: "" @@ -178,9 +183,9 @@ class EventNotificationConsumer(private val applicationContext: Context) { ( acc.userProfile().privateChatrooms[it]?.senderIntersects(followingKeySet) == true || acc.userProfile().hasSentMessagesTo(it) - ) && !acc.isAllHidden(it.users) - } - .toSet() + ) && + !acc.isAllHidden(it.users) + }.toSet() note.author?.let { if (ChatroomKey(persistentSetOf(it.pubkeyHex)) in knownChatrooms) { @@ -223,17 +228,19 @@ class EventNotificationConsumer(private val applicationContext: Context) { val user = senderInfo.first.toBestDisplayName() var title = - applicationContext.getString(R.string.app_notification_zaps_channel_message, amount) + stringRes(applicationContext, R.string.app_notification_zaps_channel_message, amount) senderInfo.second?.ifBlank { null }?.let { title += " ($it)" } var content = - applicationContext.getString( + stringRes( + applicationContext, R.string.app_notification_zaps_channel_message_from, user, ) zappedContent?.let { content += " " + - applicationContext.getString( + stringRes( + applicationContext, R.string.app_notification_zaps_channel_message_for, zappedContent, ) @@ -255,8 +262,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { } } - fun notificationManager(): NotificationManager { - return ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) + fun notificationManager(): NotificationManager = + ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager - } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt index 834c040529..4bc761c5de 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt @@ -34,6 +34,7 @@ import coil.executeBlocking import coil.request.ImageRequest import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.MainActivity +import com.vitorpamplona.amethyst.ui.stringRes object NotificationUtils { private var dmChannel: NotificationChannel? = null @@ -46,14 +47,13 @@ object NotificationUtils { dmChannel = NotificationChannel( - applicationContext.getString(R.string.app_notification_dms_channel_id), - applicationContext.getString(R.string.app_notification_dms_channel_name), + stringRes(applicationContext, R.string.app_notification_dms_channel_id), + stringRes(applicationContext, R.string.app_notification_dms_channel_name), NotificationManager.IMPORTANCE_DEFAULT, - ) - .apply { - description = - applicationContext.getString(R.string.app_notification_dms_channel_description) - } + ).apply { + description = + stringRes(applicationContext, R.string.app_notification_dms_channel_description) + } // Register the channel with the system val notificationManager: NotificationManager = @@ -69,14 +69,13 @@ object NotificationUtils { zapChannel = NotificationChannel( - applicationContext.getString(R.string.app_notification_zaps_channel_id), - applicationContext.getString(R.string.app_notification_zaps_channel_name), + stringRes(applicationContext, R.string.app_notification_zaps_channel_id), + stringRes(applicationContext, R.string.app_notification_zaps_channel_name), NotificationManager.IMPORTANCE_DEFAULT, - ) - .apply { - description = - applicationContext.getString(R.string.app_notification_zaps_channel_description) - } + ).apply { + description = + stringRes(applicationContext, R.string.app_notification_zaps_channel_description) + } // Register the channel with the system val notificationManager: NotificationManager = @@ -96,7 +95,7 @@ object NotificationUtils { applicationContext: Context, ) { val zapChannel = getOrCreateZapChannel(applicationContext) - val channelId = applicationContext.getString(R.string.app_notification_zaps_channel_id) + val channelId = stringRes(applicationContext, R.string.app_notification_zaps_channel_id) sendNotification( id, @@ -119,7 +118,7 @@ object NotificationUtils { applicationContext: Context, ) { val dmChannel = getOrCreateDMChannel(applicationContext) - val channelId = applicationContext.getString(R.string.app_notification_dms_channel_id) + val channelId = stringRes(applicationContext, R.string.app_notification_dms_channel_id) sendNotification( id, @@ -205,13 +204,13 @@ object NotificationUtils { // Build the notification val builderPublic = - NotificationCompat.Builder( - applicationContext, - channelId, - ) - .setSmallIcon(R.drawable.amethyst) + NotificationCompat + .Builder( + applicationContext, + channelId, + ).setSmallIcon(R.drawable.amethyst) .setContentTitle(messageTitle) - .setContentText(applicationContext.getString(R.string.app_notification_private_message)) + .setContentText(stringRes(applicationContext, R.string.app_notification_private_message)) .setLargeIcon(picture?.bitmap) // .setGroup(messageTitle) // .setGroup(notificationGroupKey) //-> Might need a Group summary as well before we @@ -222,11 +221,11 @@ object NotificationUtils { // Build the notification val builder = - NotificationCompat.Builder( - applicationContext, - channelId, - ) - .setSmallIcon(R.drawable.amethyst) + NotificationCompat + .Builder( + applicationContext, + channelId, + ).setSmallIcon(R.drawable.amethyst) .setContentTitle(messageTitle) .setContentText(messageBody) .setLargeIcon(picture?.bitmap) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt index f14d5048a5..91db7a2b49 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt @@ -30,14 +30,15 @@ import com.vitorpamplona.quartz.ots.exceptions.CommitmentNotFoundException import com.vitorpamplona.quartz.ots.exceptions.DeserializationException import com.vitorpamplona.quartz.ots.exceptions.ExceededSizeException import com.vitorpamplona.quartz.ots.exceptions.UrlException -import com.vitorpamplona.quartz.ots.http.Request import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody /** * Class representing remote calendar server interface. */ -class OkHttpCalendar(val url: String) : ICalendar { +class OkHttpCalendar( + val url: String, +) : ICalendar { /** * Submitting a digest to remote calendar. Returns a com.eternitywall.ots.Timestamp committing to that digest. * @@ -57,7 +58,8 @@ class OkHttpCalendar(val url: String) : ICalendar { val requestBody = digest.toRequestBody(mediaType) val request = - okhttp3.Request.Builder() + okhttp3.Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .header("Accept", "application/vnd.opentimestamps.v1") .header("Content-Type", "application/x-www-form-urlencoded") @@ -104,7 +106,8 @@ class OkHttpCalendar(val url: String) : ICalendar { val url = url + "/timestamp/" + Hex.encode(commitment) val request = - okhttp3.Request.Builder() + okhttp3.Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .header("Accept", "application/vnd.opentimestamps.v1") .header("Content-Type", "application/x-www-form-urlencoded") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 8bb9f95f66..ab7108d842 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -87,9 +87,7 @@ class Relay( listeners = listeners.minus(listener) } - fun isConnected(): Boolean { - return socket != null - } + fun isConnected(): Boolean = socket != null fun connect() { connectAndRun { @@ -123,7 +121,8 @@ class Relay( lastConnectTentative = TimeUtils.now() val request = - Request.Builder() + Request + .Builder() .header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}") .url(url.trim()) .build() @@ -141,7 +140,9 @@ class Relay( } } - inner class RelayListener(val onConnected: (Relay) -> Unit) : WebSocketListener() { + inner class RelayListener( + val onConnected: (Relay) -> Unit, + ) : WebSocketListener() { override fun onOpen( webSocket: WebSocket, response: Response, @@ -524,12 +525,11 @@ class Relay( writeToSocket("""["CLOSE","$subscriptionId"]""") } - fun isSameRelayConfig(other: RelaySetupInfo): Boolean { - return url == other.url && + fun isSameRelayConfig(other: RelaySetupInfo): Boolean = + url == other.url && write == other.write && read == other.read && activeTypes == other.feedTypes - } enum class StateType { // Websocket connected diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 4f054fa6a0..ba1ead032e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -90,6 +90,11 @@ class MainActivity : AppCompatActivity() { override fun onResume() { super.onResume() + val locales = this.applicationContext.resources.configuration.locales + if (!locales.isEmpty) { + checkLanguage(locales.get(0).language) + } + Log.d("Lifetime Event", "MainActivity.onResume") // starts muted every time @@ -263,8 +268,8 @@ class GetMediaActivityResultContract : ActivityResultContracts.GetContent() { } } -fun uriToRoute(uri: String?): String? { - return if (uri.equals("nostr:Notifications", true)) { +fun uriToRoute(uri: String?): String? = + if (uri.equals("nostr:Notifications", true)) { Route.Notification.route.replace("{scrollToTop}", "true") } else { if (uri?.startsWith("nostr:Hashtag?id=") == true) { @@ -317,4 +322,3 @@ fun uriToRoute(uri: String?): String? { null } } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/StringResourceCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/StringResourceCache.kt new file mode 100644 index 0000000000..2caf1d52b2 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/StringResourceCache.kt @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui + +import android.content.Context +import android.util.LruCache +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource + +/** + * Cache for stringResource because it seems to be > 1ms function in some phones + */ +private val resourceCache = LruCache(300) +private var resourceCacheLanguage: String? = null + +fun checkLanguage(currentLanguage: String) { + if (resourceCacheLanguage == null) { + resourceCacheLanguage = currentLanguage + } else { + if (resourceCacheLanguage != currentLanguage) { + resourceCacheLanguage = currentLanguage + resourceCache.evictAll() + } + } +} + +@Composable +fun stringRes(id: Int): String = resourceCache.get(id) ?: stringResource(id).also { resourceCache.put(id, it) } + +@Composable +fun stringRes( + id: Int, + vararg args: Any, +): String = + String + .format( + LocalConfiguration.current.locales.get(0), + resourceCache.get(id) ?: stringResource(id), + *args, + ).also { resourceCache.put(id, it) } + +fun stringRes( + ctx: Context, + id: Int, +): String = resourceCache.get(id) ?: ctx.getString(id).also { resourceCache.put(id, it) } + +fun stringRes( + ctx: Context, + id: Int, + vararg args: Any?, +): String { + val res = ctx.resources + + return String + .format( + res.configuration.locales.get(0), + resourceCache.get(id) ?: res.getString(id), + *args, + ).also { resourceCache.put(id, it) } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt new file mode 100644 index 0000000000..c0f20510c6 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/CrossfadeIfEnabled.kt @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.actions + +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.model.FeatureSetType +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel + +@Composable +fun CrossfadeIfEnabled( + targetState: T, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = tween(), + label: String = "Crossfade", + accountViewModel: AccountViewModel, + content: @Composable (T) -> Unit, +) { + if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) { + Box(modifier) { + content(targetState) + } + } else { + Crossfade(targetState, modifier, animationSpec, label, content) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 82dacec419..62ef237caa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -78,7 +78,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextDirection @@ -98,6 +97,7 @@ import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.QuoteBorder @@ -189,7 +189,7 @@ fun EditPostView( ) { Icon( painter = painterResource(R.drawable.relays), - contentDescription = stringResource(id = R.string.relay_list_selector), + contentDescription = stringRes(id = R.string.relay_list_selector), modifier = Modifier.height(25.dp), tint = MaterialTheme.colorScheme.onBackground, ) @@ -236,8 +236,7 @@ fun EditPostView( top = pad.calculateTopPadding(), end = Size10dp, bottom = pad.calculateBottomPadding(), - ) - .fillMaxSize(), + ).fillMaxSize(), ) { Column( modifier = @@ -365,8 +364,8 @@ fun EditPostView( lud16, user.pubkeyHex, accountViewModel.account, - stringResource(id = R.string.lightning_invoice), - stringResource(id = R.string.lightning_create_and_add_invoice), + stringRes(id = R.string.lightning_invoice), + stringRes(id = R.string.lightning_create_and_add_invoice), onSuccess = { postViewModel.message = TextFieldValue(postViewModel.message.text + "\n\n" + it) @@ -386,7 +385,7 @@ fun EditPostView( ) { Column { Text( - text = stringResource(R.string.message_to_author), + text = stringRes(R.string.message_to_author), fontSize = 18.sp, fontWeight = FontWeight.W500, ) @@ -399,7 +398,7 @@ fun EditPostView( modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.message_to_author_placeholder), + text = stringRes(R.string.message_to_author_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -514,8 +513,7 @@ private fun MessageField(postViewModel: EditPostViewModel) { width = 1.dp, color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp), - ) - .focusRequester(focusRequester) + ).focusRequester(focusRequester) .onFocusChanged { if (it.isFocused) { keyboardController?.show() @@ -523,7 +521,7 @@ private fun MessageField(postViewModel: EditPostViewModel) { }, placeholder = { Text( - text = stringResource(R.string.what_s_on_your_mind), + text = stringRes(R.string.what_s_on_your_mind), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -548,14 +546,14 @@ private fun AddLnInvoiceButton( if (!isLnInvoiceActive) { Icon( imageVector = Icons.Default.CurrencyBitcoin, - contentDescription = stringResource(id = R.string.add_bitcoin_invoice), + contentDescription = stringRes(id = R.string.add_bitcoin_invoice), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onBackground, ) } else { Icon( imageVector = Icons.Default.CurrencyBitcoin, - contentDescription = stringResource(id = R.string.cancel_bitcoin_invoice), + contentDescription = stringRes(id = R.string.cancel_bitcoin_invoice), modifier = Modifier.size(20.dp), tint = BitcoinOrange, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt index f2cc82d6ce..b443c53e63 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt @@ -34,8 +34,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size16dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer @@ -64,7 +64,7 @@ fun InformationDialog( contentDescription = null, ) Spacer(StdHorzSpacer) - Text(stringResource(R.string.error_dialog_button_ok)) + Text(stringRes(R.string.error_dialog_button_ok)) } } }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 4cda055f4b..57b596c40f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -59,10 +59,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -73,6 +71,7 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource @@ -83,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.note.SearchIcon import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -161,7 +161,7 @@ fun JoinUserOrChannelView( ) Text( - text = stringResource(R.string.channel_list_join_conversation), + text = stringRes(R.string.channel_list_join_conversation), fontWeight = FontWeight.Bold, ) @@ -278,13 +278,13 @@ private fun SearchEditTextForJoin( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.channel_list_user_or_group_id)) }, + label = { Text(text = stringRes(R.string.channel_list_user_or_group_id)) }, value = searchBarViewModel.searchValue, onValueChange = { searchBarViewModel.updateSearchValue(it) scope.launch(Dispatchers.IO) { searchTextChanges.trySend(it) } }, - leadingIcon = { SearchIcon(modifier = Size20Modifier, Color.Unspecified) }, + leadingIcon = { SearchIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) }, modifier = Modifier .weight(1f, true) @@ -297,7 +297,7 @@ private fun SearchEditTextForJoin( }, placeholder = { Text( - text = stringResource(R.string.channel_list_user_or_group_id_demo), + text = stringRes(R.string.channel_list_user_or_group_id_demo), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -311,7 +311,7 @@ private fun SearchEditTextForJoin( ) { Icon( imageVector = Icons.Default.Clear, - contentDescription = stringResource(R.string.clear), + contentDescription = stringRes(R.string.clear), ) } } @@ -331,11 +331,6 @@ private fun RenderSearchResults( val users by searchBarViewModel.searchResultsUsers.collectAsStateWithLifecycle() val channels by searchBarViewModel.searchResultsChannels.collectAsStateWithLifecycle() - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - Row( modifier = Modifier @@ -367,7 +362,11 @@ private fun RenderSearchResults( channels, key = { _, item -> "c" + item.idHex }, ) { _, item -> - RenderChannel(item, automaticallyShowProfilePicture) { + RenderChannel( + item, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) { nav("Channel/${item.idHex}") searchBarViewModel.clear() } @@ -385,6 +384,7 @@ private fun RenderSearchResults( private fun RenderChannel( item: com.vitorpamplona.amethyst.model.Channel, loadProfilePicture: Boolean, + loadRobohash: Boolean, onClick: () -> Unit, ) { val hasNewMessages = remember { mutableStateOf(false) } @@ -403,6 +403,7 @@ private fun RenderChannel( hasNewMessages, onClick = onClick, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } @@ -414,14 +415,15 @@ fun UserComposeForChat( ) { Row( modifier = - Modifier.clickable( - onClick = onClick, - ).padding( - start = 12.dp, - end = 12.dp, - top = 10.dp, - bottom = 10.dp, - ), + Modifier + .clickable( + onClick = onClick, + ).padding( + start = 12.dp, + end = 12.dp, + top = 10.dp, + bottom = 10.dp, + ), verticalAlignment = Alignment.CenterVertically, ) { ClickableUserPicture(baseUser, Size55dp, accountViewModel) @@ -432,7 +434,7 @@ fun UserComposeForChat( .padding(start = 10.dp) .weight(1f), ) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) } + Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) } DisplayUserAboutInfo(baseUser) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewChannelView.kt index 15f5cf8f42..e9db99763c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewChannelView.kt @@ -38,7 +38,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp @@ -48,6 +47,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.PublicChatChannel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable @@ -87,20 +87,21 @@ fun NewChannelView( postViewModel.create() onClose() }, - postViewModel.channelName.value.text.isNotBlank(), + postViewModel.channelName.value.text + .isNotBlank(), ) } Spacer(modifier = Modifier.height(15.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.channel_name)) }, + label = { Text(text = stringRes(R.string.channel_name)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.channelName.value, onValueChange = { postViewModel.channelName.value = it }, placeholder = { Text( - text = stringResource(R.string.my_awesome_group), + text = stringRes(R.string.my_awesome_group), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -114,7 +115,7 @@ fun NewChannelView( Spacer(modifier = Modifier.height(15.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.picture_url)) }, + label = { Text(text = stringRes(R.string.picture_url)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.channelPicture.value, onValueChange = { postViewModel.channelPicture.value = it }, @@ -129,13 +130,13 @@ fun NewChannelView( Spacer(modifier = Modifier.height(15.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.description)) }, + label = { Text(text = stringRes(R.string.description)) }, modifier = Modifier.fillMaxWidth().height(100.dp), value = postViewModel.channelDescription.value, onValueChange = { postViewModel.channelDescription.value = it }, placeholder = { Text( - text = stringResource(R.string.about_us), + text = stringRes(R.string.about_us), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 16d9a02582..91c0bf1c8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.service.FileHeader import com.vitorpamplona.amethyst.service.Nip96MediaServers import com.vitorpamplona.amethyst.service.Nip96Uploader import com.vitorpamplona.amethyst.ui.components.MediaCompressor +import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -114,7 +115,7 @@ open class NewMediaModel : ViewModel() { } ?: run { viewModelScope.launch { - onError(context.getString(R.string.could_not_open_the_compressed_file)) + onError(stringRes(context, R.string.could_not_open_the_compressed_file)) isUploadingImage = false uploadingPercentage.value = 0.00f uploadingDescription.value = null @@ -153,7 +154,7 @@ open class NewMediaModel : ViewModel() { isUploadingImage = false uploadingPercentage.value = 0.00f uploadingDescription.value = null - onError(context.getString(R.string.failed_to_upload_media, e.message)) + onError(stringRes(context, R.string.failed_to_upload_media, e.message)) } } } @@ -162,7 +163,7 @@ open class NewMediaModel : ViewModel() { isUploadingImage = false uploadingPercentage.value = 0.00f uploadingDescription.value = null - onError(context.getString(R.string.error_when_compressing_media, it)) + onError(stringRes(context, R.string.error_when_compressing_media, it)) }, ) } @@ -179,9 +180,7 @@ open class NewMediaModel : ViewModel() { selectedServer = ServerOption(defaultServer(), false) } - fun canPost(): Boolean { - return !isUploadingImage && galleryUri != null && selectedServer != null - } + fun canPost(): Boolean = !isUploadingImage && galleryUri != null && selectedServer != null suspend fun createNIP94Record( uploadingResult: Nip96Uploader.PartialEvent, @@ -197,11 +196,20 @@ open class NewMediaModel : ViewModel() { val imageUrl = uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "url" }?.get(1) val remoteMimeType = - uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "m" }?.get(1)?.ifBlank { null } + uploadingResult.tags + ?.firstOrNull { it.size > 1 && it[0] == "m" } + ?.get(1) + ?.ifBlank { null } val originalHash = - uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "ox" }?.get(1)?.ifBlank { null } + uploadingResult.tags + ?.firstOrNull { it.size > 1 && it[0] == "ox" } + ?.get(1) + ?.ifBlank { null } val dim = - uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "dim" }?.get(1)?.ifBlank { null } + uploadingResult.tags + ?.firstOrNull { it.size > 1 && it[0] == "dim" } + ?.get(1) + ?.ifBlank { null } val magnet = uploadingResult.tags ?.firstOrNull { it.size > 1 && it[0] == "magnet" } @@ -214,7 +222,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.00f uploadingDescription.value = null isUploadingImage = false - onError(context.getString(R.string.server_did_not_provide_a_url_after_uploading)) + onError(stringRes(context, R.string.server_did_not_provide_a_url_after_uploading)) return } @@ -254,7 +262,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.00f uploadingDescription.value = null isUploadingImage = false - onError(context.getString(R.string.could_not_prepare_local_file_to_upload, it)) + onError(stringRes(context, R.string.could_not_prepare_local_file_to_upload, it)) }, ) } else { @@ -263,7 +271,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.00f uploadingDescription.value = null isUploadingImage = false - onError(context.getString(R.string.could_not_download_from_the_server)) + onError(stringRes(context, R.string.could_not_download_from_the_server)) } } @@ -312,7 +320,7 @@ open class NewMediaModel : ViewModel() { uploadingPercentage.value = 0.00f isUploadingImage = false cancel() - onError(context.getString(R.string.could_not_prepare_local_file_to_upload, it)) + onError(stringRes(context, R.string.could_not_prepare_local_file_to_upload, it)) }, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 0f94874f22..28df2df0f1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -58,7 +58,6 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -70,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException @@ -93,7 +93,7 @@ fun NewMediaView( LaunchedEffect(uri) { val mediaType = resolver.getType(uri) ?: "" postViewModel.load(account, uri, mediaType) { - accountViewModel.toast(context.getString(R.string.failed_to_upload_media_no_details), it) + accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) } } @@ -124,7 +124,8 @@ fun NewMediaView( Column( modifier = - Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp) + Modifier + .padding(start = 10.dp, end = 10.dp, top = 10.dp) .fillMaxWidth() .fillMaxHeight() .imePadding(), @@ -194,7 +195,7 @@ fun ImageVideoPost( ServerOption( Nip96MediaServers.ServerName( "NIP95", - stringResource(id = R.string.upload_server_relays_nip95), + stringRes(id = R.string.upload_server_relays_nip95), ), true, ), @@ -209,7 +210,8 @@ fun ImageVideoPost( Row( verticalAlignment = Alignment.CenterVertically, modifier = - Modifier.fillMaxWidth() + Modifier + .fillMaxWidth() .padding(bottom = 10.dp) .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), ) { @@ -219,7 +221,8 @@ fun ImageVideoPost( contentDescription = postViewModel.galleryUri.toString(), contentScale = ContentScale.FillWidth, modifier = - Modifier.padding(top = 4.dp) + Modifier + .padding(top = 4.dp) .fillMaxWidth() .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), ) @@ -265,7 +268,7 @@ fun ImageVideoPost( modifier = Modifier.fillMaxWidth(), ) { TextSpinner( - label = stringResource(id = R.string.file_server), + label = stringRes(id = R.string.file_server), placeholder = fileServers .firstOrNull { it.server == accountViewModel.account.defaultFileServer } @@ -296,13 +299,13 @@ fun ImageVideoPost( modifier = Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.content_description)) }, + label = { Text(text = stringRes(R.string.content_description)) }, modifier = Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), value = postViewModel.alt, onValueChange = { postViewModel.alt = it }, placeholder = { Text( - text = stringResource(R.string.content_description_example), + text = stringRes(R.string.content_description_example), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollClosing.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollClosing.kt index b17ade6d8d..8c8a61c203 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollClosing.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollClosing.kt @@ -36,12 +36,12 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.CancellationException @@ -87,13 +87,13 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) { colors = if (pollViewModel.isValidClosedAt.value) colorValid else colorInValid, label = { Text( - text = stringResource(R.string.poll_closing_time), + text = stringRes(R.string.poll_closing_time), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.poll_closing_time_days), + text = stringRes(R.string.poll_closing_time_days), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollConsensusThreshold.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollConsensusThreshold.kt index d181c873dc..277b89d761 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollConsensusThreshold.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollConsensusThreshold.kt @@ -36,12 +36,12 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.CancellationException @@ -87,13 +87,13 @@ fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) { colors = if (pollViewModel.isValidConsensusThreshold.value) colorValid else colorInValid, label = { Text( - text = stringResource(R.string.poll_consensus_threshold), + text = stringRes(R.string.poll_consensus_threshold), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.poll_consensus_threshold_percent), + text = stringRes(R.string.poll_consensus_threshold_percent), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollOption.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollOption.kt index 086035c0a5..a630d31575 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollOption.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollOption.kt @@ -31,10 +31,10 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable @@ -51,7 +51,7 @@ fun NewPollOption( ) { Icon( imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.clear), + contentDescription = stringRes(R.string.clear), ) } } @@ -64,13 +64,13 @@ fun NewPollOption( }, label = { Text( - text = stringResource(R.string.poll_option_index).format(optionIndex + 1), + text = stringRes(R.string.poll_option_index).format(optionIndex + 1), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.poll_option_description), + text = stringRes(R.string.poll_option_description), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollPrimaryDescription.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollPrimaryDescription.kt index 4c355c6b2d..c368dde526 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollPrimaryDescription.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollPrimaryDescription.kt @@ -37,13 +37,13 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText @OptIn(ExperimentalComposeUiApi::class) @@ -74,7 +74,7 @@ fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) { onValueChange = { pollViewModel.updateMessage(it) }, label = { Text( - text = stringResource(R.string.poll_primary_description), + text = stringRes(R.string.poll_primary_description), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -90,7 +90,7 @@ fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) { }, placeholder = { Text( - text = stringResource(R.string.poll_primary_description), + text = stringRes(R.string.poll_primary_description), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollRecipientsField.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollRecipientsField.kt index 10cb12bc36..e575252319 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollRecipientsField.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollRecipientsField.kt @@ -26,10 +26,10 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable @@ -51,13 +51,13 @@ fun NewPollRecipientsField( enabled = false, label = { Text( - text = stringResource(R.string.poll_zap_recipients), + text = stringRes(R.string.poll_zap_recipients), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.poll_zap_recipients), + text = stringRes(R.string.poll_zap_recipients), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt index 806eee50bd..a49b488daf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt @@ -34,11 +34,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -67,13 +67,13 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) { colors = if (pollViewModel.isValidvalueMinimum.value) colorValid else colorInValid, label = { Text( - text = stringResource(R.string.poll_zap_value_min), + text = stringRes(R.string.poll_zap_value_min), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.sats), + text = stringRes(R.string.sats), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -89,13 +89,13 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) { colors = if (pollViewModel.isValidvalueMaximum.value) colorValid else colorInValid, label = { Text( - text = stringResource(R.string.poll_zap_value_max), + text = stringRes(R.string.poll_zap_value_max), color = MaterialTheme.colorScheme.placeholderText, ) }, placeholder = { Text( - text = stringResource(R.string.sats), + text = stringRes(R.string.sats), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -107,7 +107,7 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) { horizontalArrangement = Arrangement.Center, ) { Text( - text = stringResource(R.string.poll_zap_value_min_max_explainer), + text = stringRes(R.string.poll_zap_value_min_max_explainer), color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 0cfe1e6e3d..c6a2b83b22 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -105,7 +105,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization @@ -150,6 +149,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserSuggestionList import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -276,7 +276,7 @@ fun NewPostView( ) { Icon( painter = painterResource(R.drawable.relays), - contentDescription = stringResource(id = R.string.relay_list_selector), + contentDescription = stringRes(id = R.string.relay_list_selector), modifier = Modifier.height(25.dp), tint = MaterialTheme.colorScheme.onBackground, ) @@ -324,8 +324,7 @@ fun NewPostView( top = pad.calculateTopPadding(), end = Size10dp, bottom = pad.calculateBottomPadding(), - ) - .fillMaxSize(), + ).fillMaxSize(), ) { Column( modifier = @@ -511,8 +510,8 @@ fun NewPostView( lud16, accountViewModel.account.userProfile().pubkeyHex, accountViewModel.account, - stringResource(id = R.string.lightning_invoice), - stringResource(id = R.string.lightning_create_and_add_invoice), + stringRes(id = R.string.lightning_invoice), + stringRes(id = R.string.lightning_create_and_add_invoice), onSuccess = { postViewModel.insertAtCursor(it) postViewModel.wantsInvoice = false @@ -531,7 +530,7 @@ fun NewPostView( modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), ) { ZapRaiserRequest( - stringResource(id = R.string.zapraiser), + stringRes(id = R.string.zapraiser), postViewModel, ) } @@ -575,7 +574,7 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) { if (postViewModel.canUsePoll) { // These should be hashtag recommendations the user selects in the future. - // val hashtag = stringResource(R.string.poll_hashtag) + // val hashtag = stringRes(R.string.poll_hashtag) // postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag) AddPollButton(postViewModel.wantsPoll) { postViewModel.wantsPoll = !postViewModel.wantsPoll @@ -676,8 +675,7 @@ private fun MessageField(postViewModel: NewPostViewModel) { width = 1.dp, color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp), - ) - .focusRequester(focusRequester) + ).focusRequester(focusRequester) .onFocusChanged { if (it.isFocused) { keyboardController?.show() @@ -687,9 +685,9 @@ private fun MessageField(postViewModel: NewPostViewModel) { Text( text = if (postViewModel.wantsProduct) { - stringResource(R.string.description) + stringRes(R.string.description) } else { - stringResource(R.string.what_s_on_your_mind) + stringRes(R.string.what_s_on_your_mind) }, color = MaterialTheme.colorScheme.placeholderText, ) @@ -723,7 +721,7 @@ fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) { ) { Icon( imageVector = Icons.Default.VisibilityOff, - contentDescription = stringResource(id = R.string.content_warning), + contentDescription = stringRes(id = R.string.content_warning), modifier = Modifier .size(18.dp) @@ -732,7 +730,7 @@ fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) { ) Icon( imageVector = Icons.Rounded.Warning, - contentDescription = stringResource(id = R.string.content_warning), + contentDescription = stringRes(id = R.string.content_warning), modifier = Modifier .size(10.dp) @@ -742,7 +740,7 @@ fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) { } Text( - text = stringResource(R.string.add_sensitive_content_label), + text = stringRes(R.string.add_sensitive_content_label), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -752,7 +750,7 @@ fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) { HorizontalDivider(thickness = DividerThickness) Text( - text = stringResource(R.string.add_sensitive_content_explainer), + text = stringRes(R.string.add_sensitive_content_explainer), color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) @@ -769,7 +767,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.messages_new_message_to), + text = stringRes(R.string.messages_new_message_to), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -780,7 +778,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.messages_new_message_to_caption), + text = stringRes(R.string.messages_new_message_to_caption), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -803,7 +801,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.messages_new_message_subject), + text = stringRes(R.string.messages_new_message_subject), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -814,7 +812,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.messages_new_message_subject_caption), + text = stringRes(R.string.messages_new_message_subject_caption), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -844,7 +842,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.classifieds_title), + text = stringRes(R.string.classifieds_title), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -857,7 +855,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.classifieds_title_placeholder), + text = stringRes(R.string.classifieds_title_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -880,7 +878,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.classifieds_price), + text = stringRes(R.string.classifieds_price), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -917,7 +915,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.classifieds_condition), + text = stringRes(R.string.classifieds_condition), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -926,23 +924,23 @@ fun SellProduct(postViewModel: NewPostViewModel) { listOf( Triple( ClassifiedsEvent.CONDITION.NEW, - stringResource(id = R.string.classifieds_condition_new), - stringResource(id = R.string.classifieds_condition_new_explainer), + stringRes(id = R.string.classifieds_condition_new), + stringRes(id = R.string.classifieds_condition_new_explainer), ), Triple( ClassifiedsEvent.CONDITION.USED_LIKE_NEW, - stringResource(id = R.string.classifieds_condition_like_new), - stringResource(id = R.string.classifieds_condition_like_new_explainer), + stringRes(id = R.string.classifieds_condition_like_new), + stringRes(id = R.string.classifieds_condition_like_new_explainer), ), Triple( ClassifiedsEvent.CONDITION.USED_GOOD, - stringResource(id = R.string.classifieds_condition_good), - stringResource(id = R.string.classifieds_condition_good_explainer), + stringRes(id = R.string.classifieds_condition_good), + stringRes(id = R.string.classifieds_condition_good_explainer), ), Triple( ClassifiedsEvent.CONDITION.USED_FAIR, - stringResource(id = R.string.classifieds_condition_fair), - stringResource(id = R.string.classifieds_condition_fair_explainer), + stringRes(id = R.string.classifieds_condition_fair), + stringRes(id = R.string.classifieds_condition_fair_explainer), ), ) @@ -984,7 +982,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.classifieds_category), + text = stringRes(R.string.classifieds_category), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -1009,7 +1007,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { R.string.classifieds_category_other, ) - val categoryTypes = categoryList.map { Triple(it, stringResource(id = it), null) } + val categoryTypes = categoryList.map { Triple(it, stringRes(id = it), null) } val categoryOptions = remember { @@ -1050,7 +1048,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.classifieds_location), + text = stringRes(R.string.classifieds_location), fontSize = Font14SP, fontWeight = FontWeight.W500, ) @@ -1063,7 +1061,7 @@ fun SellProduct(postViewModel: NewPostViewModel) { modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.classifieds_location_placeholder), + text = stringRes(R.string.classifieds_location_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -1101,21 +1099,21 @@ fun FowardZapTo( ZapSplitIcon() Text( - text = stringResource(R.string.zap_split_title), + text = stringRes(R.string.zap_split_title), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(horizontal = 10.dp).weight(1f), ) OutlinedButton(onClick = { postViewModel.updateZapFromText() }) { - Text(text = stringResource(R.string.load_from_text)) + Text(text = stringRes(R.string.load_from_text)) } } HorizontalDivider(thickness = DividerThickness) Text( - text = stringResource(R.string.zap_split_explainer), + text = stringRes(R.string.zap_split_explainer), color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) @@ -1130,7 +1128,7 @@ fun FowardZapTo( Spacer(modifier = DoubleHorzSpacer) Column(modifier = Modifier.weight(1f)) { - UsernameDisplay(splitItem.key) + UsernameDisplay(splitItem.key, accountViewModel = accountViewModel) Text( text = String.format("%.0f%%", splitItem.percentage * 100), maxLines = 1, @@ -1156,11 +1154,11 @@ fun FowardZapTo( OutlinedTextField( value = postViewModel.forwardZapToEditting, onValueChange = { postViewModel.updateZapForwardTo(it) }, - label = { Text(text = stringResource(R.string.zap_split_search_and_add_user)) }, + label = { Text(text = stringRes(R.string.zap_split_search_and_add_user)) }, modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.zap_split_search_and_add_user_placeholder), + text = stringRes(R.string.zap_split_search_and_add_user_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -1207,7 +1205,7 @@ fun LocationAsHash(postViewModel: NewPostViewModel) { } Text( - text = stringResource(R.string.geohash_title), + text = stringRes(R.string.geohash_title), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -1219,7 +1217,7 @@ fun LocationAsHash(postViewModel: NewPostViewModel) { HorizontalDivider(thickness = DividerThickness) Text( - text = stringResource(R.string.geohash_explainer), + text = stringRes(R.string.geohash_explainer), color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) @@ -1277,7 +1275,7 @@ fun Notifying( FlowRow(horizontalArrangement = Arrangement.spacedBy(5.dp)) { if (!mentions.isNullOrEmpty()) { Text( - stringResource(R.string.reply_notify), + stringRes(R.string.reply_notify), fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.align(CenterVertically), @@ -1350,7 +1348,7 @@ private fun AddZapraiserButton( ) Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(R.string.add_zapraiser), + contentDescription = stringRes(R.string.add_zapraiser), modifier = Modifier .size(13.dp) @@ -1369,7 +1367,7 @@ private fun AddZapraiserButton( ) Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(R.string.cancel_zapraiser), + contentDescription = stringRes(R.string.cancel_zapraiser), modifier = Modifier .size(13.dp) @@ -1392,14 +1390,14 @@ fun AddGeoHash( if (!postViewModel.wantsToAddGeoHash) { Icon( imageVector = Icons.Default.LocationOff, - contentDescription = stringResource(id = R.string.add_location), + contentDescription = stringRes(id = R.string.add_location), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onBackground, ) } else { Icon( imageVector = Icons.Default.LocationOn, - contentDescription = stringResource(id = R.string.remove_location), + contentDescription = stringRes(id = R.string.remove_location), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -1418,14 +1416,14 @@ private fun AddLnInvoiceButton( if (!isLnInvoiceActive) { Icon( imageVector = Icons.Default.CurrencyBitcoin, - contentDescription = stringResource(id = R.string.add_bitcoin_invoice), + contentDescription = stringRes(id = R.string.add_bitcoin_invoice), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onBackground, ) } else { Icon( imageVector = Icons.Default.CurrencyBitcoin, - contentDescription = stringResource(id = R.string.cancel_bitcoin_invoice), + contentDescription = stringRes(id = R.string.cancel_bitcoin_invoice), modifier = Modifier.size(20.dp), tint = BitcoinOrange, ) @@ -1460,14 +1458,14 @@ private fun AddClassifiedsButton( if (!postViewModel.wantsProduct) { Icon( imageVector = Icons.Default.Sell, - contentDescription = stringResource(R.string.classifieds), + contentDescription = stringRes(R.string.classifieds), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onBackground, ) } else { Icon( imageVector = Icons.Default.Sell, - contentDescription = stringResource(id = R.string.cancel_classifieds), + contentDescription = stringRes(id = R.string.cancel_classifieds), modifier = Modifier.size(20.dp), tint = BitcoinOrange, ) @@ -1491,7 +1489,7 @@ private fun MarkAsSensitive( if (!postViewModel.wantsToMarkAsSensitive) { Icon( imageVector = Icons.Default.Visibility, - contentDescription = stringResource(R.string.add_content_warning), + contentDescription = stringRes(R.string.add_content_warning), modifier = Modifier .size(18.dp) @@ -1500,7 +1498,7 @@ private fun MarkAsSensitive( ) Icon( imageVector = Icons.Rounded.Warning, - contentDescription = stringResource(R.string.add_content_warning), + contentDescription = stringRes(R.string.add_content_warning), modifier = Modifier .size(10.dp) @@ -1510,7 +1508,7 @@ private fun MarkAsSensitive( } else { Icon( imageVector = Icons.Default.VisibilityOff, - contentDescription = stringResource(id = R.string.remove_content_warning), + contentDescription = stringRes(id = R.string.remove_content_warning), modifier = Modifier .size(18.dp) @@ -1519,7 +1517,7 @@ private fun MarkAsSensitive( ) Icon( imageVector = Icons.Rounded.Warning, - contentDescription = stringResource(id = R.string.remove_content_warning), + contentDescription = stringRes(id = R.string.remove_content_warning), modifier = Modifier .size(10.dp) @@ -1552,7 +1550,7 @@ fun PostButton( enabled = isActive, onClick = onPost, ) { - Text(text = stringResource(R.string.post)) + Text(text = stringRes(R.string.post)) } } @@ -1567,7 +1565,7 @@ fun SaveButton( modifier = modifier, onClick = onPost, ) { - Text(text = stringResource(R.string.save)) + Text(text = stringRes(R.string.save)) } } @@ -1590,7 +1588,7 @@ fun CreateButton( containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray, ), ) { - Text(text = stringResource(R.string.create), color = Color.White) + Text(text = stringRes(R.string.create), color = Color.White) } } @@ -1615,7 +1613,7 @@ fun ImageVideoDescription( ServerOption( Nip96MediaServers.ServerName( "NIP95", - stringResource(id = R.string.upload_server_relays_nip95), + stringRes(id = R.string.upload_server_relays_nip95), ), true, ), @@ -1657,7 +1655,7 @@ fun ImageVideoDescription( ) { Text( text = - stringResource( + stringRes( if (isImage) { R.string.content_description_add_image } else { @@ -1747,7 +1745,7 @@ fun ImageVideoDescription( modifier = Modifier.fillMaxWidth(), ) { TextSpinner( - label = stringResource(id = R.string.file_server), + label = stringRes(id = R.string.file_server), placeholder = fileServers .firstOrNull { it.server == accountViewModel.account.defaultFileServer } @@ -1787,7 +1785,7 @@ fun ImageVideoDescription( .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.content_description)) }, + label = { Text(text = stringRes(R.string.content_description)) }, modifier = Modifier .fillMaxWidth() @@ -1796,7 +1794,7 @@ fun ImageVideoDescription( onValueChange = { message = it }, placeholder = { Text( - text = stringResource(R.string.content_description_example), + text = stringRes(R.string.content_description_example), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -1819,7 +1817,7 @@ fun ImageVideoDescription( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(text = stringResource(R.string.add_content), color = Color.White, fontSize = 20.sp) + Text(text = stringRes(R.string.add_content), color = Color.White, fontSize = 20.sp) } } } @@ -1853,12 +1851,12 @@ fun SettingSwitchItem( verticalArrangement = Arrangement.spacedBy(Size5dp), ) { Text( - text = stringResource(id = title), + text = stringRes(id = title), maxLines = 1, overflow = TextOverflow.Ellipsis, ) Text( - text = stringResource(id = description), + text = stringRes(id = description), style = MaterialTheme.typography.bodySmall, color = Color.Gray, maxLines = 2, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt index dd0446d20e..e4dbc5a8f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -48,6 +47,7 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -108,13 +108,13 @@ fun NewUserMetadataView( modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState()), ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.display_name)) }, + label = { Text(text = stringRes(R.string.display_name)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.displayName.value, onValueChange = { postViewModel.displayName.value = it }, placeholder = { Text( - text = stringResource(R.string.my_display_name), + text = stringRes(R.string.my_display_name), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -128,13 +128,13 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.about_me)) }, + label = { Text(text = stringRes(R.string.about_me)) }, modifier = Modifier.fillMaxWidth().height(100.dp), value = postViewModel.about.value, onValueChange = { postViewModel.about.value = it }, placeholder = { Text( - text = stringResource(id = R.string.about_me), + text = stringRes(id = R.string.about_me), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -148,7 +148,7 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.avatar_url)) }, + label = { Text(text = stringRes(R.string.avatar_url)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.picture.value, onValueChange = { postViewModel.picture.value = it }, @@ -173,7 +173,7 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.banner_url)) }, + label = { Text(text = stringRes(R.string.banner_url)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.banner.value, onValueChange = { postViewModel.banner.value = it }, @@ -198,7 +198,7 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.website_url)) }, + label = { Text(text = stringRes(R.string.website_url)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.website.value, onValueChange = { postViewModel.website.value = it }, @@ -214,7 +214,7 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.nip_05)) }, + label = { Text(text = stringRes(R.string.nip_05)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.nip05.value, onValueChange = { postViewModel.nip05.value = it }, @@ -229,7 +229,7 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.ln_address)) }, + label = { Text(text = stringRes(R.string.ln_address)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.lnAddress.value, onValueChange = { postViewModel.lnAddress.value = it }, @@ -245,13 +245,13 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.ln_url_outdated)) }, + label = { Text(text = stringRes(R.string.ln_url_outdated)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.lnURL.value, onValueChange = { postViewModel.lnURL.value = it }, placeholder = { Text( - text = stringResource(R.string.lnurl), + text = stringRes(R.string.lnurl), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -260,13 +260,13 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.twitter)) }, + label = { Text(text = stringRes(R.string.twitter)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.twitter.value, onValueChange = { postViewModel.twitter.value = it }, placeholder = { Text( - text = stringResource(R.string.twitter_proof_url_template), + text = stringRes(R.string.twitter_proof_url_template), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -275,13 +275,13 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.mastodon)) }, + label = { Text(text = stringRes(R.string.mastodon)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.mastodon.value, onValueChange = { postViewModel.mastodon.value = it }, placeholder = { Text( - text = stringResource(R.string.mastodon_proof_url_template), + text = stringRes(R.string.mastodon_proof_url_template), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -290,13 +290,13 @@ fun NewUserMetadataView( Spacer(modifier = Modifier.height(10.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.github)) }, + label = { Text(text = stringRes(R.string.github)) }, modifier = Modifier.fillMaxWidth(), value = postViewModel.github.value, onValueChange = { postViewModel.github.value = it }, placeholder = { Text( - text = stringResource(R.string.github_proof_url_template), + text = stringRes(R.string.github_proof_url_template), color = MaterialTheme.colorScheme.placeholderText, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NotifyRequestDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NotifyRequestDialog.kt index 4c80efb0b4..806e20a060 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NotifyRequestDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NotifyRequestDialog.kt @@ -38,10 +38,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size16dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.quartz.events.EmptyTagList @@ -89,7 +89,7 @@ fun NotifyRequestDialog( contentDescription = null, ) Spacer(StdHorzSpacer) - Text(stringResource(R.string.error_dialog_button_ok)) + Text(stringRes(R.string.error_dialog_button_ok)) } } }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index 9addb828c1..fceca37605 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.model.RelaySetupInfo import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.quartz.encoders.Nip11RelayInformation import kotlinx.collections.immutable.ImmutableList @@ -141,7 +142,7 @@ fun RelaySelectionDialog( } RelaySwitch( - text = context.getString(R.string.select_deselect_all), + text = stringRes(context, R.string.select_deselect_all), checked = selected, onClick = { selected = !selected @@ -185,25 +186,29 @@ fun RelaySelectionDialog( val msg = when (errorCode) { Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, @@ -211,7 +216,7 @@ fun RelaySelectionDialog( } accountViewModel.toast( - context.getString(R.string.unable_to_download_relay_document), + stringRes(context, R.string.unable_to_download_relay_document), msg, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SaveToGallery.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SaveToGallery.kt index 10e8ec0dbe..7c6ffb30de 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SaveToGallery.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/SaveToGallery.kt @@ -28,11 +28,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import kotlinx.coroutines.launch import java.io.File @@ -54,22 +54,22 @@ fun SaveToGallery(url: String) { url = url, onSuccess = { scope.launch { - Toast.makeText( - localContext, - localContext.getString(R.string.image_saved_to_the_gallery), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + localContext, + stringRes(localContext, R.string.image_saved_to_the_gallery), + Toast.LENGTH_SHORT, + ).show() } }, onError = { scope.launch { - Toast.makeText( - localContext, - localContext.getString(R.string.failed_to_save_the_image), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + localContext, + stringRes(localContext, R.string.failed_to_save_the_image), + Toast.LENGTH_SHORT, + ).show() } }, ) @@ -96,7 +96,7 @@ fun SaveToGallery(url: String) { } }, ) { - Text(text = stringResource(id = R.string.save)) + Text(text = stringRes(id = R.string.save)) } } @@ -116,22 +116,22 @@ fun SaveToGallery( mimeType = mimeType, onSuccess = { scope.launch { - Toast.makeText( - localContext, - localContext.getString(R.string.image_saved_to_the_gallery), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + localContext, + stringRes(localContext, R.string.image_saved_to_the_gallery), + Toast.LENGTH_SHORT, + ).show() } }, onError = { scope.launch { - Toast.makeText( - localContext, - localContext.getString(R.string.failed_to_save_the_image), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + localContext, + stringRes(localContext, R.string.failed_to_save_the_image), + Toast.LENGTH_SHORT, + ).show() } }, ) @@ -159,6 +159,6 @@ fun SaveToGallery( }, shape = ButtonBorder, ) { - Text(text = stringResource(id = R.string.save)) + Text(text = stringRes(id = R.string.save)) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt index 0384ca6524..8c8d6b3a80 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/UploadFromGallery.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -58,6 +57,7 @@ import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.GetMediaActivityResultContract +import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.util.concurrent.atomic.AtomicBoolean @@ -114,7 +114,7 @@ private fun UploadBoxButton( if (!isUploading) { Icon( imageVector = Icons.Default.AddPhotoAlternate, - contentDescription = stringResource(id = R.string.upload_image), + contentDescription = stringRes(id = R.string.upload_image), modifier = Modifier.height(25.dp), tint = tint, ) @@ -137,8 +137,7 @@ val DefaultAnimationColors = Color(0xFFFCAF45), Color(0xFFFFDC80), Color(0xFF5851D8), - ) - .toImmutableList() + ).toImmutableList() @Composable fun LoadingAnimation( @@ -164,7 +163,8 @@ fun LoadingAnimation( CircularProgressIndicator( modifier = - Modifier.size(size = indicatorSize) + Modifier + .size(size = indicatorSize) .rotate(degrees = rotateAnimation) .border( width = 4.dp, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt index 90f684c6d6..77e55e9eca 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt @@ -37,7 +37,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -46,6 +45,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.SaveButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -76,7 +76,7 @@ fun AddDMRelayListDialog( ) { Spacer(modifier = StdHorzSpacer) - Text(stringResource(R.string.dm_relays_title)) + Text(stringRes(R.string.dm_relays_title)) SaveButton( onPost = { @@ -126,13 +126,13 @@ private fun Explanation() { Card(modifier = MaterialTheme.colorScheme.imageModifier) { Column(modifier = Modifier.padding(16.dp)) { Text( - text = stringResource(id = R.string.dm_relays_not_found_editing), + text = stringRes(id = R.string.dm_relays_not_found_editing), ) Spacer(modifier = StdVertSpacer) Text( - text = stringResource(id = R.string.dm_relays_not_found_examples), + text = stringRes(id = R.string.dm_relays_not_found_examples), ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt index 62e3469378..137024d701 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt @@ -37,7 +37,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -46,6 +45,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.SaveButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -76,7 +76,7 @@ fun AddSearchRelayListDialog( ) { Spacer(modifier = StdHorzSpacer) - Text(stringResource(R.string.search_relays_title)) + Text(stringRes(R.string.search_relays_title)) SaveButton( onPost = { @@ -126,13 +126,13 @@ private fun Explanation() { Card(modifier = MaterialTheme.colorScheme.imageModifier) { Column(modifier = Modifier.padding(16.dp)) { Text( - text = stringResource(id = R.string.search_relays_not_found_editing), + text = stringRes(id = R.string.search_relays_not_found_editing), ) Spacer(modifier = StdVertSpacer) Text( - text = stringResource(id = R.string.search_relays_not_found_examples), + text = stringRes(id = R.string.search_relays_not_found_examples), ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt index b0f74cc862..2d4bdbf45b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -53,6 +52,7 @@ import com.vitorpamplona.amethyst.service.relays.RelayStat import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.SaveButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.MinHorzSpacer import com.vitorpamplona.amethyst.ui.theme.RowColSpacing @@ -111,7 +111,7 @@ fun AllRelayListView( Spacer(modifier = MinHorzSpacer) Text( - text = stringResource(R.string.relay_settings), + text = stringRes(R.string.relay_settings), style = MaterialTheme.typography.titleLarge, modifier = Modifier.widthIn(max = 200.dp), ) @@ -168,8 +168,8 @@ fun AllRelayListView( LazyColumn(contentPadding = FeedPadding) { item { SettingsCategory( - stringResource(R.string.public_home_section), - stringResource(R.string.public_home_section_explainer), + stringRes(R.string.public_home_section), + stringRes(R.string.public_home_section_explainer), Modifier.padding(bottom = 8.dp), ) } @@ -177,32 +177,32 @@ fun AllRelayListView( item { SettingsCategory( - stringResource(R.string.public_notif_section), - stringResource(R.string.public_notif_section_explainer), + stringRes(R.string.public_notif_section), + stringRes(R.string.public_notif_section_explainer), ) } renderNip65NotifItems(notifFeedState, nip65ViewModel, accountViewModel, onClose, nav) item { SettingsCategory( - stringResource(R.string.private_inbox_section), - stringResource(R.string.private_inbox_section_explainer), + stringRes(R.string.private_inbox_section), + stringRes(R.string.private_inbox_section_explainer), ) } renderDMItems(dmFeedState, dmViewModel, accountViewModel, onClose, nav) item { SettingsCategory( - stringResource(R.string.private_outbox_section), - stringResource(R.string.private_outbox_section_explainer), + stringRes(R.string.private_outbox_section), + stringRes(R.string.private_outbox_section_explainer), ) } renderPrivateOutboxItems(privateOutboxFeedState, privateOutboxViewModel, accountViewModel, onClose, nav) item { SettingsCategoryWithButton( - stringResource(R.string.search_section), - stringResource(R.string.search_section_explainer), + stringRes(R.string.search_section), + stringRes(R.string.search_section_explainer), action = { ResetSearchRelays(searchViewModel) }, @@ -212,16 +212,16 @@ fun AllRelayListView( item { SettingsCategory( - stringResource(R.string.local_section), - stringResource(R.string.local_section_explainer), + stringRes(R.string.local_section), + stringRes(R.string.local_section_explainer), ) } renderLocalItems(localFeedState, localViewModel, accountViewModel, onClose, nav) item { SettingsCategoryWithButton( - stringResource(R.string.kind_3_section), - stringResource(R.string.kind_3_section_description), + stringRes(R.string.kind_3_section), + stringRes(R.string.kind_3_section_description), action = { ResetKind3Relays(kind3ViewModel) }, @@ -243,7 +243,7 @@ fun ResetKind3Relays(postViewModel: Kind3RelayListViewModel) { postViewModel.loadRelayDocuments() }, ) { - Text(stringResource(R.string.default_relays)) + Text(stringRes(R.string.default_relays)) } } @@ -256,7 +256,7 @@ fun ResetSearchRelays(postViewModel: SearchRelayListViewModel) { postViewModel.loadRelayDocuments() }, ) { - Text(stringResource(R.string.default_relays)) + Text(stringRes(R.string.default_relays)) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt index 045421fd63..89f40a14c0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt @@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier fun BasicRelaySetupInfoClickableRow( item: BasicRelaySetupInfo, loadProfilePicture: Boolean, + loadRobohash: Boolean, onDelete: (BasicRelaySetupInfo) -> Unit, onClick: () -> Unit, accountViewModel: AccountViewModel, @@ -65,6 +66,7 @@ fun BasicRelaySetupInfoClickableRow( item.briefInfo.displayUrl, iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon, loadProfilePicture, + loadRobohash, MaterialTheme.colorScheme.largeRelayIconModifier, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt index 5a4c8069ea..cc69a83c41 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt @@ -27,10 +27,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes @Composable fun BasicRelaySetupInfoDialog( @@ -52,14 +54,10 @@ fun BasicRelaySetupInfoDialog( ) } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - BasicRelaySetupInfoClickableRow( item = item, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onDelete = onDelete, accountViewModel = accountViewModel, onClick = { @@ -72,28 +70,32 @@ fun BasicRelaySetupInfoDialog( val msg = when (errorCode) { Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, @@ -101,7 +103,7 @@ fun BasicRelaySetupInfoDialog( } accountViewModel.toast( - context.getString(R.string.unable_to_download_relay_document), + stringRes(context, R.string.unable_to_download_relay_document), msg, ) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt index f0f09b413c..e6d6cd0a51 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt index 6eeae17168..0eba7699e2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt @@ -61,12 +61,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever @@ -76,6 +76,7 @@ import com.vitorpamplona.amethyst.service.relays.RelayStat import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding @@ -146,6 +147,7 @@ fun LazyListScope.renderKind3Items( fun ServerConfigPreview() { ClickableRelayItem( loadProfilePicture = true, + loadRobohash = false, item = Kind3BasicRelaySetupInfo( url = "nostr.mom", @@ -200,14 +202,10 @@ fun LoadRelayInfo( ) } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - ClickableRelayItem( item = item, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onToggleDownload = onToggleDownload, onToggleUpload = onToggleUpload, onToggleFollows = onToggleFollows, @@ -226,28 +224,32 @@ fun LoadRelayInfo( val msg = when (errorCode) { Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, ) Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> - context.getString( + stringRes( + context, R.string.relay_information_document_error_assemble_url, url, exceptionMessage, @@ -255,7 +257,7 @@ fun LoadRelayInfo( } accountViewModel.toast( - context.getString(R.string.unable_to_download_relay_document), + stringRes(context, R.string.unable_to_download_relay_document), msg, ) }, @@ -268,6 +270,7 @@ fun LoadRelayInfo( fun ClickableRelayItem( item: Kind3BasicRelaySetupInfo, loadProfilePicture: Boolean, + loadRobohash: Boolean, onToggleDownload: (Kind3BasicRelaySetupInfo) -> Unit, onToggleUpload: (Kind3BasicRelaySetupInfo) -> Unit, onToggleFollows: (Kind3BasicRelaySetupInfo) -> Unit, @@ -293,6 +296,7 @@ fun ClickableRelayItem( item.briefInfo.displayUrl, iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon, loadProfilePicture, + loadRobohash, MaterialTheme.colorScheme.largeRelayIconModifier, ) } @@ -346,19 +350,20 @@ private fun StatusRow( Icon( imageVector = Icons.Default.Download, - contentDescription = stringResource(R.string.read_from_relay), + contentDescription = stringRes(R.string.read_from_relay), modifier = - Modifier.size(15.dp) + Modifier + .size(15.dp) .combinedClickable( onClick = { onToggleDownload(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.read_from_relay), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.read_from_relay), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -382,19 +387,20 @@ private fun StatusRow( Icon( imageVector = Icons.Default.Upload, - stringResource(R.string.write_to_relay), + stringRes(R.string.write_to_relay), modifier = - Modifier.size(15.dp) + Modifier + .size(15.dp) .combinedClickable( onClick = { onToggleUpload(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.write_to_relay), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.write_to_relay), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -418,19 +424,20 @@ private fun StatusRow( Icon( imageVector = Icons.Default.SyncProblem, - stringResource(R.string.errors), + stringRes(R.string.errors), modifier = - Modifier.size(15.dp) + Modifier + .size(15.dp) .combinedClickable( onClick = {}, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.errors), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.errors), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -452,19 +459,20 @@ private fun StatusRow( Icon( imageVector = Icons.Default.DeleteSweep, - stringResource(R.string.spam), + stringRes(R.string.spam), modifier = - Modifier.size(15.dp) + Modifier + .size(15.dp) .combinedClickable( onClick = {}, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.spam), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.spam), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -498,7 +506,7 @@ private fun ActiveToggles( val context = LocalContext.current Text( - text = stringResource(id = R.string.active_for), + text = stringRes(id = R.string.active_for), maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colorScheme.placeholderText, @@ -512,20 +520,21 @@ private fun ActiveToggles( ) { Icon( painterResource(R.drawable.ic_home), - stringResource(R.string.home_feed), + stringRes(R.string.home_feed), modifier = - Modifier.padding(horizontal = 5.dp) + Modifier + .padding(horizontal = 5.dp) .size(15.dp) .combinedClickable( onClick = { onToggleFollows(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.home_feed), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.home_feed), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -545,20 +554,21 @@ private fun ActiveToggles( ) { Icon( painterResource(R.drawable.ic_dm), - stringResource(R.string.private_message_feed), + stringRes(R.string.private_message_feed), modifier = - Modifier.padding(horizontal = 5.dp) + Modifier + .padding(horizontal = 5.dp) .size(15.dp) .combinedClickable( onClick = { onTogglePrivateDMs(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.private_message_feed), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.private_message_feed), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -578,20 +588,21 @@ private fun ActiveToggles( ) { Icon( imageVector = Icons.Default.Groups, - contentDescription = stringResource(R.string.public_chat_feed), + contentDescription = stringRes(R.string.public_chat_feed), modifier = - Modifier.padding(horizontal = 5.dp) + Modifier + .padding(horizontal = 5.dp) .size(15.dp) .combinedClickable( onClick = { onTogglePublicChats(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.public_chat_feed), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.public_chat_feed), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -611,20 +622,21 @@ private fun ActiveToggles( ) { Icon( imageVector = Icons.Default.Public, - stringResource(R.string.global_feed), + stringRes(R.string.global_feed), modifier = - Modifier.padding(horizontal = 5.dp) + Modifier + .padding(horizontal = 5.dp) .size(15.dp) .combinedClickable( onClick = { onToggleGlobal(item) }, onLongClick = { scope.launch { - Toast.makeText( - context, - context.getString(R.string.global_feed), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.global_feed), + Toast.LENGTH_SHORT, + ).show() } }, ), @@ -672,7 +684,7 @@ private fun FirstLine( ) { Icon( imageVector = Icons.Default.Cancel, - contentDescription = stringResource(id = R.string.remove), + contentDescription = stringRes(id = R.string.remove), modifier = Modifier.padding(start = 10.dp).size(15.dp), tint = WarningColor, ) @@ -691,7 +703,7 @@ fun Kind3RelayEditBox( Row(verticalAlignment = Alignment.CenterVertically) { OutlinedTextField( - label = { Text(text = stringResource(R.string.add_a_relay)) }, + label = { Text(text = stringRes(R.string.add_a_relay)) }, modifier = Modifier.weight(1f), value = url, onValueChange = { url = it }, @@ -708,7 +720,7 @@ fun Kind3RelayEditBox( IconButton(onClick = { read = !read }) { Icon( imageVector = Icons.Default.Download, - contentDescription = stringResource(id = R.string.read_from_relay), + contentDescription = stringRes(id = R.string.read_from_relay), modifier = Modifier.size(Size35dp).padding(horizontal = 5.dp), tint = if (read) { @@ -722,7 +734,7 @@ fun Kind3RelayEditBox( IconButton(onClick = { write = !write }) { Icon( imageVector = Icons.Default.Upload, - contentDescription = stringResource(id = R.string.write_to_relay), + contentDescription = stringRes(id = R.string.write_to_relay), modifier = Modifier.size(Size35dp).padding(horizontal = 5.dp), tint = if (write) { @@ -761,7 +773,7 @@ fun Kind3RelayEditBox( }, ), ) { - Text(text = stringResource(id = R.string.add), color = Color.White) + Text(text = stringRes(id = R.string.add), color = Color.White) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt index af9d2bc7d8..13d4406003 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt index ce285786a3..275b09c725 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt index 009da474d2..a1d051b9f3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt index 7ccc0cc448..fa4b5c0862 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.actions.relays -import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -43,16 +42,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.service.relays.RelayStats import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.ClickableEmail import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -61,6 +61,7 @@ import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.timeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdPadding @@ -81,12 +82,13 @@ fun RelayInformationDialog( ) { val messages = remember(relayBriefInfo) { - RelayStats.get(url = relayBriefInfo.url).messages.snapshot().values.sortedByDescending { it.time }.toImmutableList() - } - - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value + RelayStats + .get(url = relayBriefInfo.url) + .messages + .snapshot() + .values + .sortedByDescending { it.time } + .toImmutableList() } Dialog( @@ -124,10 +126,11 @@ fun RelayInformationDialog( ) { Column { RenderRelayIcon( - relayBriefInfo.displayUrl, - relayInfo.icon ?: relayBriefInfo.favIcon, - automaticallyShowProfilePicture, - MaterialTheme.colorScheme.largeRelayIconModifier, + displayUrl = relayBriefInfo.displayUrl, + iconUrl = relayInfo.icon ?: relayBriefInfo.favIcon, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + iconModifier = MaterialTheme.colorScheme.largeRelayIconModifier, ) } @@ -141,7 +144,7 @@ fun RelayInformationDialog( } } item { - Section(stringResource(R.string.owner)) + Section(stringRes(R.string.owner)) relayInfo.pubkey?.let { DisplayOwnerInformation(it, accountViewModel) { @@ -151,16 +154,16 @@ fun RelayInformationDialog( } } item { - Section(stringResource(R.string.software)) + Section(stringRes(R.string.software)) DisplaySoftwareInformation(relayInfo) - Section(stringResource(R.string.version)) + Section(stringRes(R.string.version)) SectionContent(relayInfo.version ?: "") } item { - Section(stringResource(R.string.contact)) + Section(stringRes(R.string.contact)) Box(modifier = Modifier.padding(start = 10.dp)) { relayInfo.contact?.let { @@ -175,21 +178,21 @@ fun RelayInformationDialog( } } item { - Section(stringResource(R.string.supports)) + Section(stringRes(R.string.supports)) DisplaySupportedNips(relayInfo) } item { relayInfo.fees?.admission?.let { if (it.isNotEmpty()) { - Section(stringResource(R.string.admission_fees)) + Section(stringRes(R.string.admission_fees)) it.forEach { item -> SectionContent("${item.amount?.div(1000) ?: 0} sats") } } } relayInfo.payments_url?.let { - Section(stringResource(R.string.payments_url)) + Section(stringRes(R.string.payments_url)) Box(modifier = Modifier.padding(start = 10.dp)) { ClickableUrl( @@ -201,68 +204,68 @@ fun RelayInformationDialog( } item { relayInfo.limitation?.let { - Section(stringResource(R.string.limitations)) + Section(stringRes(R.string.limitations)) val authRequiredText = - if (it.auth_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no) + if (it.auth_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no) val paymentRequiredText = - if (it.payment_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no) + if (it.payment_required ?: false) stringRes(R.string.yes) else stringRes(R.string.no) val restrictedWritesText = - if (it.restricted_writes ?: false) stringResource(R.string.yes) else stringResource(R.string.no) + if (it.restricted_writes ?: false) stringRes(R.string.yes) else stringRes(R.string.no) Column { SectionContent( - "${stringResource(R.string.message_length)}: ${it.max_message_length ?: 0}", + "${stringRes(R.string.message_length)}: ${it.max_message_length ?: 0}", ) SectionContent( - "${stringResource(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}", + "${stringRes(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}", ) - SectionContent("${stringResource(R.string.filters)}: ${it.max_filters ?: 0}") + SectionContent("${stringRes(R.string.filters)}: ${it.max_filters ?: 0}") SectionContent( - "${stringResource(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}", + "${stringRes(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}", ) - SectionContent("${stringResource(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}") + SectionContent("${stringRes(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}") SectionContent( - "${stringResource(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}", + "${stringRes(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}", ) SectionContent( - "${stringResource(R.string.content_length)}: ${it.max_content_length ?: 0}", + "${stringRes(R.string.content_length)}: ${it.max_content_length ?: 0}", ) SectionContent( - "${stringResource(R.string.max_limit)}: ${it.max_limit ?: 0}", + "${stringRes(R.string.max_limit)}: ${it.max_limit ?: 0}", ) - SectionContent("${stringResource(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}") - SectionContent("${stringResource(R.string.auth)}: $authRequiredText") - SectionContent("${stringResource(R.string.payment)}: $paymentRequiredText") - SectionContent("${stringResource(R.string.restricted_writes)}: $restrictedWritesText") + SectionContent("${stringRes(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}") + SectionContent("${stringRes(R.string.auth)}: $authRequiredText") + SectionContent("${stringRes(R.string.payment)}: $paymentRequiredText") + SectionContent("${stringRes(R.string.restricted_writes)}: $restrictedWritesText") } } } item { relayInfo.relay_countries?.let { - Section(stringResource(R.string.countries)) + Section(stringRes(R.string.countries)) FlowRow { it.forEach { item -> SectionContent(item) } } } } item { relayInfo.language_tags?.let { - Section(stringResource(R.string.languages)) + Section(stringRes(R.string.languages)) FlowRow { it.forEach { item -> SectionContent(item) } } } } item { relayInfo.tags?.let { - Section(stringResource(R.string.tags)) + Section(stringRes(R.string.tags)) FlowRow { it.forEach { item -> SectionContent(item) } } } } item { relayInfo.posting_policy?.let { - Section(stringResource(R.string.posting_policy)) + Section(stringRes(R.string.posting_policy)) Box(Modifier.padding(10.dp)) { ClickableUrl( @@ -274,7 +277,7 @@ fun RelayInformationDialog( } item { - Section(stringResource(R.string.relay_error_messages)) + Section(stringRes(R.string.relay_error_messages)) } items(messages) { msg -> @@ -346,7 +349,7 @@ private fun DisplayOwnerInformation( nav: (String) -> Unit, ) { LoadUser(baseUserHex = userHex, accountViewModel) { - Crossfade(it) { + CrossfadeIfEnabled(it, accountViewModel = accountViewModel) { if (it != null) { UserCompose( baseUser = it, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt index 18e0d36fb5..4d71d403f8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt @@ -34,10 +34,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.WarningColor import com.vitorpamplona.amethyst.ui.theme.allGoodColor @@ -76,7 +76,7 @@ fun RelayNameAndRemoveButton( ) { Icon( imageVector = Icons.Default.Cancel, - contentDescription = stringResource(id = R.string.remove), + contentDescription = stringRes(id = R.string.remove), modifier = Modifier .padding(start = 10.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt index 755a3566d8..8ebf04afad 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt @@ -36,11 +36,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.allGoodColor import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.warningColor @@ -58,7 +58,7 @@ fun RelayStatusRow( Icon( imageVector = Icons.Default.Download, - contentDescription = stringResource(R.string.read_from_relay), + contentDescription = stringRes(R.string.read_from_relay), modifier = Modifier .size(15.dp) @@ -84,7 +84,7 @@ fun RelayStatusRow( Icon( imageVector = Icons.Default.Upload, - stringResource(R.string.write_to_relay), + stringRes(R.string.write_to_relay), modifier = Modifier .size(15.dp) @@ -110,7 +110,7 @@ fun RelayStatusRow( Icon( imageVector = Icons.Default.SyncProblem, - stringResource(R.string.errors), + stringRes(R.string.errors), modifier = Modifier .size(15.dp) @@ -141,7 +141,7 @@ fun RelayStatusRow( Icon( imageVector = Icons.Default.DeleteSweep, - stringResource(R.string.spam), + stringRes(R.string.spam), modifier = Modifier .size(15.dp) @@ -153,12 +153,12 @@ fun RelayStatusRow( R.string.spam_description, ) scope.launch { - Toast.makeText( - context, - context.getString(R.string.spam), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.spam), + Toast.LENGTH_SHORT, + ).show() } }, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt index 9af9c83262..ded0da3987 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt @@ -35,9 +35,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.relays.RelayStat +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -49,7 +49,7 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Size10dp)) { OutlinedTextField( - label = { Text(text = stringResource(R.string.add_a_relay)) }, + label = { Text(text = stringRes(R.string.add_a_relay)) }, modifier = Modifier.weight(1f), value = url, onValueChange = { url = it }, @@ -81,7 +81,7 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { }, ), ) { - Text(text = stringResource(id = R.string.add), color = Color.White) + Text(text = stringRes(id = R.string.add), color = Color.White) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt index bb9f1489c3..e203c9e397 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt index 5eb6d9e041..d556fe6f9c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt @@ -38,13 +38,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.Size55Modifier @@ -86,7 +86,7 @@ fun ChannelFabColumn( containerColor = MaterialTheme.colorScheme.primary, ) { Text( - text = stringResource(R.string.messages_new_message), + text = stringRes(R.string.messages_new_message), color = Color.White, textAlign = TextAlign.Center, fontSize = Font12SP, @@ -105,7 +105,7 @@ fun ChannelFabColumn( containerColor = MaterialTheme.colorScheme.primary, ) { Text( - text = stringResource(R.string.messages_create_public_chat), + text = stringRes(R.string.messages_create_public_chat), color = Color.White, textAlign = TextAlign.Center, fontSize = Font12SP, @@ -123,7 +123,7 @@ fun ChannelFabColumn( ) { Icon( imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.messages_create_public_private_chat_description), + contentDescription = stringRes(R.string.messages_create_public_private_chat_description), modifier = Modifier.size(26.dp), tint = Color.White, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewChannelButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewChannelButton.kt index 2ca79c6849..7d69fc9540 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewChannelButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewChannelButton.kt @@ -35,11 +35,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.ui.theme.ZeroPadding @@ -61,7 +61,7 @@ fun NewChannelButton(accountViewModel: AccountViewModel) { ) { Icon( imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.new_channel), + contentDescription = stringRes(R.string.new_channel), modifier = Modifier.size(26.dp), tint = Color.White, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt index 63f132eebc..78f39e7692 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt @@ -33,13 +33,13 @@ import androidx.compose.runtime.setValue 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.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size55Modifier @Composable @@ -73,7 +73,7 @@ fun NewCommunityNoteButton( ) { Icon( painter = painterResource(R.drawable.ic_compose), - contentDescription = stringResource(id = R.string.new_community_note), + contentDescription = stringRes(id = R.string.new_community_note), modifier = Modifier.size(26.dp), tint = Color.White, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewImageButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewImageButton.kt index 16973d1c33..5724d57b45 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewImageButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewImageButton.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -60,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -136,7 +136,7 @@ fun NewImageButton( ) { Icon( painter = painterResource(R.drawable.ic_compose), - contentDescription = stringResource(id = R.string.new_short), + contentDescription = stringRes(id = R.string.new_short), modifier = Modifier.size(26.dp), tint = Color.White, ) @@ -152,8 +152,7 @@ private fun ShowProgress(postViewModel: NewMediaModel) { animateFloatAsState( targetValue = postViewModel.uploadingPercentage.value, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, - ) - .value, + ).value, modifier = Size55Modifier .clip(CircleShape) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt index a0e8fe4407..cac2b8c490 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt @@ -33,11 +33,11 @@ import androidx.compose.runtime.setValue 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.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size55Modifier @Composable @@ -59,7 +59,7 @@ fun NewNoteButton( ) { Icon( painter = painterResource(R.drawable.ic_compose), - contentDescription = stringResource(R.string.new_post), + contentDescription = stringRes(R.string.new_post), modifier = Modifier.size(26.dp), tint = Color.White, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 8a8daec0da..9b7e17876d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.components import android.content.Context import android.content.Intent import android.net.Uri -import androidx.compose.animation.Crossfade import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -53,7 +52,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection @@ -69,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.model.ThemeType import com.vitorpamplona.amethyst.service.CachedCashuProcessor import com.vitorpamplona.amethyst.service.CashuToken +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation import com.vitorpamplona.amethyst.ui.note.CashuIcon import com.vitorpamplona.amethyst.ui.note.CopyIcon @@ -76,6 +75,7 @@ import com.vitorpamplona.amethyst.ui.note.OpenInNewIcon import com.vitorpamplona.amethyst.ui.note.ZapIcon import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -106,7 +106,7 @@ fun CashuPreview( } } - Crossfade(targetState = cashuData, label = "CashuPreview") { + CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) { when (it) { is GenericLoadable.Loaded -> CashuPreview(it.loaded, accountViewModel) is GenericLoadable.Error -> @@ -190,7 +190,7 @@ fun CashuPreview( ) Text( - text = stringResource(R.string.cashu), + text = stringRes(R.string.cashu), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -200,7 +200,7 @@ fun CashuPreview( HorizontalDivider(thickness = DividerThickness) Text( - text = "${token.totalAmount} ${stringResource(id = R.string.sats)}", + text = "${token.totalAmount} ${stringRes(id = R.string.sats)}", fontSize = 25.sp, fontWeight = FontWeight.W500, modifier = @@ -239,7 +239,7 @@ fun CashuPreview( Spacer(DoubleHorzSpacer) Text( - stringResource(id = R.string.cashu_redeem_to_zap), + stringRes(id = R.string.cashu_redeem_to_zap), color = Color.White, fontSize = 16.sp, ) @@ -256,7 +256,7 @@ fun CashuPreview( startActivity(context, intent, null) } catch (e: Exception) { if (e is CancellationException) throw e - toast("Cashu", context.getString(R.string.cashu_no_wallet_found)) + toast(stringRes(context, R.string.cashu), stringRes(context, R.string.cashu_no_wallet_found)) } }, shape = QuoteBorder, @@ -268,7 +268,7 @@ fun CashuPreview( CashuIcon(Size20Modifier) Spacer(DoubleHorzSpacer) Text( - stringResource(id = R.string.cashu_redeem_to_cashu), + stringRes(id = R.string.cashu_redeem_to_cashu), color = Color.White, fontSize = 16.sp, ) @@ -287,7 +287,7 @@ fun CashuPreview( ) { CopyIcon(Size20Modifier, Color.White) Spacer(DoubleHorzSpacer) - Text(stringResource(id = R.string.cashu_copy_token), color = Color.White, fontSize = 16.sp) + Text(stringRes(id = R.string.cashu_copy_token), color = Color.White, fontSize = 16.sp) } Spacer(modifier = StdHorzSpacer) } @@ -328,14 +328,14 @@ fun CashuPreviewNew( ) Text( - text = stringResource(R.string.cashu), + text = stringRes(R.string.cashu), fontSize = 12.sp, modifier = Modifier.padding(start = 5.dp, bottom = 1.dp), ) } Text( - text = "${token.totalAmount} ${stringResource(id = R.string.sats)}", + text = "${token.totalAmount} ${stringRes(id = R.string.sats)}", fontSize = 20.sp, modifier = Modifier.padding(top = 5.dp), ) @@ -378,7 +378,7 @@ fun CashuPreviewNew( startActivity(context, intent, null) } catch (e: Exception) { if (e is CancellationException) throw e - toast("Cashu", context.getString(R.string.cashu_no_wallet_found)) + toast(stringRes(context, R.string.cashu), stringRes(context, R.string.cashu_no_wallet_found)) } }, shape = SmallishBorder, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableBox.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableBox.kt new file mode 100644 index 0000000000..57410903e2 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableBox.kt @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import com.vitorpamplona.amethyst.ui.theme.Size24dp + +@Composable +fun ClickableBox( + modifier: Modifier, + onClick: () -> Unit, + content: @Composable () -> Unit, +) { + Box( + modifier.clickable( + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false, radius = Size24dp), + onClick = onClick, + ), + contentAlignment = Alignment.Center, + ) { + content() + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 3dca50c64e..dd7cbea9af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -470,13 +470,12 @@ fun CreateTextWithEmoji( overflow: TextOverflow = TextOverflow.Clip, modifier: Modifier = Modifier, ) { - val textColor = - color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } } - CustomEmojiChecker( text, tags, onEmojiText = { + val textColor = + color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } } val style = LocalTextStyle.current .merge( @@ -486,12 +485,13 @@ fun CreateTextWithEmoji( fontWeight = fontWeight, fontSize = fontSize, ), - ) - .toSpanStyle() + ).toSpanStyle() InLineIconRenderer(it, style, fontSize, maxLines, overflow, modifier) }, onRegularText = { + val textColor = + color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } } Text( text = it, color = textColor, @@ -536,8 +536,7 @@ fun CreateTextWithEmoji( fontWeight = fontWeight, fontSize = fontSize, ), - ) - .toSpanStyle() + ).toSpanStyle() } InLineIconRenderer(it, style, fontSize, maxLines, overflow, modifier) @@ -672,8 +671,7 @@ fun ClickableInLineIconRenderer( } else { null } - } - .associate { it.first to it.second } + }.associate { it.first to it.second } val annotatedText = buildAnnotatedString { @@ -757,8 +755,7 @@ fun InLineIconRenderer( } else { null } - } - .associate { it.first to it.second } + }.associate { it.first to it.second } val annotatedText = remember { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt index a46bddec4d..647d31c395 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -35,21 +34,27 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextDirection import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog import com.vitorpamplona.amethyst.ui.note.payViaIntent +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.encoders.LnWithdrawalUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable -fun MayBeWithdrawal(lnurlWord: String) { +fun MayBeWithdrawal( + lnurlWord: String, + accountViewModel: AccountViewModel, +) { var lnWithdrawal by remember { mutableStateOf(null) } LaunchedEffect(key1 = lnurlWord) { launch(Dispatchers.IO) { lnWithdrawal = LnWithdrawalUtil.findWithdrawal(lnurlWord) } } - Crossfade(targetState = lnWithdrawal) { + CrossfadeIfEnabled(targetState = lnWithdrawal, accountViewModel = accountViewModel) { if (it != null) { ClickableWithdrawal(withdrawalString = it) } else { @@ -71,7 +76,7 @@ fun ClickableWithdrawal(withdrawalString: String) { if (showErrorMessageDialog != null) { ErrorMessageDialog( - title = context.getString(R.string.error_dialog_pay_withdraw_error), + title = stringRes(context, R.string.error_dialog_pay_withdraw_error), textContent = showErrorMessageDialog ?: "", onDismiss = { showErrorMessageDialog = null }, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt index ae858719b7..c4445c71cf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt @@ -40,11 +40,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator import com.vitorpamplona.amethyst.ui.note.getGradient import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.StdTopPadding @@ -110,7 +110,8 @@ fun ExpandableRichTextViewer( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = - Modifier.align(Alignment.BottomCenter) + Modifier + .align(Alignment.BottomCenter) .fillMaxWidth() .background(getGradient(backgroundColor)), ) { @@ -135,6 +136,6 @@ fun ShowMoreButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.show_more), color = Color.White) + Text(text = stringRes(R.string.show_more), color = Color.White) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt new file mode 100644 index 0000000000..ab6cabd43b --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ForwardingPainter.kt @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.components + +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.DefaultAlpha +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter + +/** + * Create and return a new [Painter] that wraps [painter] with its [alpha], [colorFilter], or [onDraw] overwritten. + */ +fun forwardingPainter( + painter: Painter, + alpha: Float = DefaultAlpha, + colorFilter: ColorFilter? = null, + onDraw: DrawScope.(ForwardingDrawInfo) -> Unit = DefaultOnDraw, +): Painter = ForwardingPainter(painter, alpha, colorFilter, onDraw) + +data class ForwardingDrawInfo( + val painter: Painter, + val alpha: Float, + val colorFilter: ColorFilter?, +) + +private class ForwardingPainter( + private val painter: Painter, + private var alpha: Float, + private var colorFilter: ColorFilter?, + private val onDraw: DrawScope.(ForwardingDrawInfo) -> Unit, +) : Painter() { + private var info = newInfo() + + override val intrinsicSize get() = painter.intrinsicSize + + override fun applyAlpha(alpha: Float): Boolean { + if (alpha != DefaultAlpha) { + this.alpha = alpha + this.info = newInfo() + } + return true + } + + override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { + if (colorFilter == null) { + this.colorFilter = colorFilter + this.info = newInfo() + } + return true + } + + override fun DrawScope.onDraw() = onDraw(info) + + private fun newInfo() = ForwardingDrawInfo(painter, alpha, colorFilter) +} + +private val DefaultOnDraw: DrawScope.(ForwardingDrawInfo) -> Unit = { info -> + with(info.painter) { + draw(size, info.alpha, info.colorFilter) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt index f7cc281c5b..bacc64738f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -44,7 +43,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp @@ -54,8 +52,11 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.hashtags.Lightning import com.vitorpamplona.amethyst.service.lnurl.CachedLnInvoiceParser import com.vitorpamplona.amethyst.service.lnurl.InvoiceAmount +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog import com.vitorpamplona.amethyst.ui.note.payViaIntent +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -82,9 +83,12 @@ fun LoadValueFromInvoice( } @Composable -fun MayBeInvoicePreview(lnbcWord: String) { +fun MayBeInvoicePreview( + lnbcWord: String, + accountViewModel: AccountViewModel, +) { LoadValueFromInvoice(lnbcWord = lnbcWord) { invoiceAmount -> - Crossfade(targetState = invoiceAmount, label = "MayBeInvoicePreview") { + CrossfadeIfEnabled(targetState = invoiceAmount, label = "MayBeInvoicePreview", accountViewModel = accountViewModel) { if (it != null) { InvoicePreview(it.invoice, it.amount) } else { @@ -108,7 +112,7 @@ fun InvoicePreview( if (showErrorMessageDialog != null) { ErrorMessageDialog( - title = context.getString(R.string.error_dialog_pay_invoice_error), + title = stringRes(context, R.string.error_dialog_pay_invoice_error), textContent = showErrorMessageDialog ?: "", onDismiss = { showErrorMessageDialog = null }, ) @@ -143,7 +147,7 @@ fun InvoicePreview( ) Text( - text = stringResource(R.string.lightning_invoice), + text = stringRes(R.string.lightning_invoice), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -154,7 +158,7 @@ fun InvoicePreview( amount?.let { Text( - text = "$it ${stringResource(id = R.string.sats)}", + text = "$it ${stringRes(id = R.string.sats)}", fontSize = 25.sp, fontWeight = FontWeight.W500, modifier = @@ -176,7 +180,7 @@ fun InvoicePreview( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(text = stringResource(R.string.pay), color = Color.White, fontSize = 20.sp) + Text(text = stringRes(R.string.pay), color = Color.White, fontSize = 20.sp) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt index 89a976e46a..915df45fa6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -56,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.Lightning import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -78,7 +78,8 @@ fun InvoiceRequestCard( ) { Column( modifier = - Modifier.fillMaxWidth() + Modifier + .fillMaxWidth() .padding(start = 30.dp, end = 30.dp) .clip(shape = QuoteBorder) .border(1.dp, MaterialTheme.colorScheme.subtleBorder, QuoteBorder), @@ -126,7 +127,7 @@ fun InvoiceRequest( ) Text( - text = titleText ?: stringResource(R.string.lightning_tips), + text = titleText ?: stringRes(R.string.lightning_tips), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -139,13 +140,13 @@ fun InvoiceRequest( var amount by remember { mutableStateOf(1000L) } OutlinedTextField( - label = { Text(text = stringResource(R.string.note_to_receiver)) }, + label = { Text(text = stringRes(R.string.note_to_receiver)) }, modifier = Modifier.fillMaxWidth(), value = message, onValueChange = { message = it }, placeholder = { Text( - text = stringResource(R.string.thank_you_so_much), + text = stringRes(R.string.thank_you_so_much), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -157,7 +158,7 @@ fun InvoiceRequest( ) OutlinedTextField( - label = { Text(text = stringResource(R.string.amount_in_sats)) }, + label = { Text(text = stringRes(R.string.amount_in_sats)) }, modifier = Modifier.fillMaxWidth(), value = amount.toString(), onValueChange = { @@ -199,9 +200,7 @@ fun InvoiceRequest( context = context, ) } else { - account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) { - zapRequest, - -> + account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) { zapRequest -> LocalCache.justConsume(zapRequest, null) LightningAddressResolver() .lnAddressInvoice( @@ -225,7 +224,7 @@ fun InvoiceRequest( ), ) { Text( - text = buttonText ?: stringResource(R.string.send_sats), + text = buttonText ?: stringRes(R.string.send_sats), color = Color.White, fontSize = 20.sp, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt index 2d91cab5c5..7ef2508c6c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadUrlPreview.kt @@ -20,16 +20,14 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.model.UrlCachedPreviewer +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding @@ -40,9 +38,7 @@ fun LoadUrlPreview( callbackUri: String? = null, accountViewModel: AccountViewModel, ) { - val automaticallyShowUrlPreview = remember { accountViewModel.settings.showUrlPreview.value } - - if (!automaticallyShowUrlPreview) { + if (!accountViewModel.settings.showUrlPreview.value) { ClickableUrl(urlText, url) } else { val urlPreviewState by @@ -55,10 +51,10 @@ fun LoadUrlPreview( } } - Crossfade( + CrossfadeIfEnabled( targetState = urlPreviewState, - animationSpec = tween(durationMillis = 100), label = "UrlPreview", + accountViewModel = accountViewModel, ) { state -> when (state) { is UrlPreviewState.Loaded -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 7bcb049923..a499e4b98b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -30,7 +29,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material3.Icon @@ -86,6 +84,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon import com.vitorpamplona.amethyst.service.CachedRichTextParser +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.markdown.RenderContentAsMarkdown import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.note.NoteCompose @@ -103,15 +102,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -fun isMarkdown(content: String): Boolean { - return content.startsWith("> ") || +fun isMarkdown(content: String): Boolean = + content.startsWith("> ") || content.startsWith("# ") || content.contains("##") || content.contains("__") || content.contains("**") || content.contains("```") || content.contains("](") -} @Composable fun RichTextViewer( @@ -172,8 +170,8 @@ fun RenderRegularPreview() { // is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel) // is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel) is EmojiSegment -> RenderCustomEmoji(word.segmentText, state) - is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) - is WithdrawSegment -> MayBeWithdrawal(word.segmentText) + // is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) + // is WithdrawSegment -> MayBeWithdrawal(word.segmentText) // is CashuSegment -> CashuPreview(word.segmentText, accountViewModel) is EmailSegment -> ClickableEmail(word.segmentText) is PhoneSegment -> ClickablePhone(word.segmentText) @@ -209,8 +207,8 @@ fun RenderRegularPreview2() { // is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel) // is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel) is EmojiSegment -> RenderCustomEmoji(word.segmentText, state) - is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) - is WithdrawSegment -> MayBeWithdrawal(word.segmentText) + // is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) + // is WithdrawSegment -> MayBeWithdrawal(word.segmentText) // is CashuSegment -> CashuPreview(word.segmentText, accountViewModel) is EmailSegment -> ClickableEmail(word.segmentText) is PhoneSegment -> ClickablePhone(word.segmentText) @@ -270,8 +268,8 @@ fun RenderRegularPreview3() { // is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel) is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, null, accountViewModel) is EmojiSegment -> RenderCustomEmoji(word.segmentText, state) - is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) - is WithdrawSegment -> MayBeWithdrawal(word.segmentText) + // is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) + // is WithdrawSegment -> MayBeWithdrawal(word.segmentText) // is CashuSegment -> CashuPreview(word.segmentText, accountViewModel) is EmailSegment -> ClickableEmail(word.segmentText) is PhoneSegment -> ClickablePhone(word.segmentText) @@ -424,8 +422,8 @@ private fun RenderWordWithPreview( is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel) is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, callbackUri, accountViewModel) is EmojiSegment -> RenderCustomEmoji(word.segmentText, state) - is InvoiceSegment -> MayBeInvoicePreview(word.segmentText) - is WithdrawSegment -> MayBeWithdrawal(word.segmentText) + is InvoiceSegment -> MayBeInvoicePreview(word.segmentText, accountViewModel) + is WithdrawSegment -> MayBeWithdrawal(word.segmentText, accountViewModel) is CashuSegment -> CashuPreview(word.segmentText, accountViewModel) is EmailSegment -> ClickableEmail(word.segmentText) is PhoneSegment -> ClickablePhone(word.segmentText) @@ -598,7 +596,7 @@ fun TagLink( Text(text = word.segmentText) } else { Row { - DisplayUserFromTag(it, nav) + DisplayUserFromTag(it, accountViewModel, nav) word.extras?.let { Text(text = it) } @@ -683,11 +681,12 @@ private fun DisplayNoteFromTag( @Composable private fun DisplayUserFromTag( baseUser: User, + accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { val meta by baseUser.live().userMetadataInfo.observeAsState(baseUser.info) - Crossfade(targetState = meta, label = "DisplayUserFromTag") { + CrossfadeIfEnabled(targetState = meta, label = "DisplayUserFromTag", accountViewModel = accountViewModel) { Row { CreateClickableTextWithEmoji( clickablePart = remember(meta) { it?.bestName() ?: baseUser.pubkeyDisplayHex() }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index b0cd257661..1970fee3c5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -24,11 +24,11 @@ import android.content.Context import android.graphics.BitmapFactory import android.net.Uri import androidx.compose.foundation.Image +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter @@ -51,6 +51,7 @@ import coil.request.Options import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.theme.isLight +import com.vitorpamplona.amethyst.ui.theme.onBackgroundColorFilter import java.util.Base64 @Composable @@ -58,19 +59,22 @@ fun RobohashAsyncImage( robot: String, modifier: Modifier = Modifier, contentDescription: String? = null, + loadRobohash: Boolean, ) { - val isLightTheme = MaterialTheme.colorScheme.isLight - - val robotPainter = - remember(robot) { - CachedRobohash.get(robot, isLightTheme) - } - - Image( - imageVector = robotPainter, - contentDescription = contentDescription, - modifier = modifier, - ) + if (loadRobohash) { + Image( + imageVector = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight), + contentDescription = contentDescription, + modifier = modifier, + ) + } else { + Image( + imageVector = Icons.Default.Face, + contentDescription = contentDescription, + colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter, + modifier = modifier, + ) + } } @Composable @@ -85,6 +89,7 @@ fun RobohashFallbackAsyncImage( colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, loadProfilePicture: Boolean, + loadRobohash: Boolean, ) { if (model != null && loadProfilePicture) { val isBase64 = model.startsWith("data:image/jpeg;base64,") @@ -106,9 +111,19 @@ fun RobohashFallbackAsyncImage( ) } else { val painter = - rememberVectorPainter( - image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight), - ) + if (loadRobohash) { + rememberVectorPainter( + image = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight), + ) + } else { + forwardingPainter( + painter = + rememberVectorPainter( + image = Icons.Default.Face, + ), + colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter, + ) + } AsyncImage( model = model, @@ -125,16 +140,25 @@ fun RobohashFallbackAsyncImage( ) } } else { - val robotPainter = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight) - - Image( - imageVector = robotPainter, - contentDescription = contentDescription, - modifier = modifier, - alignment = alignment, - contentScale = contentScale, - colorFilter = colorFilter, - ) + if (loadRobohash) { + Image( + imageVector = CachedRobohash.get(robot, MaterialTheme.colorScheme.isLight), + contentDescription = contentDescription, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + colorFilter = colorFilter, + ) + } else { + Image( + imageVector = Icons.Default.Face, + contentDescription = contentDescription, + colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + ) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SelectTextDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SelectTextDialog.kt index 52b3844c75..db8f877e6d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SelectTextDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SelectTextDialog.kt @@ -37,11 +37,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size24dp @@ -74,7 +74,7 @@ fun SelectTextDialog( ) { ArrowBackIcon() } - Text(text = stringResource(R.string.select_text_dialog_top)) + Text(text = stringRes(R.string.select_text_dialog_top)) } HorizontalDivider(thickness = DividerThickness) Column( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt index fd19d837dd..3866f34474 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -47,14 +46,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.quartz.events.EventInterface @@ -102,7 +102,7 @@ fun SensitivityWarning( var showContentWarningNote by remember(accountState) { mutableStateOf(accountState?.account?.showSensitiveContent != true) } - Crossfade(targetState = showContentWarningNote) { + CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) { if (it) { ContentWarningNote { showContentWarningNote = false } } else { @@ -122,13 +122,13 @@ fun ContentWarningNote(onDismiss: () -> Unit) { ) { Icon( imageVector = Icons.Default.Visibility, - contentDescription = stringResource(R.string.content_warning), + contentDescription = stringRes(R.string.content_warning), modifier = Modifier.size(70.dp).align(Alignment.BottomStart), tint = MaterialTheme.colorScheme.onBackground, ) Icon( imageVector = Icons.Rounded.Warning, - contentDescription = stringResource(R.string.content_warning), + contentDescription = stringRes(R.string.content_warning), modifier = Modifier.size(30.dp).align(Alignment.TopEnd), tint = MaterialTheme.colorScheme.onBackground, ) @@ -137,7 +137,7 @@ fun ContentWarningNote(onDismiss: () -> Unit) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Text( - text = stringResource(R.string.content_warning), + text = stringRes(R.string.content_warning), fontWeight = FontWeight.Bold, fontSize = 18.sp, ) @@ -145,7 +145,7 @@ fun ContentWarningNote(onDismiss: () -> Unit) { Row { Text( - text = stringResource(R.string.content_warning_explanation), + text = stringRes(R.string.content_warning_explanation), color = Color.Gray, modifier = Modifier.padding(top = 10.dp), textAlign = TextAlign.Center, @@ -164,7 +164,7 @@ fun ContentWarningNote(onDismiss: () -> Unit) { contentPadding = ButtonPadding, ) { Text( - text = stringResource(R.string.show_anyway), + text = stringRes(R.string.show_anyway), color = Color.White, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt index 781c12ac5d..ab89efc627 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -36,12 +36,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.previews.UrlInfoItem +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.MaxWidthWithHorzPadding import com.vitorpamplona.amethyst.ui.theme.innerPostModifier @@ -59,7 +59,7 @@ private fun CopyToClipboard( ) { val clipboardManager = LocalClipboardManager.current DropdownMenuItem( - text = { Text(stringResource(R.string.copy_url_to_clipboard)) }, + text = { Text(stringRes(R.string.copy_url_to_clipboard)) }, onClick = { clipboardManager.setText(AnnotatedString(content)) onDismiss() @@ -103,7 +103,7 @@ fun UrlPreviewCard( ) { AsyncImage( model = previewInfo.imageUrlFullPath, - contentDescription = stringResource(R.string.preview_card_image_for, previewInfo.url), + contentDescription = previewInfo.title, contentScale = ContentScale.Crop, modifier = previewCardImageModifier, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 68e4c61cb4..4636903435 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -81,7 +81,6 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle @@ -108,6 +107,7 @@ import com.vitorpamplona.amethyst.ui.note.LyricsOffIcon import com.vitorpamplona.amethyst.ui.note.MuteIcon import com.vitorpamplona.amethyst.ui.note.MutedIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize import com.vitorpamplona.amethyst.ui.theme.Size110dp import com.vitorpamplona.amethyst.ui.theme.Size165dp @@ -374,13 +374,15 @@ data class MediaItemData( val artworkUri: String? = null, ) -class MediaItemCache() : GenericBaseCache(20) { - override suspend fun compute(key: MediaItemData): MediaItem { - return MediaItem.Builder() +class MediaItemCache : GenericBaseCache(20) { + override suspend fun compute(key: MediaItemData): MediaItem = + MediaItem + .Builder() .setMediaId(key.videoUri) .setUri(key.videoUri) .setMediaMetadata( - MediaMetadata.Builder() + MediaMetadata + .Builder() .setArtist(key.authorName?.ifBlank { null }) .setTitle(key.title?.ifBlank { null } ?: key.videoUri) .setArtworkUri( @@ -394,11 +396,8 @@ class MediaItemCache() : GenericBaseCache(20) { if (e is CancellationException) throw e null }, - ) - .build(), - ) - .build() - } + ).build(), + ).build() } @Composable @@ -409,7 +408,15 @@ fun GetMediaItem( authorName: String?, inner: @Composable (State) -> Unit, ) { - val data = remember(videoUri) { MediaItemData(videoUri, title, artworkUri, authorName) } + val data = + remember(videoUri) { + MediaItemData( + videoUri = videoUri, + authorName = authorName, + title = title, + artworkUri = artworkUri, + ) + } val mediaItem by produceCachedState(cache = mediaItemCache, key = data) mediaItem?.let { @@ -619,7 +626,7 @@ var keepPlayingMutex: MediaController? = null val trackingVideos = mutableListOf() @Stable -class VisibilityData() { +class VisibilityData { var distanceToCenter: Float? = null } @@ -645,8 +652,7 @@ fun VideoPlayerActiveMutex( val myModifier = remember(videoUri) { - Modifier.fillMaxWidth().defaultMinSize(minHeight = 70.dp).onVisiblePositionChanges { - distanceToCenter -> + Modifier.fillMaxWidth().defaultMinSize(minHeight = 70.dp).onVisiblePositionChanges { distanceToCenter -> myCache.distanceToCenter = distanceToCenter if (distanceToCenter != null) { @@ -815,8 +821,7 @@ private fun pollCurrentDuration(controller: MediaController) = emit(controller.currentPosition / controller.duration.toFloat()) delay(100) } - } - .conflate() + }.conflate() @Composable fun Waveform( @@ -1000,7 +1005,8 @@ private fun MuteButton( ) { Box(modifier = VolumeBottomIconSize) { Box( - Modifier.clip(CircleShape) + Modifier + .clip(CircleShape) .fillMaxSize(0.6f) .align(Alignment.Center) .background(MaterialTheme.colorScheme.background), @@ -1040,7 +1046,8 @@ private fun KeepPlayingButton( ) { Box(modifier = PinBottomIconSize) { Box( - Modifier.clip(CircleShape) + Modifier + .clip(CircleShape) .fillMaxSize(0.6f) .align(Alignment.Center) .background(MaterialTheme.colorScheme.background), @@ -1099,7 +1106,8 @@ fun AnimatedShareButton( fun ShareButton(innerAction: @Composable (MutableState, () -> Unit) -> Unit) { Box(modifier = PinBottomIconSize) { Box( - Modifier.clip(CircleShape) + Modifier + .clip(CircleShape) .fillMaxSize(0.6f) .align(Alignment.Center) .background(MaterialTheme.colorScheme.background), @@ -1116,7 +1124,7 @@ fun ShareButton(innerAction: @Composable (MutableState, () -> Unit) -> Icon( imageVector = Icons.Default.Share, modifier = Size20Modifier, - contentDescription = stringResource(R.string.share_or_save), + contentDescription = stringRes(R.string.share_or_save), ) innerAction(popupExpanded) { popupExpanded.value = false } @@ -1129,7 +1137,8 @@ fun ShareButton(innerAction: @Composable (MutableState, () -> Unit) -> fun SaveButton(onSaveClick: (localContext: Context) -> Unit) { Box(modifier = PinBottomIconSize) { Box( - Modifier.clip(CircleShape) + Modifier + .clip(CircleShape) .fillMaxSize(0.6f) .align(Alignment.Center) .background(MaterialTheme.colorScheme.background), @@ -1160,7 +1169,7 @@ fun SaveButton(onSaveClick: (localContext: Context) -> Unit) { Icon( imageVector = Icons.Default.Download, modifier = Size20Modifier, - contentDescription = stringResource(R.string.save_to_gallery), + contentDescription = stringRes(R.string.save_to_gallery), ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt index b721070230..a8346df302 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @@ -43,6 +42,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.hashtags.Lightning import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -67,7 +67,7 @@ fun ZapRaiserRequest( ) Text( - text = titleText ?: stringResource(R.string.zapraiser), + text = titleText ?: stringRes(R.string.zapraiser), fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = 10.dp), @@ -77,13 +77,13 @@ fun ZapRaiserRequest( HorizontalDivider(thickness = DividerThickness) Text( - text = stringResource(R.string.zapraiser_explainer), + text = stringRes(R.string.zapraiser_explainer), color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) OutlinedTextField( - label = { Text(text = stringResource(R.string.zapraiser_target_amount_in_sats)) }, + label = { Text(text = stringRes(R.string.zapraiser_target_amount_in_sats)) }, modifier = Modifier.fillMaxWidth(), value = if (newPostViewModel.zapRaiserAmount != null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt index 3025f957ef..d028c9ce8b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt @@ -31,7 +31,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -67,7 +66,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.net.toUri @@ -86,6 +84,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.ui.actions.ImageSaver import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -234,7 +233,12 @@ private fun DialogContent( exit = remember { fadeOut() }, ) { Row( - modifier = Modifier.padding(horizontal = Size15dp, vertical = Size10dp).statusBarsPadding().systemBarsPadding().fillMaxWidth(), + modifier = + Modifier + .padding(horizontal = Size15dp, vertical = Size10dp) + .statusBarsPadding() + .systemBarsPadding() + .fillMaxWidth(), horizontalArrangement = spacedBy(Size10dp), verticalAlignment = Alignment.CenterVertically, ) { @@ -245,7 +249,7 @@ private fun DialogContent( ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.back), + contentDescription = stringRes(R.string.back), ) } @@ -262,7 +266,7 @@ private fun DialogContent( Icon( imageVector = Icons.Default.Share, modifier = Size20Modifier, - contentDescription = stringResource(R.string.quick_action_share), + contentDescription = stringRes(R.string.quick_action_share), ) ShareImageAction(popupExpanded = popupExpanded, myContent, onDismiss = { popupExpanded.value = false }) @@ -294,7 +298,7 @@ private fun DialogContent( Icon( imageVector = Icons.Default.Download, modifier = Size20Modifier, - contentDescription = stringResource(R.string.save_to_gallery), + contentDescription = stringRes(R.string.save_to_gallery), ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 00f0f5651b..74b2295ca7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -28,7 +28,6 @@ import android.view.Window import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio @@ -56,7 +55,6 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -64,7 +62,6 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import androidx.compose.ui.window.DialogWindowProvider -import androidx.core.net.toFile import androidx.core.net.toUri import coil.annotation.ExperimentalCoilApi import coil.compose.AsyncImage @@ -89,6 +86,7 @@ import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon import com.vitorpamplona.amethyst.ui.note.HashCheckFailedIcon import com.vitorpamplona.amethyst.ui.note.HashCheckIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font17SP import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size24dp @@ -99,7 +97,7 @@ import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.encoders.toHexKey import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -109,21 +107,20 @@ import kotlinx.coroutines.withContext @Composable fun ZoomableContentView( content: BaseMediaContent, - images: ImmutableList = remember(content) { listOf(content).toImmutableList() }, + images: ImmutableList = remember(content) { persistentListOf(content) }, roundedCorner: Boolean, isFiniteHeight: Boolean, accountViewModel: AccountViewModel, ) { - // store the dialog open or close state var dialogOpen by remember(content) { mutableStateOf(false) } - val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true } - val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - when (content) { is MediaUrlImage -> SensitivityWarning(content.contentWarning != null, accountViewModel) { TwoSecondController(content) { controllerVisible -> + val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true } + val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() + UrlImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel) } } @@ -148,6 +145,9 @@ fun ZoomableContentView( } is MediaLocalImage -> TwoSecondController(content) { controllerVisible -> + val mainImageModifier = Modifier.fillMaxWidth().clickable { dialogOpen = true } + val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() + LocalImageView(content, mainImageModifier, loadedImageModifier, isFiniteHeight, controllerVisible, accountViewModel = accountViewModel) } is MediaLocalVideo -> @@ -315,15 +315,13 @@ fun UrlImageView( ) } - val contentScale = if (isFiniteHeight) ContentScale.Fit else ContentScale.FillWidth - val ratio = remember(content) { aspectRatio(content.dim) } if (showImage.value) { SubcomposeAsyncImage( model = content.url, contentDescription = content.description, - contentScale = contentScale, + contentScale = if (isFiniteHeight) ContentScale.Fit else ContentScale.FillWidth, modifier = mainImageModifier, ) { when (painter.state) { @@ -635,7 +633,7 @@ fun ShareImageAction( if (videoUri != null && !videoUri.startsWith("file")) { DropdownMenuItem( - text = { Text(stringResource(R.string.copy_url_to_clipboard)) }, + text = { Text(stringRes(R.string.copy_url_to_clipboard)) }, onClick = { clipboardManager.setText(AnnotatedString(videoUri)) onDismiss() @@ -645,7 +643,7 @@ fun ShareImageAction( postNostrUri?.let { DropdownMenuItem( - text = { Text(stringResource(R.string.copy_the_note_id_to_the_clipboard)) }, + text = { Text(stringRes(R.string.copy_the_note_id_to_the_clipboard)) }, onClick = { clipboardManager.setText(AnnotatedString(it)) onDismiss() @@ -677,7 +675,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) { openDialogMsg.value?.let { InformationDialog( - title = localContext.getString(R.string.hash_verification_info_title), + title = stringRes(localContext, R.string.hash_verification_info_title), textContent = it, ) { openDialogMsg.value = null @@ -688,7 +686,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) { IconButton( modifier = hashVerifierMark, onClick = { - openDialogMsg.value = localContext.getString(R.string.hash_verification_passed) + openDialogMsg.value = stringRes(localContext, R.string.hash_verification_passed) }, ) { HashCheckIcon(Size30dp) @@ -697,7 +695,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) { IconButton( modifier = hashVerifierMark, onClick = { - openDialogMsg.value = localContext.getString(R.string.hash_verification_failed) + openDialogMsg.value = stringRes(localContext, R.string.hash_verification_failed) }, ) { HashCheckFailedIcon(Size30dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/ChatHeaderLayout.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/ChatHeaderLayout.kt index c0db96fd8d..a7d4f5566a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/ChatHeaderLayout.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/ChatHeaderLayout.kt @@ -37,11 +37,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.NewItemsBubble import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ChatHeadlineBorders import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -57,7 +57,7 @@ fun ChannelNamePreview() { channelPicture = { Image( painter = painterResource(R.drawable.github), - contentDescription = stringResource(id = R.string.profile_banner), + contentDescription = stringRes(id = R.string.profile_banner), contentScale = ContentScale.FillWidth, ) }, @@ -90,7 +90,7 @@ fun ChannelNamePreview() { leadingContent = { Image( painter = painterResource(R.drawable.github), - contentDescription = stringResource(id = R.string.profile_banner), + contentDescription = stringRes(id = R.string.profile_banner), contentScale = ContentScale.FillWidth, modifier = Size55Modifier, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/LeftPictureLayout.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/LeftPictureLayout.kt index 03c64c9a3a..d9b23f2ae9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/LeftPictureLayout.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/LeftPictureLayout.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale 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 import androidx.compose.ui.tooling.preview.Preview @@ -46,6 +45,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.LikeIcon import com.vitorpamplona.amethyst.ui.note.TextCount import com.vitorpamplona.amethyst.ui.note.ZappedIcon +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size16Modifier @@ -66,7 +66,7 @@ fun LeftPictureLayoutPreviewCard() { onImage = { Image( painter = painterResource(R.drawable.github), - contentDescription = stringResource(id = R.string.profile_banner), + contentDescription = stringRes(id = R.string.profile_banner), contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxSize().clip(QuoteBorder), ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt index 4539b0d8f4..bf570842d8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/layouts/RepostLayout.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.ui.note.RepostedIcon +import com.vitorpamplona.amethyst.ui.note.RepostIcon import com.vitorpamplona.amethyst.ui.theme.Size18Modifier import com.vitorpamplona.amethyst.ui.theme.Size35Modifier import com.vitorpamplona.amethyst.ui.theme.Size55Modifier @@ -55,7 +55,7 @@ fun GenericRepostLayout( Box( remember { Size18Modifier.align(Alignment.BottomStart).padding(1.dp) }, ) { - RepostedIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText) + RepostIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText) } Box( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index 4114a82091..7df7d06188 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -55,7 +55,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -64,6 +63,7 @@ import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji @@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size55dp @@ -100,7 +101,7 @@ fun AccountSwitchBottomSheet( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { - Text(stringResource(R.string.account_switch_select_account), fontWeight = FontWeight.Bold) + Text(stringRes(R.string.account_switch_select_account), fontWeight = FontWeight.Bold) } accounts.forEach { acc -> DisplayAccount(acc, accountViewModel, accountStateViewModel) } Row( @@ -112,7 +113,7 @@ fun AccountSwitchBottomSheet( verticalAlignment = Alignment.CenterVertically, ) { TextButton(onClick = { popupExpanded = true }) { - Text(stringResource(R.string.account_switch_add_account_btn)) + Text(stringRes(R.string.account_switch_add_account_btn)) } } } @@ -127,7 +128,7 @@ fun AccountSwitchBottomSheet( LoginOrSignupScreen(accountStateViewModel, isFirstLogin = false) TopAppBar( title = { - Text(text = stringResource(R.string.account_switch_add_account_dialog_title)) + Text(text = stringRes(R.string.account_switch_add_account_dialog_title)) }, navigationIcon = { IconButton(onClick = { popupExpanded = false }) { ArrowBackIcon() } @@ -191,12 +192,11 @@ fun DisplayAccount( .width(55.dp) .padding(0.dp), ) { - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - - AccountPicture(it, automaticallyShowProfilePicture) + AccountPicture( + it, + accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) } Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { AccountName(acc, it) } @@ -222,7 +222,7 @@ private fun ActiveMarker( if (isCurrentUser) { Icon( imageVector = Icons.Default.RadioButtonChecked, - contentDescription = stringResource(R.string.account_switch_active_account), + contentDescription = stringRes(R.string.account_switch_active_account), tint = MaterialTheme.colorScheme.secondary, ) } @@ -232,15 +232,17 @@ private fun ActiveMarker( private fun AccountPicture( user: User, loadProfilePicture: Boolean, + loadRobohash: Boolean, ) { val profilePicture by user.live().profilePictureChanges.observeAsState() RobohashFallbackAsyncImage( robot = user.pubkeyHex, model = profilePicture, - contentDescription = stringResource(R.string.profile_image), + contentDescription = stringRes(R.string.profile_image), modifier = AccountPictureModifier, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } @@ -275,8 +277,8 @@ private fun LogoutButton( var logoutDialog by remember { mutableStateOf(false) } if (logoutDialog) { AlertDialog( - title = { Text(text = stringResource(R.string.log_out)) }, - text = { Text(text = stringResource(R.string.are_you_sure_you_want_to_log_out)) }, + title = { Text(text = stringRes(R.string.log_out)) }, + text = { Text(text = stringRes(R.string.are_you_sure_you_want_to_log_out)) }, onDismissRequest = { logoutDialog = false }, confirmButton = { TextButton( @@ -285,14 +287,14 @@ private fun LogoutButton( accountStateViewModel.logOff(acc) }, ) { - Text(text = stringResource(R.string.log_out)) + Text(text = stringRes(R.string.log_out)) } }, dismissButton = { TextButton( onClick = { logoutDialog = false }, ) { - Text(text = stringResource(R.string.cancel)) + Text(text = stringRes(R.string.cancel)) } }, ) @@ -303,7 +305,7 @@ private fun LogoutButton( ) { Icon( imageVector = Icons.Default.Logout, - contentDescription = stringResource(R.string.log_out), + contentDescription = stringRes(R.string.log_out), tint = MaterialTheme.colorScheme.onSurface, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt index e3dd7225fd..c67ca00e0a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt @@ -35,7 +35,6 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -44,10 +43,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavBackStackEntry import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size0dp @@ -146,12 +145,16 @@ private fun RowScope.HasNewItemsIcon( navEntryState: State, nav: (Route, Boolean) -> Unit, ) { - val selected by - remember(navEntryState.value) { - derivedStateOf { navEntryState.value?.destination?.route?.substringBefore("?") == route.base } - } + val selected = + ( + navEntryState.value + ?.destination + ?.route + ?.indexOf(route.base) ?: -1 + ) > -1 NavigationBarItem( + alwaysShowLabel = false, icon = { NotifiableIcon( selected, @@ -173,7 +176,7 @@ private fun NotifiableIcon( Box(route.notifSize) { Icon( painter = painterResource(id = route.icon), - contentDescription = stringResource(route.contentDescriptor), + contentDescription = stringRes(route.contentDescriptor), modifier = route.iconSize, tint = if (selected) MaterialTheme.colorScheme.primary else Color.Unspecified, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 9fca1d6ff0..f8faac59ea 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -41,7 +41,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material3.DrawerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -58,15 +57,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue 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.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -81,6 +77,7 @@ import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache @@ -130,6 +127,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.RoomNameOnlyDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShortChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.observeAppDefinition +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -151,16 +149,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combineTransform -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest -import kotlinx.coroutines.launch @Composable fun AppTopBar( followLists: FollowListViewModel, navEntryState: State, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, navPopBack: () -> Unit, @@ -180,7 +176,7 @@ fun AppTopBar( derivedStateOf { navEntryState.value?.arguments?.getString("id") } } - RenderTopRouteBar(currentRoute, id, followLists, drawerState, accountViewModel, nav, navPopBack) + RenderTopRouteBar(currentRoute, id, followLists, openDrawer, accountViewModel, nav, navPopBack) } @Composable @@ -188,19 +184,19 @@ private fun RenderTopRouteBar( currentRoute: String?, id: String?, followLists: FollowListViewModel, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, navPopBack: () -> Unit, ) { when (currentRoute) { - Route.Home.base -> HomeTopBar(followLists, drawerState, accountViewModel, nav) - Route.Video.base -> StoriesTopBar(followLists, drawerState, accountViewModel, nav) - Route.Discover.base -> DiscoveryTopBar(followLists, drawerState, accountViewModel, nav) - Route.Notification.base -> NotificationTopBar(followLists, drawerState, accountViewModel, nav) - Route.Settings.base -> TopBarWithBackButton(stringResource(id = R.string.application_preferences), navPopBack) - Route.Bookmarks.base -> TopBarWithBackButton(stringResource(id = R.string.bookmarks), navPopBack) - Route.Drafts.base -> TopBarWithBackButton(stringResource(id = R.string.drafts), navPopBack) + Route.Home.base -> HomeTopBar(followLists, openDrawer, accountViewModel, nav) + Route.Video.base -> StoriesTopBar(followLists, openDrawer, accountViewModel, nav) + Route.Discover.base -> DiscoveryTopBar(followLists, openDrawer, accountViewModel, nav) + Route.Notification.base -> NotificationTopBar(followLists, openDrawer, accountViewModel, nav) + Route.Settings.base -> TopBarWithBackButton(stringRes(id = R.string.application_preferences), navPopBack) + Route.Bookmarks.base -> TopBarWithBackButton(stringRes(id = R.string.bookmarks), navPopBack) + Route.Drafts.base -> TopBarWithBackButton(stringRes(id = R.string.drafts), navPopBack) else -> { if (id != null) { @@ -213,10 +209,10 @@ private fun RenderTopRouteBar( Route.Geohash.base -> GeoHashTopBar(id, accountViewModel, navPopBack) Route.Note.base -> ThreadTopBar(id, accountViewModel, navPopBack) Route.ContentDiscovery.base -> DvmTopBar(id, accountViewModel, nav, navPopBack) - else -> MainTopBar(drawerState, accountViewModel, nav) + else -> MainTopBar(openDrawer, accountViewModel, nav) } } else { - MainTopBar(drawerState, accountViewModel, nav) + MainTopBar(openDrawer, accountViewModel, nav) } } } @@ -229,7 +225,7 @@ private fun ThreadTopBar( navPopBack: () -> Unit, ) { FlexibleTopBarWithBackButton( - title = { Text(stringResource(id = R.string.thread_title)) }, + title = { Text(stringRes(id = R.string.thread_title)) }, popBack = navPopBack, ) } @@ -381,7 +377,7 @@ private fun RenderRoomTopBar( Spacer(modifier = DoubleHorzSpacer) - UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal) + UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal, accountViewModel = accountViewModel) } } }, @@ -413,7 +409,7 @@ private fun RenderRoomTopBar( .padding(start = 10.dp) .weight(1f), fontWeight = FontWeight.Normal, - accountViewModel.userProfile(), + accountViewModel, ) }, extendableRow = { @@ -452,11 +448,11 @@ private fun ChannelTopBar( @Composable fun StoriesTopBar( followLists: FollowListViewModel, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - GenericMainTopBar(drawerState, accountViewModel, nav) { + GenericMainTopBar(openDrawer, accountViewModel, nav) { val list by accountViewModel.account.defaultStoriesFollowList.collectAsStateWithLifecycle() FollowListWithRoutes( @@ -471,11 +467,11 @@ fun StoriesTopBar( @Composable fun HomeTopBar( followLists: FollowListViewModel, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - GenericMainTopBar(drawerState, accountViewModel, nav) { + GenericMainTopBar(openDrawer, accountViewModel, nav) { val list by accountViewModel.account.defaultHomeFollowList.collectAsStateWithLifecycle() FollowListWithRoutes( @@ -494,11 +490,11 @@ fun HomeTopBar( @Composable fun NotificationTopBar( followLists: FollowListViewModel, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - GenericMainTopBar(drawerState, accountViewModel, nav) { + GenericMainTopBar(openDrawer, accountViewModel, nav) { val list by accountViewModel.account.defaultNotificationFollowList.collectAsStateWithLifecycle() FollowListWithoutRoutes( @@ -513,11 +509,11 @@ fun NotificationTopBar( @Composable fun DiscoveryTopBar( followLists: FollowListViewModel, - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - GenericMainTopBar(drawerState, accountViewModel, nav) { + GenericMainTopBar(openDrawer, accountViewModel, nav) { val list by accountViewModel.account.defaultDiscoveryFollowList.collectAsStateWithLifecycle() FollowListWithoutRoutes( @@ -531,17 +527,17 @@ fun DiscoveryTopBar( @Composable fun MainTopBar( - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - GenericMainTopBar(drawerState, accountViewModel, nav) { AmethystClickableIcon() } + GenericMainTopBar(openDrawer, accountViewModel, nav) { AmethystClickableIcon() } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun GenericMainTopBar( - drawerState: DrawerState, + openDrawer: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit, content: @Composable () -> Unit, @@ -558,8 +554,7 @@ fun GenericMainTopBar( } }, navigationIcon = { - val coroutineScope = rememberCoroutineScope() - LoggedInUserPictureDrawer(accountViewModel) { coroutineScope.launch { drawerState.open() } } + LoggedInUserPictureDrawer(accountViewModel, openDrawer) }, actions = { SearchButton { nav(Route.Search.route) } }, ) @@ -572,7 +567,7 @@ private fun SearchButton(onClick: () -> Unit) { IconButton( onClick = onClick, ) { - SearchIcon(modifier = Size22Modifier, Color.Unspecified) + SearchIcon(modifier = Size22Modifier, MaterialTheme.colorScheme.placeholderText) } } @@ -592,10 +587,11 @@ private fun LoggedInUserPictureDrawer( RobohashFallbackAsyncImage( robot = accountViewModel.userProfile().pubkeyHex, model = profilePicture, - contentDescription = stringResource(id = R.string.your_profile_image), + contentDescription = stringRes(id = R.string.your_profile_image), modifier = HeaderPictureModifier, contentScale = ContentScale.Crop, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } } @@ -659,7 +655,7 @@ class ResourceName( ) : Name() { override fun name() = " $resourceId " // Space to make sure it goes first - override fun name(context: Context) = context.getString(resourceId) + override fun name(context: Context) = stringRes(context, resourceId) } class PeopleListName( @@ -800,7 +796,7 @@ fun SimpleTextSpinner( val context = LocalContext.current val selectAnOption = - stringResource( + stringRes( id = R.string.select_an_option, ) @@ -882,7 +878,7 @@ fun RenderOption(option: Name) { modifier = Modifier.fillMaxWidth(), ) { Text( - text = stringResource(id = option.resourceId), + text = stringRes(id = option.resourceId), color = MaterialTheme.colorScheme.onSurface, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 8eb7f42ef2..4202294fc8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.navigation import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -44,7 +43,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Send import androidx.compose.material3.AlertDialog import androidx.compose.material3.DrawerState import androidx.compose.material3.HorizontalDivider @@ -72,7 +70,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight @@ -86,6 +83,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relays.RelayPool import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus @@ -98,6 +96,7 @@ import com.vitorpamplona.amethyst.ui.qrcode.ShowQRDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.Font18SP @@ -131,11 +130,6 @@ fun DrawerContent( Unit } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - ModalDrawerSheet( drawerContainerColor = MaterialTheme.colorScheme.background, drawerTonalElevation = 0.dp, @@ -174,7 +168,8 @@ fun DrawerContent( BottomContent( accountViewModel.account.userProfile(), drawerState, - automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, nav, ) } @@ -217,14 +212,14 @@ fun ProfileContentTemplate( if (profileBanner != null) { AsyncImage( model = profileBanner, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), contentScale = ContentScale.FillWidth, modifier = bannerModifier, ) } else { Image( painter = painterResource(R.drawable.profile_banner), - contentDescription = stringResource(R.string.profile_banner), + contentDescription = stringRes(R.string.profile_banner), contentScale = ContentScale.FillWidth, modifier = bannerModifier, ) @@ -234,7 +229,7 @@ fun ProfileContentTemplate( RobohashFallbackAsyncImage( robot = profilePubHex, model = profilePicture, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), modifier = Modifier .width(100.dp) @@ -243,6 +238,7 @@ fun ProfileContentTemplate( .border(3.dp, MaterialTheme.colorScheme.background, CircleShape) .clickable(onClick = onClick), loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) if (bestDisplayName != null) { @@ -295,11 +291,11 @@ fun StatusEditBar( OutlinedTextField( value = currentStatus.value, onValueChange = { currentStatus.value = it }, - label = { Text(text = stringResource(R.string.status_update)) }, + label = { Text(text = stringRes(R.string.status_update)) }, modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.status_update), + text = stringRes(R.string.status_update), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -401,7 +397,7 @@ private fun FollowingAndFollowerCounts( fontWeight = FontWeight.Bold, ) - Text(stringResource(R.string.following)) + Text(stringRes(R.string.following)) Spacer(modifier = DoubleHorzSpacer) @@ -410,7 +406,7 @@ private fun FollowingAndFollowerCounts( fontWeight = FontWeight.Bold, ) - Text(stringResource(R.string.followers)) + Text(stringRes(R.string.followers)) } } @@ -464,7 +460,7 @@ fun ListContent( Column(modifier) { NavigationRow( - title = stringResource(R.string.profile), + title = stringRes(R.string.profile), icon = Route.Profile.icon, tint = MaterialTheme.colorScheme.primary, nav = nav, @@ -473,7 +469,7 @@ fun ListContent( ) NavigationRow( - title = stringResource(R.string.bookmarks), + title = stringRes(R.string.bookmarks), icon = Route.Bookmarks.icon, tint = MaterialTheme.colorScheme.onBackground, nav = nav, @@ -482,7 +478,7 @@ fun ListContent( ) NavigationRow( - title = stringResource(R.string.drafts), + title = stringRes(R.string.drafts), icon = Route.Drafts.icon, tint = MaterialTheme.colorScheme.onBackground, nav = nav, @@ -499,7 +495,7 @@ fun ListContent( ) NavigationRow( - title = stringResource(R.string.security_filters), + title = stringRes(R.string.security_filters), icon = Route.BlockedUsers.icon, tint = MaterialTheme.colorScheme.onBackground, nav = nav, @@ -509,7 +505,7 @@ fun ListContent( accountViewModel.account.keyPair.privKey?.let { IconRow( - title = stringResource(R.string.backup_keys), + title = stringRes(R.string.backup_keys), icon = R.drawable.ic_key, tint = MaterialTheme.colorScheme.onBackground, onClick = { @@ -522,9 +518,9 @@ fun ListContent( IconRow( title = if (checked) { - stringResource(R.string.disconnect_from_your_orbot_setup) + stringRes(R.string.disconnect_from_your_orbot_setup) } else { - stringResource(R.string.connect_via_tor_short) + stringRes(R.string.connect_via_tor_short) }, icon = R.drawable.ic_tor, tint = MaterialTheme.colorScheme.onBackground, @@ -543,7 +539,7 @@ fun ListContent( ) NavigationRow( - title = stringResource(R.string.settings), + title = stringRes(R.string.settings), icon = Route.Settings.icon, tint = MaterialTheme.colorScheme.onBackground, nav = nav, @@ -554,7 +550,7 @@ fun ListContent( Spacer(modifier = Modifier.weight(1f)) IconRow( - title = stringResource(R.string.drawer_accounts), + title = stringRes(R.string.drawer_accounts), icon = R.drawable.manage_accounts, tint = MaterialTheme.colorScheme.onBackground, onClick = openSheet, @@ -578,7 +574,7 @@ fun ListContent( }, onError = { accountViewModel.toast( - context.getString(R.string.could_not_connect_to_tor), + stringRes(context, R.string.could_not_connect_to_tor), it, ) }, @@ -588,8 +584,8 @@ fun ListContent( if (disconnectTorDialog) { AlertDialog( - title = { Text(text = stringResource(R.string.do_you_really_want_to_disable_tor_title)) }, - text = { Text(text = stringResource(R.string.do_you_really_want_to_disable_tor_text)) }, + title = { Text(text = stringRes(R.string.do_you_really_want_to_disable_tor_title)) }, + text = { Text(text = stringRes(R.string.do_you_really_want_to_disable_tor_text)) }, onDismissRequest = { disconnectTorDialog = false }, confirmButton = { TextButton( @@ -599,14 +595,14 @@ fun ListContent( accountViewModel.enableTor(false, proxyPort) }, ) { - Text(text = stringResource(R.string.yes)) + Text(text = stringRes(R.string.yes)) } }, dismissButton = { TextButton( onClick = { disconnectTorDialog = false }, ) { - Text(text = stringResource(R.string.no)) + Text(text = stringRes(R.string.no)) } }, ) @@ -730,7 +726,7 @@ fun IconRowRelays( Text( modifier = Modifier.padding(start = 16.dp), - text = stringResource(id = R.string.relay_setup), + text = stringRes(id = R.string.relay_setup), fontSize = 18.sp, ) @@ -746,6 +742,7 @@ fun BottomContent( user: User, drawerState: DrawerState, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, ) { val coroutineScope = rememberCoroutineScope() @@ -792,7 +789,7 @@ fun BottomContent( ) { Icon( painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringResource(id = R.string.show_npub_as_a_qr_code), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -804,6 +801,7 @@ fun BottomContent( ShowQRDialog( user, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, onScan = { dialogOpen = false coroutineScope.launch { drawerState.close() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 5f6361890e..6097177a2a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -24,7 +24,6 @@ import android.os.Bundle import androidx.compose.foundation.layout.size import androidx.compose.runtime.Immutable import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.NamedNavArgument import androidx.navigation.NavBackStackEntry @@ -77,8 +76,7 @@ sealed class Route( nullable = true defaultValue = null }, - ) - .toImmutableList(), + ).toImmutableList(), contentDescriptor = R.string.route_home, hasNewItems = { accountViewModel, newNotes -> HomeLatestItem.hasNewItems(accountViewModel, newNotes) @@ -95,7 +93,7 @@ sealed class Route( object Search : Route( route = "Search", - icon = R.drawable.ic_search, + icon = R.drawable.ic_moments, contentDescriptor = R.string.route_search, ) @@ -212,8 +210,7 @@ sealed class Route( nullable = true defaultValue = null }, - ) - .toImmutableList(), + ).toImmutableList(), ) object RoomByAuthor : @@ -256,9 +253,7 @@ sealed class Route( open class LatestItem { var newestItemPerAccount: Map = mapOf() - fun getNewestItem(account: Account): Note? { - return newestItemPerAccount[account.userProfile().pubkeyHex] - } + fun getNewestItem(account: Account): Note? = newestItemPerAccount[account.userProfile().pubkeyHex] fun clearNewestItem(account: Account) { val userHex = account.userProfile().pubkeyHex @@ -279,9 +274,10 @@ open class LatestItem { if (newestItem == null || !account.isAcceptable(newestItem)) { filterMore(filter.feed(), account).firstOrNull { it.createdAt() != null && account.isAcceptable(it) } } else { - filter.sort( - filterMore(filter.applyFilter(newNotes), account) + newestItem, - ).firstOrNull { it.createdAt() != null && account.isAcceptable(it) } + filter + .sort( + filterMore(filter.applyFilter(newNotes), account) + newestItem, + ).firstOrNull { it.createdAt() != null && account.isAcceptable(it) } } newestItemPerAccount = newestItemPerAccount + Pair(account.userProfile().pubkeyHex, newNewest) @@ -292,16 +288,12 @@ open class LatestItem { open fun filterMore( newItems: Set, account: Account, - ): Set { - return newItems - } + ): Set = newItems open fun filterMore( newItems: List, account: Account, - ): List { - return newItems - } + ): List = newItems } object HomeLatestItem : LatestItem() { @@ -382,16 +374,12 @@ object MessagesLatestItem : LatestItem() { override fun filterMore( newItems: Set, account: Account, - ): Set { - return newItems.filter { isNew(it, account) }.toSet() - } + ): Set = newItems.filter { isNew(it, account) }.toSet() override fun filterMore( newItems: List, account: Account, - ): List { - return newItems.filter { isNew(it, account) } - } + ): List = newItems.filter { isNew(it, account) } } fun getRouteWithArguments(navController: NavHostController): String? { @@ -399,9 +387,7 @@ fun getRouteWithArguments(navController: NavHostController): String? { return getRouteWithArguments(currentEntry.destination, currentEntry.arguments) } -fun getRouteWithArguments(navState: State): String? { - return navState.value?.let { getRouteWithArguments(it.destination, it.arguments) } -} +fun getRouteWithArguments(navState: State): String? = navState.value?.let { getRouteWithArguments(it.destination, it.arguments) } private fun getRouteWithArguments( destination: NavDestination, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt index 05824a15a7..08639b0acc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.note import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -31,32 +31,25 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MilitaryTech -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.navigation.routeFor -import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu +import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.ui.screen.BadgeCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.theme.Size15Modifier +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText -import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable @@ -67,16 +60,14 @@ fun BadgeCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val noteState by likeSetCard.note.live().metadata.observeAsState() + val noteState by likeSetCard.note + .live() + .metadata + .observeAsState() val note = noteState?.note val context = LocalContext.current.applicationContext - val popupExpanded = remember { mutableStateOf(false) } - val enablePopup = remember { { popupExpanded.value = true } } - - val scope = rememberCoroutineScope() - if (note == null) { BlankNote(Modifier) } else { @@ -89,18 +80,15 @@ fun BadgeCompose( Column( modifier = - Modifier.background(backgroundColor.value) - .combinedClickable( + Modifier + .background(backgroundColor.value) + .clickable( onClick = { - scope.launch { - routeFor( - note, - accountViewModel.userProfile(), - ) - ?.let { nav(it) } - } + routeFor( + note, + accountViewModel.userProfile(), + )?.let { nav(it) } }, - onLongClick = enablePopup, ), ) { Row( @@ -128,7 +116,7 @@ fun BadgeCompose( Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { Row { Text( - stringResource(R.string.new_badge_award_notif), + stringRes(R.string.new_badge_award_notif), fontWeight = FontWeight.Bold, modifier = Modifier.padding(bottom = 5.dp).weight(1f), ) @@ -139,19 +127,7 @@ fun BadgeCompose( maxLines = 1, ) - IconButton( - modifier = Modifier.then(Modifier.size(24.dp)), - onClick = enablePopup, - ) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(id = R.string.more_options), - modifier = Size15Modifier, - tint = MaterialTheme.colorScheme.placeholderText, - ) - - NoteDropDownMenu(note, popupExpanded, null, accountViewModel, nav) - } + MoreOptionsButton(note, null, accountViewModel, nav) } note.replyTo?.firstOrNull()?.let { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index ea3716edd0..75af3272ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -42,6 +41,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.Size35dp @@ -75,7 +75,7 @@ fun BlankNote( horizontalArrangement = Arrangement.Center, ) { Text( - text = stringResource(R.string.post_not_found) + if (idHex != null) ": $idHex" else "", + text = stringRes(R.string.post_not_found) + if (idHex != null) ": $idHex" else "", modifier = Modifier.padding(30.dp), color = Color.Gray, textAlign = TextAlign.Center, @@ -125,7 +125,7 @@ fun HiddenNote( modifier = Modifier.padding(30.dp), ) { Text( - text = stringResource(R.string.post_was_flagged_as_inappropriate_by), + text = stringRes(R.string.post_was_flagged_as_inappropriate_by), color = Color.Gray, ) FlowRow(modifier = Modifier.padding(top = 10.dp)) { @@ -157,7 +157,7 @@ fun HiddenNote( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.show_anyway), color = Color.White) + Text(text = stringRes(R.string.show_anyway), color = Color.White) } } } @@ -188,7 +188,7 @@ fun HiddenNoteByMe( modifier = Modifier.padding(30.dp), ) { Text( - text = stringResource(R.string.post_was_hidden), + text = stringRes(R.string.post_was_hidden), color = Color.Gray, textAlign = TextAlign.Center, ) @@ -203,7 +203,7 @@ fun HiddenNoteByMe( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.show_anyway), color = Color.White) + Text(text = stringRes(R.string.show_anyway), color = Color.White) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlockReportChecker.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlockReportChecker.kt index 4a89b303c8..69343f910e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlockReportChecker.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlockReportChecker.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Note diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index bf2a687782..986f2fd6ca 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -68,6 +67,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.ParticipantListBuilder import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout import com.vitorpamplona.amethyst.ui.note.elements.BannerImage @@ -472,7 +472,7 @@ fun RenderLiveActivityThumb( } ?: run { DisplayAuthorBanner(baseNote) } Box(Modifier.padding(10.dp)) { - Crossfade(targetState = card.status, label = "RenderLiveActivityThumb") { + CrossfadeIfEnabled(targetState = card.status, label = "RenderLiveActivityThumb", accountViewModel = accountViewModel) { when (it) { STATUS_LIVE -> { val url = card.media diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt index 61501fbd6b..af245f9ec4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -46,7 +45,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight @@ -61,12 +59,15 @@ import androidx.lifecycle.map import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.layouts.ChatHeaderLayout import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.grayText @@ -136,7 +137,7 @@ private fun ChatroomPrivateMessages( } } - Crossfade(userRoom, label = "ChatroomPrivateMessages") { room -> + CrossfadeIfEnabled(targetState = userRoom, label = "ChatroomPrivateMessages", accountViewModel = accountViewModel) { room -> if (room != null) { UserRoomCompose(baseNote, room, accountViewModel, nav) } else { @@ -182,20 +183,15 @@ private fun ChannelRoomCompose( val description = if (noteEvent is ChannelCreateEvent) { - stringResource(R.string.channel_created) + stringRes(R.string.channel_created) } else if (noteEvent is ChannelMetadataEvent) { - "${stringResource(R.string.channel_information_changed_to)} " + "${stringRes(R.string.channel_information_changed_to)} " } else { noteEvent?.content() } val hasNewMessages = remember { mutableStateOf(false) } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages -> if (hasNewMessages.value != newHasNewMessages) { hasNewMessages.value = newHasNewMessages @@ -209,7 +205,8 @@ private fun ChannelRoomCompose( channelLastTime = remember(note) { note.createdAt() }, channelLastContent = remember(note, authorState) { "$authorName: $description" }, hasNewMessages = hasNewMessages, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onClick = { nav(route) }, ) } @@ -219,7 +216,7 @@ private fun ChannelTitleWithLabelInfo( channelName: String, modifier: Modifier, ) { - val label = stringResource(id = R.string.public_chat) + val label = stringRes(id = R.string.public_chat) val placeHolderColor = MaterialTheme.colorScheme.placeholderText val channelNameAndBoostInfo = remember(channelName) { @@ -305,7 +302,7 @@ fun RoomNameDisplay( .distinctUntilChanged() .observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject) - Crossfade(targetState = roomSubject, modifier, label = "RoomNameDisplay") { + CrossfadeIfEnabled(targetState = roomSubject, modifier, label = "RoomNameDisplay", accountViewModel = accountViewModel) { if (!it.isNullOrBlank()) { if (room.users.size > 1) { DisplayRoomSubject(it) @@ -337,7 +334,7 @@ private fun DisplayUserAndSubject( maxLines = 1, ) LoadUser(baseUserHex = user, accountViewModel = accountViewModel) { - it?.let { UsernameDisplay(it, Modifier.weight(1f)) } + it?.let { UsernameDisplay(it, Modifier.weight(1f), accountViewModel = accountViewModel) } } } } @@ -354,14 +351,14 @@ fun DisplayUserSetAsSubject( // Regular Design Row { LoadUser(baseUserHex = userList[0], accountViewModel) { - it?.let { UsernameDisplay(it, Modifier.weight(1f), fontWeight = fontWeight) } + it?.let { UsernameDisplay(it, Modifier.weight(1f), fontWeight = fontWeight, accountViewModel = accountViewModel) } } } } else { Row { userList.take(4).forEachIndexedExtended { index, isFirst, isLast, value -> LoadUser(baseUserHex = value, accountViewModel) { - it?.let { ShortUsernameDisplay(baseUser = it, fontWeight = fontWeight) } + it?.let { ShortUsernameDisplay(baseUser = it, fontWeight = fontWeight, accountViewModel = accountViewModel) } } if (!isLast) { @@ -395,6 +392,7 @@ fun ShortUsernameDisplay( baseUser: User, weight: Modifier = Modifier, fontWeight: FontWeight = FontWeight.Bold, + accountViewModel: AccountViewModel, ) { val userName by baseUser @@ -404,7 +402,7 @@ fun ShortUsernameDisplay( .distinctUntilChanged() .observeAsState(baseUser.toBestShortFirstName()) - Crossfade(targetState = userName, modifier = weight) { + CrossfadeIfEnabled(targetState = userName, modifier = weight, accountViewModel = accountViewModel) { CreateTextWithEmoji( text = it, tags = baseUser.info?.tags, @@ -459,6 +457,7 @@ fun ChannelName( channelLastContent: String?, hasNewMessages: MutableState, loadProfilePicture: Boolean, + loadRobohash: Boolean, onClick: () -> Unit, ) { ChannelName( @@ -466,9 +465,10 @@ fun ChannelName( RobohashFallbackAsyncImage( robot = channelIdHex, model = channelPicture, - contentDescription = stringResource(R.string.channel_image), + contentDescription = stringRes(R.string.channel_image), modifier = AccountPictureModifier, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) }, channelTitle, @@ -506,7 +506,7 @@ fun ChannelName( ) } else { Text( - stringResource(R.string.referenced_event_not_found), + stringRes(R.string.referenced_event_not_found), color = MaterialTheme.colorScheme.grayText, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 9f0ffe7e9d..fc41eb0222 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -46,7 +46,6 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -54,7 +53,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import com.vitorpamplona.amethyst.R @@ -65,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ChatBubbleMaxSizeModifier import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeMe import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeThem @@ -78,7 +77,7 @@ import com.vitorpamplona.amethyst.ui.theme.RowColSpacing import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size20dp -import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.amethyst.ui.theme.Size5Modifier import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdTopPadding import com.vitorpamplona.amethyst.ui.theme.chatAuthorBox @@ -173,7 +172,7 @@ fun NormalChatNote( ChatBubbleLayout( isLoggedInUser = isLoggedInUser, innerQuote = innerQuote, - isSimplified = accountViewModel.settings.featureSet == FeatureSetType.SIMPLIFIED, + isComplete = accountViewModel.settings.featureSet == FeatureSetType.COMPLETE, hasDetailsToShow = note.zaps.isNotEmpty() || note.zapPayments.isNotEmpty() || note.reactions.isNotEmpty(), drawAuthorInfo = drawAuthorInfo, parentBackgroundColor = parentBackgroundColor, @@ -249,7 +248,7 @@ fun NormalChatNote( fun ChatBubbleLayout( isLoggedInUser: Boolean, innerQuote: Boolean, - isSimplified: Boolean, + isComplete: Boolean, hasDetailsToShow: Boolean, drawAuthorInfo: Boolean, parentBackgroundColor: MutableState? = null, @@ -300,10 +299,10 @@ fun ChatBubbleLayout( val showDetails = remember { mutableStateOf( - if (isSimplified) { - hasDetailsToShow - } else { + if (isComplete) { true + } else { + hasDetailsToShow }, ) } @@ -313,7 +312,7 @@ fun ChatBubbleLayout( Modifier.combinedClickable( onClick = { if (!onClick()) { - if (isSimplified) { + if (!isComplete) { showDetails.value = !showDetails.value } } @@ -376,7 +375,7 @@ private fun BubblePreview() { ChatBubbleLayout( isLoggedInUser = false, innerQuote = false, - isSimplified = false, + isComplete = true, hasDetailsToShow = true, drawAuthorInfo = true, parentBackgroundColor = backgroundBubbleColor, @@ -406,7 +405,7 @@ private fun BubblePreview() { ChatBubbleLayout( isLoggedInUser = true, innerQuote = false, - isSimplified = false, + isComplete = true, hasDetailsToShow = true, drawAuthorInfo = true, parentBackgroundColor = backgroundBubbleColor, @@ -623,10 +622,8 @@ fun IncognitoBadge(baseNote: Note) { @Composable fun ChatTimeAgo(baseNote: Note) { - val nowStr = stringResource(id = R.string.now) - - val time by - remember(baseNote) { derivedStateOf { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) } } + val nowStr = stringRes(id = R.string.now) + val time = remember(baseNote) { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) } Text( text = time, @@ -668,7 +665,7 @@ private fun RenderRegularTextNote( } } else { TranslatableRichTextViewer( - content = stringResource(id = R.string.could_not_decrypt_the_message), + content = stringRes(id = R.string.could_not_decrypt_the_message), canPreview = true, quotesLeft = 0, modifier = HalfTopPadding, @@ -690,11 +687,11 @@ private fun RenderChangeChannelMetadataNote(note: Note) { val channelInfo = noteEvent.channelInfo() val text = note.author?.toBestDisplayName().toString() + - " ${stringResource(R.string.changed_chat_name_to)} '" + + " ${stringRes(R.string.changed_chat_name_to)} '" + (channelInfo.name ?: "") + - "', ${stringResource(R.string.description_to)} '" + + "', ${stringRes(R.string.description_to)} '" + (channelInfo.about ?: "") + - "', ${stringResource(R.string.and_picture_to)} '" + + "', ${stringRes(R.string.and_picture_to)} '" + (channelInfo.picture ?: "") + "'" @@ -711,11 +708,11 @@ private fun RenderCreateChannelNote(note: Note) { val text = note.author?.toBestDisplayName().toString() + - " ${stringResource(R.string.created)} " + + " ${stringRes(R.string.created)} " + (channelInfo.name ?: "") + - " ${stringResource(R.string.with_description_of)} '" + + " ${stringRes(R.string.with_description_of)} '" + (channelInfo.about ?: "") + - "', ${stringResource(R.string.and_picture)} '" + + "', ${stringRes(R.string.and_picture)} '" + (channelInfo.picture ?: "") + "'" @@ -769,7 +766,11 @@ private fun WatchAndDisplayUser( accountViewModel = accountViewModel, ) - ObserveAndDisplayFollowingMark(author.pubkeyHex, Size5dp, accountViewModel) + WatchUserFollows(author.pubkeyHex, accountViewModel) { newFollowingState -> + if (newFollowingState) { + FollowingIcon(Size5Modifier) + } + } }, name = { if (userState != null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt index 7796c6ad59..905f36d81f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt @@ -43,10 +43,8 @@ import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.PushPin import androidx.compose.material.icons.filled.Report -import androidx.compose.material.icons.materialIcon -import androidx.compose.material.icons.materialPath +import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.outlined.ArrowForwardIos -import androidx.compose.material.icons.outlined.BarChart import androidx.compose.material.icons.outlined.Bolt import androidx.compose.material.icons.outlined.PlayCircle import androidx.compose.material3.Icon @@ -56,9 +54,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -66,9 +62,18 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.Cashu import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons -import com.vitorpamplona.amethyst.commons.labels.Following +import com.vitorpamplona.amethyst.commons.icons.Following +import com.vitorpamplona.amethyst.commons.icons.Like +import com.vitorpamplona.amethyst.commons.icons.Liked +import com.vitorpamplona.amethyst.commons.icons.Reply +import com.vitorpamplona.amethyst.commons.icons.Repost +import com.vitorpamplona.amethyst.commons.icons.Reposted +import com.vitorpamplona.amethyst.commons.icons.Search +import com.vitorpamplona.amethyst.commons.icons.Zap +import com.vitorpamplona.amethyst.commons.icons.ZapSplit +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange -import com.vitorpamplona.amethyst.ui.theme.Size18Modifier +import com.vitorpamplona.amethyst.ui.theme.Size19Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size30Modifier import com.vitorpamplona.amethyst.ui.theme.grayText @@ -79,7 +84,7 @@ import com.vitorpamplona.amethyst.ui.theme.subtleButton fun AmethystIcon(iconSize: Dp) { Icon( imageVector = CustomHashTagIcons.Amethyst, - contentDescription = stringResource(id = R.string.app_logo), + contentDescription = stringRes(id = R.string.app_logo), modifier = Modifier.size(iconSize), tint = Color.Unspecified, ) @@ -87,10 +92,15 @@ fun AmethystIcon(iconSize: Dp) { @Composable fun FollowingIcon(iconSize: Dp) { + FollowingIcon(Modifier.size(iconSize)) +} + +@Composable +fun FollowingIcon(modifier: Modifier) { Icon( imageVector = Following, - contentDescription = stringResource(id = R.string.following), - modifier = Modifier.size(iconSize), + contentDescription = stringRes(id = R.string.following), + modifier = modifier, tint = Color.Unspecified, ) } @@ -99,7 +109,7 @@ fun FollowingIcon(iconSize: Dp) { fun ArrowBackIcon() { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(R.string.back), + contentDescription = stringRes(R.string.back), tint = MaterialTheme.colorScheme.grayText, ) } @@ -121,7 +131,7 @@ fun DownloadForOfflineIcon( ) { Icon( imageVector = Icons.Default.DownloadForOffline, - contentDescription = stringResource(id = R.string.accessibility_download_for_offline), + contentDescription = stringRes(id = R.string.accessibility_download_for_offline), modifier = remember(iconSize) { Modifier.size(iconSize) }, tint = tint, ) @@ -131,7 +141,7 @@ fun DownloadForOfflineIcon( fun HashCheckIcon(iconSize: Dp) { Icon( painter = painterResource(R.drawable.original), - contentDescription = stringResource(id = R.string.hash_verification_passed), + contentDescription = stringRes(id = R.string.hash_verification_passed), modifier = remember(iconSize) { Modifier.size(iconSize) }, tint = Color.Unspecified, ) @@ -141,19 +151,22 @@ fun HashCheckIcon(iconSize: Dp) { fun HashCheckFailedIcon(iconSize: Dp) { Icon( imageVector = Icons.Default.Report, - contentDescription = stringResource(id = R.string.hash_verification_failed), + contentDescription = stringRes(id = R.string.hash_verification_failed), modifier = remember(iconSize) { Modifier.size(iconSize) }, tint = Color.Red, ) } @Composable -fun LikedIcon(modifier: Modifier) { +fun LikedIcon( + modifier: Modifier, + tint: Color = Color.Unspecified, +) { Icon( - painter = painterResource(R.drawable.ic_liked), - null, + imageVector = Liked, + stringRes(id = R.string.like_description), modifier = modifier, - tint = Color.Unspecified, + tint = tint, ) } @@ -163,21 +176,34 @@ fun LikeIcon( grayTint: Color, ) { Icon( - painter = painterResource(R.drawable.ic_like), - contentDescription = stringResource(id = R.string.like_description), + imageVector = Like, + contentDescription = stringRes(id = R.string.like_description), modifier = iconSizeModifier, tint = grayTint, ) } +@Composable +fun RepostIcon( + modifier: Modifier, + tint: Color = Color.Unspecified, +) { + Icon( + imageVector = Repost, + contentDescription = stringRes(id = R.string.boost_or_quote_description), + modifier = modifier, + tint = tint, + ) +} + @Composable fun RepostedIcon( modifier: Modifier, tint: Color = Color.Unspecified, ) { Icon( - painter = painterResource(R.drawable.ic_retweeted), - contentDescription = stringResource(id = R.string.boost_or_quote_description), + imageVector = Reposted, + contentDescription = stringRes(id = R.string.boost_or_quote_description), modifier = modifier, tint = tint, ) @@ -190,7 +216,7 @@ fun LightningAddressIcon( ) { Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(R.string.lightning_address), + contentDescription = stringRes(R.string.lightning_address), tint = tint, modifier = modifier, ) @@ -213,8 +239,10 @@ fun ReactionRowIconPreview() { CommentIcon(Size20Modifier, Color.Unspecified) RepostedIcon(Size20Modifier) LikeIcon(Size20Modifier, Color.Unspecified) + OutlinedZapIcon(Size20Modifier) ZapIcon(Size20Modifier) ZappedIcon(Size20Modifier) + ShareIcon(Size20Modifier, Color.Unspecified) } } @@ -226,17 +254,44 @@ fun ZapIcon( ) { Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(contentDescriptor), + contentDescription = stringRes(contentDescriptor), tint = tint, modifier = modifier, ) } +@Composable +fun OutlinedZapIcon( + modifier: Modifier, + tint: Color = Color.Unspecified, + contentDescriptor: Int = R.string.zap_description, +) { + Icon( + imageVector = Zap, + contentDescription = stringRes(contentDescriptor), + tint = tint, + modifier = modifier, + ) +} + +@Composable +fun ShareIcon( + modifier: Modifier, + tint: Color = Color.Unspecified, +) { + Icon( + imageVector = Icons.Default.Share, + modifier = modifier, + contentDescription = stringRes(R.string.share_or_save), + tint = tint, + ) +} + @Composable fun CashuIcon(modifier: Modifier) { Icon( imageVector = CustomHashTagIcons.Cashu, - "Cashu", + stringRes(R.string.cashu), tint = Color.Unspecified, modifier = modifier, ) @@ -249,7 +304,7 @@ fun CopyIcon( ) { Icon( imageVector = Icons.Default.ContentCopy, - stringResource(id = R.string.copy_to_clipboard), + stringRes(id = R.string.copy_to_clipboard), tint = tint, modifier = modifier, ) @@ -262,7 +317,7 @@ fun OpenInNewIcon( ) { Icon( imageVector = Icons.AutoMirrored.Filled.OpenInNew, - stringResource(id = R.string.copy_to_clipboard), + stringRes(id = R.string.copy_to_clipboard), tint = tint, modifier = modifier, ) @@ -275,7 +330,7 @@ fun ExpandLessIcon( ) { Icon( imageVector = Icons.Default.ExpandLess, - contentDescription = stringResource(id = contentDescriptor), + contentDescription = stringRes(id = contentDescriptor), modifier = modifier, tint = MaterialTheme.colorScheme.subtleButton, ) @@ -288,7 +343,7 @@ fun ExpandMoreIcon( ) { Icon( imageVector = Icons.Default.ExpandMore, - contentDescription = stringResource(id = contentDescriptor), + contentDescription = stringRes(id = contentDescriptor), modifier = modifier, tint = MaterialTheme.colorScheme.subtleButton, ) @@ -300,31 +355,18 @@ fun CommentIcon( tint: Color, ) { Icon( - painter = painterResource(R.drawable.ic_comment), - contentDescription = stringResource(id = R.string.reply_description), + imageVector = Reply, + contentDescription = stringRes(id = R.string.reply_description), modifier = iconSizeModifier, tint = tint, ) } -@Composable -fun ViewCountIcon( - modifier: Modifier, - tint: Color = Color.Unspecified, -) { - Icon( - imageVector = Icons.Outlined.BarChart, - null, - modifier = modifier, - tint = tint, - ) -} - @Composable fun PollIcon() { Icon( painter = painterResource(R.drawable.ic_poll), - contentDescription = stringResource(id = R.string.poll), + contentDescription = stringRes(id = R.string.poll), modifier = Size20Modifier, tint = MaterialTheme.colorScheme.onBackground, ) @@ -334,7 +376,7 @@ fun PollIcon() { fun RegularPostIcon() { Icon( painter = painterResource(R.drawable.ic_lists), - contentDescription = stringResource(id = R.string.disable_poll), + contentDescription = stringRes(id = R.string.disable_poll), modifier = Size20Modifier, tint = MaterialTheme.colorScheme.onBackground, ) @@ -344,7 +386,7 @@ fun RegularPostIcon() { fun CancelIcon() { Icon( imageVector = Icons.Default.Cancel, - contentDescription = stringResource(id = R.string.cancel), + contentDescription = stringRes(id = R.string.cancel), modifier = Size30Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) @@ -354,7 +396,7 @@ fun CancelIcon() { fun CloseIcon() { Icon( painter = painterResource(id = R.drawable.ic_close), - contentDescription = stringResource(id = R.string.cancel), + contentDescription = stringRes(id = R.string.cancel), modifier = Size20Modifier, ) } @@ -363,7 +405,7 @@ fun CloseIcon() { fun MutedIcon() { Icon( imageVector = Icons.AutoMirrored.Filled.VolumeOff, - contentDescription = stringResource(id = R.string.muted_button), + contentDescription = stringRes(id = R.string.muted_button), tint = MaterialTheme.colorScheme.onBackground, modifier = Size30Modifier, ) @@ -373,7 +415,7 @@ fun MutedIcon() { fun MuteIcon() { Icon( imageVector = Icons.AutoMirrored.Filled.VolumeUp, - contentDescription = stringResource(id = R.string.mute_button), + contentDescription = stringRes(id = R.string.mute_button), tint = MaterialTheme.colorScheme.onBackground, modifier = Size30Modifier, ) @@ -385,8 +427,8 @@ fun SearchIcon( tint: Color = Color.Unspecified, ) { Icon( - painter = painterResource(R.drawable.ic_search), - contentDescription = stringResource(id = R.string.search_button), + imageVector = Search, + contentDescription = stringRes(id = R.string.search_button), modifier = modifier, tint = tint, ) @@ -399,7 +441,7 @@ fun PlayIcon( ) { Icon( imageVector = Icons.Outlined.PlayCircle, - contentDescription = stringResource(id = R.string.accessibility_play_username), + contentDescription = stringRes(id = R.string.accessibility_play_username), modifier = modifier, tint = tint, ) @@ -425,7 +467,7 @@ fun LyricsIcon( ) { Icon( painter = painterResource(id = R.drawable.lyrics_on), - contentDescription = stringResource(id = R.string.accessibility_lyrics_on), + contentDescription = stringRes(id = R.string.accessibility_lyrics_on), modifier = modifier, tint = tint, ) @@ -438,7 +480,7 @@ fun LyricsOffIcon( ) { Icon( painter = painterResource(id = R.drawable.lyrics_off), - contentDescription = stringResource(id = R.string.accessibility_lyrics_off), + contentDescription = stringRes(id = R.string.accessibility_lyrics_off), modifier = modifier, tint = tint, ) @@ -448,7 +490,7 @@ fun LyricsOffIcon( fun ClearTextIcon() { Icon( imageVector = Icons.Default.Clear, - contentDescription = stringResource(R.string.clear), + contentDescription = stringRes(R.string.clear), ) } @@ -459,18 +501,18 @@ fun LinkIcon( ) { Icon( imageVector = Icons.Default.Link, - contentDescription = stringResource(R.string.website), + contentDescription = stringRes(R.string.website), modifier = modifier, tint = tint, ) } @Composable -fun VerticalDotsIcon(contentDescriptor: Int? = null) { +fun VerticalDotsIcon() { Icon( imageVector = Icons.Default.MoreVert, - contentDescription = contentDescriptor?.let { stringResource(id = it) }, - modifier = Size18Modifier, + contentDescription = stringRes(id = R.string.note_options), + modifier = Size19Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) } @@ -479,7 +521,7 @@ fun VerticalDotsIcon(contentDescriptor: Int? = null) { fun NIP05CheckingIcon(modifier: Modifier) { Icon( imageVector = Icons.Default.Downloading, - contentDescription = stringResource(id = R.string.nip05_checking), + contentDescription = stringRes(id = R.string.nip05_checking), modifier = modifier, tint = Color.Yellow, ) @@ -489,7 +531,7 @@ fun NIP05CheckingIcon(modifier: Modifier) { fun NIP05VerifiedIcon(modifier: Modifier) { Icon( painter = painterResource(R.drawable.nip_05), - contentDescription = stringResource(id = R.string.nip05_verified), + contentDescription = stringRes(id = R.string.nip05_verified), modifier = modifier, tint = Color.Unspecified, ) @@ -499,7 +541,7 @@ fun NIP05VerifiedIcon(modifier: Modifier) { fun NIP05FailedVerification(modifier: Modifier) { Icon( imageVector = Icons.Default.Report, - contentDescription = stringResource(id = R.string.nip05_failed), + contentDescription = stringRes(id = R.string.nip05_failed), modifier = modifier, tint = Color.Red, ) @@ -512,7 +554,7 @@ fun IncognitoIconOn( ) { Icon( painter = painterResource(id = R.drawable.incognito), - contentDescription = stringResource(id = R.string.accessibility_turn_off_sealed_message), + contentDescription = stringRes(id = R.string.accessibility_turn_off_sealed_message), modifier = modifier, tint = tint, ) @@ -525,7 +567,7 @@ fun IncognitoIconOff( ) { Icon( painter = painterResource(id = R.drawable.incognito_off), - contentDescription = stringResource(id = R.string.accessibility_turn_on_sealed_message), + contentDescription = stringRes(id = R.string.accessibility_turn_on_sealed_message), modifier = modifier, tint = tint, ) @@ -537,8 +579,8 @@ fun ZapSplitIcon( tint: Color = BitcoinOrange, ) { Icon( - imageVector = ZapSplitVector, - contentDescription = stringResource(id = R.string.zap_split_title), + imageVector = ZapSplit, + contentDescription = stringRes(id = R.string.zap_split_title), modifier = modifier, tint = tint, ) @@ -549,56 +591,29 @@ fun ZapSplitIcon( fun ZapSplitPreview() { Column(horizontalAlignment = Alignment.CenterHorizontally) { Box( - Modifier.height(20.dp).width(25.dp), + Modifier + .height(20.dp) + .width(25.dp), ) { Icon( imageVector = Icons.Outlined.Bolt, - contentDescription = stringResource(id = R.string.zaps), - modifier = Modifier.size(20.dp).align(Alignment.CenterStart), + contentDescription = stringRes(id = R.string.zaps), + modifier = + Modifier + .size(20.dp) + .align(Alignment.CenterStart), tint = BitcoinOrange, ) Icon( imageVector = Icons.Outlined.ArrowForwardIos, - contentDescription = stringResource(id = R.string.zaps), - modifier = Modifier.size(13.dp).align(Alignment.CenterEnd), + contentDescription = stringRes(id = R.string.zaps), + modifier = + Modifier + .size(13.dp) + .align(Alignment.CenterEnd), tint = BitcoinOrange, ) } ZapSplitIcon(tint = BitcoinOrange) } } - -public val ZapSplitVector: ImageVector - get() { - if (zapSplit != null) { - return zapSplit!! - } - zapSplit = - materialIcon(name = "ZapSplit") { - materialPath { - moveTo(7.0f, 21.0f) - horizontalLineToRelative(-1.0f) - lineToRelative(1.0f, -7.0f) - horizontalLineTo(3.5f) - curveToRelative(-0.88f, 0.0f, -0.33f, -0.75f, -0.31f, -0.78f) - curveTo(4.48f, 10.94f, 6.42f, 7.54f, 9.01f, 3.0f) - horizontalLineToRelative(1.0f) - lineToRelative(-1.0f, 7.0f) - horizontalLineToRelative(3.51f) - curveToRelative(0.4f, 0.0f, 0.62f, 0.19f, 0.4f, 0.66f) - curveTo(8.97f, 17.55f, 7.0f, 21.0f, 7.0f, 21.0f) - close() - moveTo(14.59f, 16.59f) - lineTo(19.17f, 12.0f) - lineTo(14.59f, 7.41f) - lineTo(16.0f, 6.0f) - lineToRelative(6.0f, 6.0f) - lineToRelative(-6.0f, 6.0f) - lineToRelative(-1.41f, -1.41f) - close() - } - } - return zapSplit!! - } - -private var zapSplit: ImageVector? = null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt index 5c7417b26b..3ea88e7aa4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt @@ -68,25 +68,23 @@ fun MessageSetCompose( val columnModifier = remember(backgroundColor.value) { - Modifier.background(backgroundColor.value) + Modifier + .background(backgroundColor.value) .padding( start = 12.dp, end = 12.dp, top = 10.dp, - ) - .combinedClickable( + ).combinedClickable( onClick = { scope.launch { routeFor( baseNote, accountViewModel.userProfile(), - ) - ?.let { nav(it) } + )?.let { nav(it) } } }, onLongClick = enablePopup, - ) - .fillMaxWidth() + ).fillMaxWidth() } Column(columnModifier) { @@ -111,7 +109,9 @@ fun MessageSetCompose( nav = nav, ) - NoteDropDownMenu(baseNote, popupExpanded, null, accountViewModel, nav) + if (popupExpanded.value) { + NoteDropDownMenu(baseNote, { popupExpanded.value = false }, null, accountViewModel, nav) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 0ade7e95a7..4552e57db7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -53,12 +53,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.User @@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.screen.CombinedZap import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.ui.theme.NotificationIconModifier import com.vitorpamplona.amethyst.ui.theme.NotificationIconModifierSmaller @@ -105,7 +106,6 @@ fun MultiSetCompose( val baseNote = remember { multiSetCard.note } val popupExpanded = remember { mutableStateOf(false) } - val enablePopup = remember { { popupExpanded.value = true } } val scope = rememberCoroutineScope() @@ -125,7 +125,7 @@ fun MultiSetCompose( onClick = { scope.launch { routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } } }, - onLongClick = enablePopup, + onLongClick = { popupExpanded.value = true }, ).padding( start = 12.dp, end = 12.dp, @@ -151,7 +151,9 @@ fun MultiSetCompose( nav = nav, ) - NoteDropDownMenu(baseNote, popupExpanded, null, accountViewModel, nav) + if (popupExpanded.value) { + NoteDropDownMenu(baseNote, { popupExpanded.value = false }, null, accountViewModel, nav) + } } } } @@ -493,7 +495,7 @@ private fun BoxedAuthor( accountViewModel: AccountViewModel, ) { Box(modifier = Size35Modifier.clickable(onClick = { nav(authorRouteFor(note)) })) { - WatchAuthorWithBlank(note, Size35Modifier) { author -> + WatchAuthorWithBlank(note, Size35Modifier, accountViewModel) { author -> WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor( author, accountViewModel, @@ -510,7 +512,7 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePictureOrDefaultAuthor( if (author != null) { WatchUserMetadataAndFollowsAndRenderUserProfilePicture(author, accountViewModel) } else { - DisplayBlankAuthor(Size35dp) + DisplayBlankAuthor(Size35dp, accountViewModel = accountViewModel) } } @@ -520,26 +522,23 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture( accountViewModel: AccountViewModel, ) { WatchUserMetadata(author) { baseUserPicture -> - // Crossfade(targetState = baseUserPicture) { userPicture -> RobohashFallbackAsyncImage( robot = author.pubkeyHex, model = baseUserPicture, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), modifier = MaterialTheme.colorScheme.profile35dpModifier, contentScale = ContentScale.Crop, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) - // } } WatchUserFollows(author.pubkeyHex, accountViewModel) { isFollowing -> - // Crossfade(targetState = isFollowing) { if (isFollowing) { Box(modifier = Size35Modifier, contentAlignment = Alignment.TopEnd) { FollowingIcon(Size10dp) } } - // } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 3f34d409b8..5435d2f7dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.components -import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.Tunestr import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadStatuses @@ -132,10 +132,11 @@ fun ObserveDisplayNip05Status( val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05()) LoadStatuses(baseUser, accountViewModel) { statuses -> - Crossfade( + CrossfadeIfEnabled( targetState = nip05, modifier = columnModifier, label = "ObserveDisplayNip05StatusCrossfade", + accountViewModel = accountViewModel, ) { VerifyAndDisplayNIP05OrStatusLine( it, @@ -165,11 +166,11 @@ private fun VerifyAndDisplayNIP05OrStatusLine( nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex, accountViewModel) if (nip05Verified.value != true) { - DisplayNIP05(nip05, nip05Verified) + DisplayNIP05(nip05, nip05Verified, accountViewModel) } else if (!statuses.isEmpty()) { RotateStatuses(statuses, accountViewModel, nav) } else { - DisplayNIP05(nip05, nip05Verified) + DisplayNIP05(nip05, nip05Verified, accountViewModel) } } else { if (!statuses.isEmpty()) { @@ -271,8 +272,7 @@ fun DisplayStatus( routeFor( note, accountViewModel.userProfile(), - ) - ?.let { nav(it) } + )?.let { nav(it) } }, ) { Icon( @@ -294,8 +294,7 @@ fun DisplayStatus( routeFor( it, accountViewModel.userProfile(), - ) - ?.let { nav(it) } + )?.let { nav(it) } }, ) { Icon( @@ -314,6 +313,7 @@ fun DisplayStatus( private fun DisplayNIP05( nip05: String, nip05Verified: MutableState, + accountViewModel: AccountViewModel, ) { val uri = LocalUriHandler.current val (user, domain) = @@ -336,7 +336,7 @@ private fun DisplayNIP05( ) } - NIP05VerifiedSymbol(nip05Verified, NIP05IconSize) + NIP05VerifiedSymbol(nip05Verified, NIP05IconSize, accountViewModel) ClickableText( text = remember(nip05) { AnnotatedString(domain) }, @@ -352,8 +352,9 @@ private fun DisplayNIP05( private fun NIP05VerifiedSymbol( nip05Verified: MutableState, modifier: Modifier, + accountViewModel: AccountViewModel, ) { - Crossfade(targetState = nip05Verified.value) { + CrossfadeIfEnabled(targetState = nip05Verified.value, accountViewModel = accountViewModel) { when (it) { null -> NIP05CheckingIcon(modifier = modifier) true -> NIP05VerifiedIcon(modifier = modifier) @@ -373,7 +374,7 @@ fun DisplayNip05ProfileStatus( if (nip05.split("@").size <= 2) { val nip05Verified = nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel) Row(verticalAlignment = Alignment.CenterVertically) { - NIP05VerifiedSymbol(nip05Verified, Size16Modifier) + NIP05VerifiedSymbol(nip05Verified, Size16Modifier, accountViewModel) var domainPadStart = 5.dp val (user, domain) = diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index e2a26e1106..d6d7cd777a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.distinctUntilChanged @@ -113,6 +112,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderWikiContent import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.RenderChannelHeader +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer @@ -461,7 +461,14 @@ fun InnerNoteWithReactions( horizontalArrangement = RowColSpacing10dp, ) { if (notBoostedNorQuote) { - AuthorAndRelayInformation(baseNote, accountViewModel, nav) + Column(WidthAuthorPictureModifier, verticalArrangement = RowColSpacing5dp) { + // Draws the boosted picture outside the boosted card. + Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) { + RenderAuthorImages(baseNote, nav, accountViewModel) + } + + BadgeBox(baseNote, accountViewModel, nav) + } } Column(Modifier.fillMaxWidth()) { @@ -470,7 +477,7 @@ fun InnerNoteWithReactions( baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote && - accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED + accountViewModel.settings.featureSet == FeatureSetType.COMPLETE NoteBody( baseNote = baseNote, showAuthorPicture = isQuotedNote, @@ -968,9 +975,9 @@ fun FirstUserInfoRow( if (showAuthorPicture) { NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp) Spacer(HalfPadding) - NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor) + NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } else { - NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor) + NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } if (isCommunityPost) { @@ -1051,29 +1058,13 @@ fun observeEdits( return editState } -@Composable -private fun AuthorAndRelayInformation( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - Column(WidthAuthorPictureModifier, verticalArrangement = RowColSpacing5dp) { - // Draws the boosted picture outside the boosted card. - Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) { - RenderAuthorImages(baseNote, nav, accountViewModel) - } - - BadgeBox(baseNote, accountViewModel, nav) - } -} - @Composable private fun BadgeBox( baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - if (accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED) { + if (accountViewModel.settings.featureSet == FeatureSetType.COMPLETE) { if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) { baseNote.replyTo?.lastOrNull()?.let { RelayBadges(it, accountViewModel, nav) } } else { @@ -1088,9 +1079,7 @@ private fun RenderAuthorImages( nav: (String) -> Unit, accountViewModel: AccountViewModel, ) { - val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent - - if (isRepost) { + if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) { val baseRepost = baseNote.replyTo?.lastOrNull() if (baseRepost != null) { RepostNoteAuthorPicture(baseNote, baseRepost, accountViewModel, nav) @@ -1108,6 +1097,7 @@ private fun RenderAuthorImages( ChannelNotePicture( channel, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } } @@ -1118,6 +1108,7 @@ private fun RenderAuthorImages( private fun ChannelNotePicture( baseChannel: Channel, loadProfilePicture: Boolean, + loadRobohash: Boolean, ) { val model by baseChannel.live @@ -1129,9 +1120,10 @@ private fun ChannelNotePicture( RobohashFallbackAsyncImage( robot = baseChannel.idHex, model = model, - contentDescription = stringResource(R.string.group_picture), + contentDescription = stringRes(R.string.group_picture), modifier = MaterialTheme.colorScheme.channelNotePictureModifier, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 94b058d579..159961c921 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -73,7 +73,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign @@ -89,6 +88,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.SelectTextDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.WarningColor import com.vitorpamplona.amethyst.ui.theme.isLight import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground @@ -260,12 +260,12 @@ private fun RenderMainPopup( MaterialTheme.colorScheme.secondaryButtonBackground } - val showToast = { stringResource: Int -> + val showToast = { stringRes: Int -> scope.launch { Toast .makeText( context, - context.getString(stringResource), + stringRes(context, stringRes), Toast.LENGTH_SHORT, ).show() } @@ -284,7 +284,7 @@ private fun RenderMainPopup( Row(modifier = Modifier.height(IntrinsicSize.Min)) { NoteQuickActionItem( icon = Icons.Default.ContentCopy, - label = stringResource(R.string.quick_action_copy_text), + label = stringRes(R.string.quick_action_copy_text), ) { accountViewModel.decrypt(note) { clipboardManager.setText(AnnotatedString(it)) @@ -296,7 +296,7 @@ private fun RenderMainPopup( VerticalDivider(color = primaryLight) NoteQuickActionItem( Icons.Default.AlternateEmail, - stringResource(R.string.quick_action_copy_user_id), + stringRes(R.string.quick_action_copy_user_id), ) { scope.launch(Dispatchers.IO) { clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}")) @@ -307,7 +307,7 @@ private fun RenderMainPopup( VerticalDivider(color = primaryLight) NoteQuickActionItem( Icons.Default.FormatQuote, - stringResource(R.string.quick_action_copy_note_id), + stringRes(R.string.quick_action_copy_note_id), ) { scope.launch(Dispatchers.IO) { clipboardManager.setText(AnnotatedString("nostr:${note.toNEvent()}")) @@ -321,7 +321,7 @@ private fun RenderMainPopup( NoteQuickActionItem( Icons.Default.Block, - stringResource(R.string.quick_action_block), + stringRes(R.string.quick_action_block), ) { if (accountViewModel.hideBlockAlertDialog) { note.author?.let { accountViewModel.hide(it) } @@ -339,7 +339,7 @@ private fun RenderMainPopup( if (isOwnNote) { NoteQuickActionItem( Icons.Default.Delete, - stringResource(R.string.quick_action_delete), + stringRes(R.string.quick_action_delete), ) { if (accountViewModel.hideDeleteRequestDialog) { accountViewModel.delete(note) @@ -351,7 +351,7 @@ private fun RenderMainPopup( } else if (isFollowingUser) { NoteQuickActionItem( Icons.Default.PersonRemove, - stringResource(R.string.quick_action_unfollow), + stringRes(R.string.quick_action_unfollow), ) { accountViewModel.unfollow(note.author!!) onDismiss() @@ -359,7 +359,7 @@ private fun RenderMainPopup( } else { NoteQuickActionItem( Icons.Default.PersonAdd, - stringResource(R.string.quick_action_follow), + stringRes(R.string.quick_action_follow), ) { accountViewModel.follow(note.author!!) onDismiss() @@ -369,7 +369,7 @@ private fun RenderMainPopup( VerticalDivider(color = primaryLight) NoteQuickActionItem( icon = ImageVector.vectorResource(id = R.drawable.relays), - label = stringResource(R.string.broadcast), + label = stringRes(R.string.broadcast), ) { accountViewModel.broadcast(note) // showSelectTextDialog = true @@ -379,7 +379,7 @@ private fun RenderMainPopup( if (isOwnNote && note.isDraft()) { NoteQuickActionItem( Icons.Default.Edit, - stringResource(R.string.edit_draft), + stringRes(R.string.edit_draft), ) { onDismiss() onWantsToEditDraft() @@ -387,7 +387,7 @@ private fun RenderMainPopup( } else { NoteQuickActionItem( icon = Icons.Default.Share, - label = stringResource(R.string.quick_action_share), + label = stringRes(R.string.quick_action_share), ) { val sendIntent = Intent().apply { @@ -399,14 +399,14 @@ private fun RenderMainPopup( ) putExtra( Intent.EXTRA_TITLE, - context.getString(R.string.quick_action_share_browser_link), + stringRes(context, R.string.quick_action_share_browser_link), ) } val shareIntent = Intent.createChooser( sendIntent, - context.getString(R.string.quick_action_share), + stringRes(context, R.string.quick_action_share), ) ContextCompat.startActivity(context, shareIntent, null) onDismiss() @@ -418,7 +418,7 @@ private fun RenderMainPopup( NoteQuickActionItem( Icons.Default.Report, - stringResource(R.string.quick_action_report), + stringRes(R.string.quick_action_report), ) { showReportDialog.value = true } @@ -463,10 +463,10 @@ fun DeleteAlertDialog( onDismiss: () -> Unit, ) { QuickActionAlertDialog( - title = stringResource(R.string.quick_action_request_deletion_alert_title), - textContent = stringResource(R.string.quick_action_request_deletion_alert_body), + title = stringRes(R.string.quick_action_request_deletion_alert_title), + textContent = stringRes(R.string.quick_action_request_deletion_alert_body), buttonIcon = Icons.Default.Delete, - buttonText = stringResource(R.string.quick_action_delete_dialog_btn), + buttonText = stringRes(R.string.quick_action_delete_dialog_btn), onClickDoOnce = { accountViewModel.delete(note) onDismiss() @@ -486,10 +486,10 @@ private fun BlockAlertDialog( accountViewModel: AccountViewModel, onDismiss: () -> Unit, ) = QuickActionAlertDialog( - title = stringResource(R.string.report_dialog_block_hide_user_btn), - textContent = stringResource(R.string.report_dialog_blocking_a_user), + title = stringRes(R.string.report_dialog_block_hide_user_btn), + textContent = stringRes(R.string.report_dialog_blocking_a_user), buttonIcon = Icons.Default.Block, - buttonText = stringResource(R.string.quick_action_block_dialog_btn), + buttonText = stringRes(R.string.quick_action_block_dialog_btn), buttonColors = ButtonDefaults.buttonColors( containerColor = WarningColor, @@ -587,7 +587,7 @@ fun QuickActionAlertDialog( horizontalArrangement = Arrangement.SpaceBetween, ) { TextButton(onClick = onClickDontShowAgain) { - Text(stringResource(R.string.quick_action_dont_show_again_button)) + Text(stringRes(R.string.quick_action_dont_show_again_button)) } Button( onClick = onClickDoOnce, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index d973d91855..b4e689a521 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -61,7 +61,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -76,6 +75,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BigPadding import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder @@ -437,7 +437,7 @@ fun ZapVote( zappingProgress = 0f showErrorMessageDialog = StringToastMsg( - context.getString(R.string.error_dialog_zap_error), + stringRes(context, R.string.error_dialog_zap_error), it, ) } @@ -446,7 +446,7 @@ fun ZapVote( scope.launch { showErrorMessageDialog = StringToastMsg( - context.getString(R.string.error_dialog_zap_error), + stringRes(context, R.string.error_dialog_zap_error), it, ) } @@ -471,7 +471,7 @@ fun ZapVote( zappingProgress = 1f Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(R.string.zaps), + contentDescription = stringRes(R.string.zaps), modifier = Modifier.size(20.dp), tint = BitcoinOrange, ) @@ -479,7 +479,7 @@ fun ZapVote( if (zappingProgress < 0.1 || zappingProgress > 0.99) { Icon( imageVector = Icons.Outlined.Bolt, - contentDescription = stringResource(id = R.string.zaps), + contentDescription = stringRes(id = R.string.zaps), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.placeholderText, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 30d17ece1d..ce5ff730e7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -21,10 +21,10 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Context +import android.content.Intent import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform -import androidx.compose.animation.Crossfade import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween @@ -82,11 +82,8 @@ import androidx.compose.ui.Alignment.Companion.CenterStart import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.style.TextAlign @@ -95,27 +92,28 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup +import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map -import coil.compose.AsyncImage -import coil.request.CachePolicy -import coil.request.ImageRequest import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.ZapPaymentHandler +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.NewPostView +import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DarkerGreen import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer -import com.vitorpamplona.amethyst.ui.theme.Height24dpModifier import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp import com.vitorpamplona.amethyst.ui.theme.NoSoTinyBorders import com.vitorpamplona.amethyst.ui.theme.ReactionRowExpandButton @@ -124,8 +122,8 @@ import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightWithPadding import com.vitorpamplona.amethyst.ui.theme.ReactionRowZapraiser import com.vitorpamplona.amethyst.ui.theme.ReactionRowZapraiserWithPadding import com.vitorpamplona.amethyst.ui.theme.RowColSpacing -import com.vitorpamplona.amethyst.ui.theme.Size16Modifier -import com.vitorpamplona.amethyst.ui.theme.Size17Modifier +import com.vitorpamplona.amethyst.ui.theme.Size18Modifier +import com.vitorpamplona.amethyst.ui.theme.Size18dp import com.vitorpamplona.amethyst.ui.theme.Size19Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size20dp @@ -134,7 +132,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size24dp import com.vitorpamplona.amethyst.ui.theme.TinyBorders import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.placeholderText -import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter import com.vitorpamplona.quartz.encoders.Nip30CustomEmoji import com.vitorpamplona.quartz.events.BaseTextNoteEvent import kotlinx.collections.immutable.ImmutableList @@ -182,7 +179,7 @@ private fun InnerReactionRow( addPadding = addPadding, one = { WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) { - RenderShowIndividualReactionsButton(wantsToSeeReactions) + RenderShowIndividualReactionsButton(wantsToSeeReactions, accountViewModel) } }, two = { @@ -209,15 +206,51 @@ private fun InnerReactionRow( ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav) }, six = { - ViewCountReaction( + ShareReaction( note = baseNote, grayTint = MaterialTheme.colorScheme.placeholderText, - viewCountColorFilter = MaterialTheme.colorScheme.placeholderTextColorFilter, ) }, ) } +@Composable +fun ShareReaction( + note: Note, + grayTint: Color, + barChartModifier: Modifier = Size19Modifier, +) { + val context = LocalContext.current + + ClickableBox( + modifier = barChartModifier, + onClick = { + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra( + Intent.EXTRA_TEXT, + externalLinkForNote(note), + ) + putExtra( + Intent.EXTRA_TITLE, + stringRes(context, R.string.quick_action_share_browser_link), + ) + } + + val shareIntent = + Intent.createChooser( + sendIntent, + stringRes(context, R.string.quick_action_share), + ) + ContextCompat.startActivity(context, shareIntent, null) + }, + ) { + ShareIcon(barChartModifier, grayTint) + } +} + @Composable private fun GenericInnerReactionRow( showReactionDetail: Boolean, @@ -251,7 +284,7 @@ private fun GenericInnerReactionRow( Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { five() } - Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { six() } + Row(verticalAlignment = CenterVertically, modifier = Modifier) { six() } } } @@ -329,7 +362,7 @@ fun RenderZapRaiser( Text( text = - stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserStatus.left), + stringRes(id = R.string.sats_to_complete, totalPercentage, zapraiserStatus.left), modifier = NoSoTinyBorders, color = MaterialTheme.colorScheme.placeholderText, fontSize = Font14SP, @@ -382,14 +415,18 @@ fun LiveData.combineWith( } @Composable -private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableState) { - IconButton( +private fun RenderShowIndividualReactionsButton( + wantsToSeeReactions: MutableState, + accountViewModel: AccountViewModel, +) { + ClickableBox( onClick = { wantsToSeeReactions.value = !wantsToSeeReactions.value }, modifier = Size20Modifier, ) { - Crossfade( + CrossfadeIfEnabled( targetState = wantsToSeeReactions.value, label = "RenderShowIndividualReactionsButton", + accountViewModel = accountViewModel, ) { if (it) { ExpandLessIcon(modifier = Size22Modifier, R.string.close_all_reactions_to_this_post) @@ -585,10 +622,10 @@ fun ReplyReaction( grayTint: Color, accountViewModel: AccountViewModel, showCounter: Boolean = true, - iconSizeModifier: Modifier = Size17Modifier, + iconSizeModifier: Modifier = Size19Modifier, onPress: () -> Unit, ) { - IconButton( + ClickableBox( modifier = iconSizeModifier, onClick = { if (baseNote.isDraft()) { @@ -596,15 +633,15 @@ fun ReplyReaction( R.string.draft_note, R.string.it_s_not_possible_to_reply_to_a_draft_note, ) - return@IconButton - } - if (accountViewModel.isWriteable()) { - onPress() } else { - accountViewModel.toast( - R.string.read_only_user, - R.string.login_with_a_private_key_to_be_able_to_reply, - ) + if (accountViewModel.isWriteable()) { + onPress() + } else { + accountViewModel.toast( + R.string.read_only_user, + R.string.login_with_a_private_key_to_be_able_to_reply, + ) + } } }, ) { @@ -612,7 +649,7 @@ fun ReplyReaction( } if (showCounter) { - ReplyCounter(baseNote, grayTint) + ReplyCounter(baseNote, grayTint, accountViewModel) } } @@ -620,23 +657,29 @@ fun ReplyReaction( fun ReplyCounter( baseNote: Note, textColor: Color, + accountViewModel: AccountViewModel, ) { val repliesState by baseNote.live().replyCount.observeAsState(baseNote.replies.size) - SlidingAnimationCount(repliesState, textColor) + SlidingAnimationCount(repliesState, textColor, accountViewModel) } @Composable private fun SlidingAnimationCount( baseCount: Int, textColor: Color, + accountViewModel: AccountViewModel, ) { - AnimatedContent( - targetState = baseCount, - transitionSpec = AnimatedContentTransitionScope::transitionSpec, - label = "SlidingAnimationCount", - ) { count -> - TextCount(count, textColor) + if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) { + TextCount(baseCount, textColor) + } else { + AnimatedContent( + targetState = baseCount, + transitionSpec = AnimatedContentTransitionScope::transitionSpec, + label = "SlidingAnimationCount", + ) { count -> + TextCount(count, textColor) + } } } @@ -674,18 +717,28 @@ fun TextCount( fun SlidingAnimationAmount( amount: String, textColor: Color, + accountViewModel: AccountViewModel, ) { - AnimatedContent( - targetState = amount, - transitionSpec = AnimatedContentTransitionScope::transitionSpec, - label = "SlidingAnimationAmount", - ) { count -> + if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) { Text( - text = count, + text = amount, fontSize = Font14SP, color = textColor, maxLines = 1, ) + } else { + AnimatedContent( + targetState = amount, + transitionSpec = AnimatedContentTransitionScope::transitionSpec, + label = "SlidingAnimationAmount", + ) { count -> + Text( + text = count, + fontSize = Font14SP, + color = textColor, + maxLines = 1, + ) + } } } @@ -694,16 +747,18 @@ fun BoostReaction( baseNote: Note, grayTint: Color, accountViewModel: AccountViewModel, - iconSizeModifier: Modifier = Size20Modifier, + iconSizeModifier: Modifier = Size19Modifier, iconSize: Dp = Size20dp, onQuotePress: () -> Unit, onForkPress: () -> Unit, ) { var wantsToBoost by remember { mutableStateOf(false) } - IconButton( + ClickableBox( modifier = iconSizeModifier, - onClick = { accountViewModel.tryBoost(baseNote) { wantsToBoost = true } }, + onClick = { + accountViewModel.tryBoost(baseNote) { wantsToBoost = true } + }, ) { ObserveBoostIcon(baseNote, accountViewModel) { hasBoosted -> RepostedIcon(iconSizeModifier, if (hasBoosted) Color.Unspecified else grayTint) @@ -730,7 +785,7 @@ fun BoostReaction( } } - BoostText(baseNote, grayTint) + BoostText(baseNote, grayTint, accountViewModel) } @Composable @@ -757,10 +812,11 @@ fun ObserveBoostIcon( fun BoostText( baseNote: Note, grayTint: Color, + accountViewModel: AccountViewModel, ) { val boostState by baseNote.live().boostCount.observeAsState(baseNote.boosts.size) - SlidingAnimationCount(boostState, grayTint) + SlidingAnimationCount(boostState, grayTint, accountViewModel) } @OptIn(ExperimentalFoundationApi::class) @@ -770,8 +826,8 @@ fun LikeReaction( grayTint: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit, - iconSize: Dp = Size20dp, - heartSizeModifier: Modifier = Size16Modifier, + iconSize: Dp = Size18dp, + heartSizeModifier: Modifier = Size18Modifier, iconFontSize: TextUnit = Font14SP, ) { var wantsToChangeReactionSymbol by remember { mutableStateOf(false) } @@ -797,7 +853,7 @@ fun LikeReaction( ), ) { ObserveLikeIcon(baseNote, accountViewModel) { reactionType -> - Crossfade(targetState = reactionType, label = "LikeIcon") { + CrossfadeIfEnabled(targetState = reactionType, label = "LikeIcon", accountViewModel = accountViewModel) { if (it != null) { RenderReactionType(it, heartSizeModifier, iconFontSize) } else { @@ -828,7 +884,7 @@ fun LikeReaction( } } - ObserveLikeText(baseNote) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint) } + ObserveLikeText(baseNote) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) } } @Composable @@ -1001,7 +1057,7 @@ fun ZapReaction( if (showErrorMessageDialog.isNotEmpty()) { val msg = showErrorMessageDialog.joinToString("\n") ErrorMessageDialog( - title = stringResource(id = R.string.error_dialog_zap_error), + title = stringRes(id = R.string.error_dialog_zap_error), textContent = msg, onClickStartMessage = { baseNote.author?.let { @@ -1076,11 +1132,11 @@ fun ZapReaction( baseNote, accountViewModel, ) { wasZappedByLoggedInUser -> - Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") { + CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) { if (it) { ZappedIcon(iconSizeModifier) } else { - ZapIcon(iconSizeModifier, grayTint) + OutlinedZapIcon(iconSizeModifier, grayTint) } } } @@ -1088,7 +1144,7 @@ fun ZapReaction( } ObserveZapAmountText(baseNote, accountViewModel) { zapAmountTxt -> - SlidingAnimationAmount(zapAmountTxt, grayTint) + SlidingAnimationAmount(zapAmountTxt, grayTint, accountViewModel) } } @@ -1111,13 +1167,13 @@ fun zapClick( if (accountViewModel.account.zapAmountChoices.isEmpty()) { accountViewModel.toast( - context.getString(R.string.error_dialog_zap_error), - context.getString(R.string.no_zap_amount_setup_long_press_to_change), + R.string.error_dialog_zap_error, + R.string.no_zap_amount_setup_long_press_to_change, ) } else if (!accountViewModel.isWriteable()) { accountViewModel.toast( - context.getString(R.string.error_dialog_zap_error), - context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps), + R.string.error_dialog_zap_error, + R.string.login_with_a_private_key_to_be_able_to_send_zaps, ) } else if (accountViewModel.account.zapAmountChoices.size == 1) { accountViewModel.zap( @@ -1187,42 +1243,6 @@ fun ObserveZapAmountText( } } -@Composable -fun ViewCountReaction( - note: Note, - grayTint: Color, - barChartModifier: Modifier = Size19Modifier, - numberSizeModifier: Modifier = Height24dpModifier, - viewCountColorFilter: ColorFilter, -) { - ViewCountIcon(barChartModifier, grayTint) - DrawViewCount(note, numberSizeModifier, viewCountColorFilter) -} - -@Composable -private fun DrawViewCount( - note: Note, - iconModifier: Modifier = Modifier, - viewCountColorFilter: ColorFilter, -) { - val context = LocalContext.current - - AsyncImage( - model = - remember(note) { - ImageRequest - .Builder(context) - .data("https://counter.amethyst.social/${note.idHex}.svg?label=+&color=00000000") - .diskCachePolicy(CachePolicy.DISABLED) - .memoryCachePolicy(CachePolicy.ENABLED) - .build() - }, - contentDescription = context.getString(R.string.view_count), - modifier = iconModifier, - colorFilter = viewCountColorFilter, - ) -} - @OptIn(ExperimentalLayoutApi::class) @Composable private fun BoostTypeChoicePopup( @@ -1259,7 +1279,7 @@ private fun BoostTypeChoicePopup( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(stringResource(R.string.boost), color = Color.White, textAlign = TextAlign.Center) + Text(stringRes(R.string.boost), color = Color.White, textAlign = TextAlign.Center) } Button( @@ -1271,7 +1291,7 @@ private fun BoostTypeChoicePopup( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(stringResource(R.string.quote), color = Color.White, textAlign = TextAlign.Center) + Text(stringRes(R.string.quote), color = Color.White, textAlign = TextAlign.Center) } Button( @@ -1283,7 +1303,7 @@ private fun BoostTypeChoicePopup( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(stringResource(R.string.fork), color = Color.White, textAlign = TextAlign.Center) + Text(stringRes(R.string.fork), color = Color.White, textAlign = TextAlign.Center) } } } @@ -1389,12 +1409,8 @@ private fun ActionableReactionButton( } else { when (reactionType) { "+" -> { - Icon( - painter = painterResource(R.drawable.ic_liked), - null, - modifier = remember { thisModifier.size(16.dp) }, - tint = Color.White, - ) + LikedIcon(modifier = thisModifier.size(16.dp), tint = Color.White) + Text( text = removeSymbol, color = Color.White, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt index 15a8503a4f..8756f7ad10 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt @@ -38,13 +38,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding @@ -90,7 +90,7 @@ fun RelayCompose( } Text( - "${relay.counter} ${stringResource(R.string.posts_received)}", + "${relay.counter} ${stringRes(R.string.posts_received)}", color = MaterialTheme.colorScheme.placeholderText, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -114,7 +114,8 @@ private fun RelayOptions( val isNotUsingRelay = remember(userState) { - accountViewModel.account.connectToRelays.value.none { it.url == relay.url } + accountViewModel.account.connectToRelays.value + .none { it.url == relay.url } } if (isNotUsingRelay) { @@ -136,7 +137,7 @@ fun AddRelayButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(id = R.string.add), color = Color.White) + Text(text = stringRes(id = R.string.add), color = Color.White) } } @@ -152,12 +153,12 @@ fun RemoveRelayButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.remove), color = Color.White) + Text(text = stringRes(R.string.remove), color = Color.White) } } -fun formattedDateTime(timestamp: Long): String { - return Instant.ofEpochSecond(timestamp) +fun formattedDateTime(timestamp: Long): String = + Instant + .ofEpochSecond(timestamp) .atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("MMM d, uuuu hh:mm a")) -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt index 893c142c6e..5cd95e3a55 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -47,13 +46,14 @@ 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.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer import com.vitorpamplona.amethyst.ui.theme.Size17Modifier import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -69,7 +69,7 @@ fun RelayBadges( ) { val expanded = remember { mutableStateOf(false) } - Crossfade(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges") { + CrossfadeIfEnabled(expanded.value, modifier = noteComposeRelayBox, label = "RelayBadges", accountViewModel = accountViewModel) { if (it) { RenderAllRelayList(baseNote, Modifier.fillMaxWidth(), accountViewModel = accountViewModel, nav = nav) } else { @@ -131,7 +131,7 @@ fun WatchAndRenderRelay( baseNote.relays.getOrNull(relayIndex), ) - Crossfade(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier) { + CrossfadeIfEnabled(targetState = noteRelays, label = "RenderRelay", modifier = Size17Modifier, accountViewModel = accountViewModel) { if (it != null) { RenderRelay(it, accountViewModel, nav) } @@ -239,7 +239,7 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) { ) { Icon( imageVector = Icons.Default.ExpandMore, - contentDescription = stringResource(id = R.string.expand_relay_list), + contentDescription = stringRes(id = R.string.expand_relay_list), tint = MaterialTheme.colorScheme.placeholderText, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 7486e908e9..39a5b3dd6a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -37,16 +36,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog +import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size17dp @@ -87,13 +88,13 @@ fun ShouldShowExpandButton( @Composable fun ChatRelayExpandButton(onClick: () -> Unit) { - IconButton( + ClickableBox( modifier = Size15Modifier, onClick = onClick, ) { Icon( imageVector = Icons.Default.ChevronRight, - contentDescription = stringResource(id = R.string.expand_relay_list), + contentDescription = stringRes(id = R.string.expand_relay_list), modifier = Size15Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) @@ -178,6 +179,7 @@ fun RenderRelay( displayUrl = relay.displayUrl, iconUrl = relayInfo?.icon ?: relay.favIcon, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } } @@ -187,14 +189,16 @@ fun RenderRelayIcon( displayUrl: String, iconUrl: String?, loadProfilePicture: Boolean, + loadRobohash: Boolean, iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier, ) { RobohashFallbackAsyncImage( robot = displayUrl, model = iconUrl, - contentDescription = stringResource(id = R.string.relay_info, displayUrl), + contentDescription = stringRes(id = R.string.relay_info, displayUrl), colorFilter = RelayIconFilter, modifier = iconModifier, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt index fce63082d1..f57ce96ada 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt @@ -33,13 +33,13 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -85,7 +85,7 @@ fun ReplyInformationChannel( if (mentions != null && mentions.isNotEmpty()) { if (replyTo != null && replyTo.isNotEmpty()) { Text( - stringResource(id = R.string.replying_to), + stringRes(id = R.string.replying_to), fontSize = 13.sp, color = MaterialTheme.colorScheme.placeholderText, ) @@ -101,7 +101,7 @@ fun ReplyInformationChannel( ) } else if (idx < mentions.size - 1) { Text( - " ${stringResource(id = R.string.and)} ", + " ${stringRes(id = R.string.and)} ", fontSize = 13.sp, color = MaterialTheme.colorScheme.placeholderText, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/TimeAgoFormatter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/TimeAgoFormatter.kt index d2040b21a1..4cc691cfe1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/TimeAgoFormatter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/TimeAgoFormatter.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Context import android.text.format.DateUtils import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.utils.TimeUtils import java.text.SimpleDateFormat import java.util.Locale @@ -36,7 +37,7 @@ fun timeAgo( context: Context, ): String { if (time == null) return " " - if (time == 0L) return " • ${context.getString(R.string.never)}" + if (time == 0L) return " • ${stringRes(context, R.string.never)}" val timeDifference = TimeUtils.now() - time @@ -61,13 +62,13 @@ fun timeAgo( monthFormatter.format(time * 1000) } else if (timeDifference > TimeUtils.ONE_DAY) { // 2 days - " • " + (timeDifference / TimeUtils.ONE_DAY).toString() + context.getString(R.string.d) + " • " + (timeDifference / TimeUtils.ONE_DAY).toString() + stringRes(context, R.string.d) } else if (timeDifference > TimeUtils.ONE_HOUR) { - " • " + (timeDifference / TimeUtils.ONE_HOUR).toString() + context.getString(R.string.h) + " • " + (timeDifference / TimeUtils.ONE_HOUR).toString() + stringRes(context, R.string.h) } else if (timeDifference > TimeUtils.ONE_MINUTE) { - " • " + (timeDifference / TimeUtils.ONE_MINUTE).toString() + context.getString(R.string.m) + " • " + (timeDifference / TimeUtils.ONE_MINUTE).toString() + stringRes(context, R.string.m) } else { - " • " + context.getString(R.string.now) + " • " + stringRes(context, R.string.now) } } @@ -76,7 +77,7 @@ fun timeAgoNoDot( context: Context, ): String { if (time == null) return " " - if (time == 0L) return " ${context.getString(R.string.never)}" + if (time == 0L) return " ${stringRes(context, R.string.never)}" val timeDifference = TimeUtils.now() - time @@ -101,13 +102,13 @@ fun timeAgoNoDot( monthFormatter.format(time * 1000) } else if (timeDifference > TimeUtils.ONE_DAY) { // 2 days - (timeDifference / TimeUtils.ONE_DAY).toString() + context.getString(R.string.d) + (timeDifference / TimeUtils.ONE_DAY).toString() + stringRes(context, R.string.d) } else if (timeDifference > TimeUtils.ONE_HOUR) { - (timeDifference / TimeUtils.ONE_HOUR).toString() + context.getString(R.string.h) + (timeDifference / TimeUtils.ONE_HOUR).toString() + stringRes(context, R.string.h) } else if (timeDifference > TimeUtils.ONE_MINUTE) { - (timeDifference / TimeUtils.ONE_MINUTE).toString() + context.getString(R.string.m) + (timeDifference / TimeUtils.ONE_MINUTE).toString() + stringRes(context, R.string.m) } else { - context.getString(R.string.now) + stringRes(context, R.string.now) } } @@ -118,13 +119,13 @@ fun timeAgoShort( if (mills == null) return " " var humanReadable = - DateUtils.getRelativeTimeSpanString( - mills * 1000, - System.currentTimeMillis(), - DateUtils.MINUTE_IN_MILLIS, - DateUtils.FORMAT_ABBREV_ALL, - ) - .toString() + DateUtils + .getRelativeTimeSpanString( + mills * 1000, + System.currentTimeMillis(), + DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_ALL, + ).toString() if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) { humanReadable = stringForNow } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index 15a26db15c..bae00dcd36 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -45,7 +45,6 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -62,8 +61,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment 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.text.SpanStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -87,6 +84,7 @@ import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.encoders.ATag @@ -98,7 +96,9 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch -class UpdateReactionTypeViewModel(val account: Account) : ViewModel() { +class UpdateReactionTypeViewModel( + val account: Account, +) : ViewModel() { var nextChoice by mutableStateOf(TextFieldValue("")) var reactionSet by mutableStateOf(listOf()) @@ -106,9 +106,7 @@ class UpdateReactionTypeViewModel(val account: Account) : ViewModel() { this.reactionSet = account.reactionChoices } - fun toListOfChoices(commaSeparatedAmounts: String): List { - return commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 } - } + fun toListOfChoices(commaSeparatedAmounts: String): List = commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 } fun addChoice() { val newValue = nextChoice.text.trim().firstFullChar() @@ -134,14 +132,12 @@ class UpdateReactionTypeViewModel(val account: Account) : ViewModel() { nextChoice = TextFieldValue("") } - fun hasChanged(): Boolean { - return reactionSet != account.reactionChoices - } + fun hasChanged(): Boolean = reactionSet != account.reactionChoices - class Factory(val account: Account) : ViewModelProvider.Factory { - override fun create(modelClass: Class): UpdateReactionTypeViewModel { - return UpdateReactionTypeViewModel(account) as UpdateReactionTypeViewModel - } + class Factory( + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): UpdateReactionTypeViewModel = UpdateReactionTypeViewModel(account) as UpdateReactionTypeViewModel } } @@ -224,7 +220,7 @@ fun UpdateReactionTypeDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.new_reaction_symbol)) }, + label = { Text(text = stringRes(R.string.new_reaction_symbol)) }, value = postViewModel.nextChoice, onValueChange = { postViewModel.nextChoice = it }, keyboardOptions = @@ -250,7 +246,7 @@ fun UpdateReactionTypeDialog( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(text = stringResource(R.string.add), color = Color.White) + Text(text = stringRes(R.string.add), color = Color.White) } } } @@ -302,12 +298,8 @@ private fun RenderReactionOption( } else { when (reactionType) { "+" -> { - Icon( - painter = painterResource(R.drawable.ic_liked), - null, - modifier = remember { Modifier.size(16.dp) }, - tint = MaterialTheme.colorScheme.onBackground, - ) + LikedIcon(modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.onBackground) + Text( text = " ✖", color = MaterialTheme.colorScheme.onBackground, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index 9c27ff91dd..c41dedb185 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -72,7 +72,6 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -95,6 +94,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.screen.loggedIn.getFragmentActivity +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Font14SP @@ -108,7 +108,9 @@ import com.vitorpamplona.quartz.events.LnZapEvent import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException -class UpdateZapAmountViewModel(val account: Account) : ViewModel() { +class UpdateZapAmountViewModel( + val account: Account, +) : ViewModel() { var nextAmount by mutableStateOf(TextFieldValue("")) var amountSet by mutableStateOf(listOf()) var walletConnectRelay by mutableStateOf(TextFieldValue("")) @@ -134,9 +136,7 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() { this.selectedZapType = account.defaultZapType } - fun toListOfAmounts(commaSeparatedAmounts: String): List { - return commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 } - } + fun toListOfAmounts(commaSeparatedAmounts: String): List = commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 } fun addAmount() { val newValue = nextAmount.text.trim().toLongOrNull() @@ -193,15 +193,14 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() { nextAmount = TextFieldValue("") } - fun hasChanged(): Boolean { - return ( + fun hasChanged(): Boolean = + ( selectedZapType != account?.defaultZapType || amountSet != account?.zapAmountChoices || walletConnectPubkey.text != (account?.zapPaymentRequest?.pubKeyHex ?: "") || walletConnectRelay.text != (account?.zapPaymentRequest?.relayUri ?: "") || walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: "") ) - } fun updateNIP47(uri: String) { val contact = Nip47WalletConnect.parse(uri) @@ -212,10 +211,10 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() { } } - class Factory(val account: Account) : ViewModelProvider.Factory { - override fun create(modelClass: Class): UpdateZapAmountViewModel { - return UpdateZapAmountViewModel(account) as UpdateZapAmountViewModel - } + class Factory( + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): UpdateZapAmountViewModel = UpdateZapAmountViewModel(account) as UpdateZapAmountViewModel } } @@ -241,23 +240,23 @@ fun UpdateZapAmountDialog( listOf( Triple( LnZapEvent.ZapType.PUBLIC, - stringResource(id = R.string.zap_type_public), - stringResource(id = R.string.zap_type_public_explainer), + stringRes(id = R.string.zap_type_public), + stringRes(id = R.string.zap_type_public_explainer), ), Triple( LnZapEvent.ZapType.PRIVATE, - stringResource(id = R.string.zap_type_private), - stringResource(id = R.string.zap_type_private_explainer), + stringRes(id = R.string.zap_type_private), + stringRes(id = R.string.zap_type_private_explainer), ), Triple( LnZapEvent.ZapType.ANONYMOUS, - stringResource(id = R.string.zap_type_anonymous), - stringResource(id = R.string.zap_type_anonymous_explainer), + stringRes(id = R.string.zap_type_anonymous), + stringRes(id = R.string.zap_type_anonymous_explainer), ), Triple( LnZapEvent.ZapType.NONZAP, - stringResource(id = R.string.zap_type_nonzap), - stringResource(id = R.string.zap_type_nonzap_explainer), + stringRes(id = R.string.zap_type_nonzap), + stringRes(id = R.string.zap_type_nonzap_explainer), ), ) @@ -274,13 +273,13 @@ fun UpdateZapAmountDialog( } catch (e: IllegalArgumentException) { if (e.message != null) { accountViewModel.toast( - context.getString(R.string.error_parsing_nip47_title), - context.getString(R.string.error_parsing_nip47, nip47uri, e.message!!), + stringRes(context, R.string.error_parsing_nip47_title), + stringRes(context, R.string.error_parsing_nip47, nip47uri, e.message!!), ) } else { accountViewModel.toast( - context.getString(R.string.error_parsing_nip47_title), - context.getString(R.string.error_parsing_nip47_no_error, nip47uri), + stringRes(context, R.string.error_parsing_nip47_title), + stringRes(context, R.string.error_parsing_nip47_no_error, nip47uri), ) } } @@ -367,7 +366,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.new_amount_in_sats)) }, + label = { Text(text = stringRes(R.string.new_amount_in_sats)) }, value = postViewModel.nextAmount, onValueChange = { postViewModel.nextAmount = it }, keyboardOptions = @@ -393,7 +392,7 @@ fun UpdateZapAmountDialog( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(text = stringResource(R.string.add), color = Color.White) + Text(text = stringRes(R.string.add), color = Color.White) } } @@ -402,7 +401,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { TextSpinner( - label = stringResource(id = R.string.zap_type_explainer), + label = stringRes(id = R.string.zap_type_explainer), placeholder = zapTypes.filter { it.first == accountViewModel.defaultZapType() }.first().second, options = zapOptions, @@ -423,7 +422,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { Text( - stringResource(id = R.string.wallet_connect_service), + stringRes(id = R.string.wallet_connect_service), Modifier.weight(1f), ) @@ -447,7 +446,7 @@ fun UpdateZapAmountDialog( ) { Icon( painter = painterResource(R.drawable.alby), - contentDescription = stringResource(id = R.string.accessibility_navigate_to_alby), + contentDescription = stringRes(id = R.string.accessibility_navigate_to_alby), modifier = Modifier.size(24.dp), tint = Color.Unspecified, ) @@ -460,7 +459,7 @@ fun UpdateZapAmountDialog( ) { Icon( Icons.Default.ContentPaste, - contentDescription = stringResource(id = R.string.paste_from_clipboard), + contentDescription = stringRes(id = R.string.paste_from_clipboard), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -469,7 +468,7 @@ fun UpdateZapAmountDialog( IconButton(onClick = { qrScanning = true }) { Icon( painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringResource(id = R.string.accessibility_scan_qr_code), + contentDescription = stringRes(id = R.string.accessibility_scan_qr_code), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.primary, ) @@ -481,7 +480,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { Text( - stringResource(id = R.string.wallet_connect_service_explainer), + stringRes(id = R.string.wallet_connect_service_explainer), Modifier.weight(1f), color = MaterialTheme.colorScheme.placeholderText, fontSize = Font14SP, @@ -497,13 +496,13 @@ fun UpdateZapAmountDialog( } catch (e: IllegalArgumentException) { if (e.message != null) { accountViewModel.toast( - context.getString(R.string.error_parsing_nip47_title), - context.getString(R.string.error_parsing_nip47, it, e.message!!), + stringRes(context, R.string.error_parsing_nip47_title), + stringRes(context, R.string.error_parsing_nip47, it, e.message!!), ) } else { accountViewModel.toast( - context.getString(R.string.error_parsing_nip47_title), - context.getString(R.string.error_parsing_nip47_no_error, it), + stringRes(context, R.string.error_parsing_nip47_title), + stringRes(context, R.string.error_parsing_nip47_no_error, it), ) } } @@ -516,7 +515,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.wallet_connect_service_pubkey)) }, + label = { Text(text = stringRes(R.string.wallet_connect_service_pubkey)) }, value = postViewModel.walletConnectPubkey, onValueChange = { postViewModel.walletConnectPubkey = it }, keyboardOptions = @@ -539,7 +538,7 @@ fun UpdateZapAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.wallet_connect_service_relay)) }, + label = { Text(text = stringRes(R.string.wallet_connect_service_relay)) }, modifier = Modifier.weight(1f), value = postViewModel.walletConnectRelay, onValueChange = { postViewModel.walletConnectRelay = it }, @@ -559,21 +558,20 @@ fun UpdateZapAmountDialog( val context = LocalContext.current val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - result: ActivityResult -> + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { showPassword = true } } - val authTitle = stringResource(id = R.string.wallet_connect_service_show_secret) + val authTitle = stringRes(id = R.string.wallet_connect_service_show_secret) Row( modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.wallet_connect_service_secret)) }, + label = { Text(text = stringRes(R.string.wallet_connect_service_secret)) }, modifier = Modifier.weight(1f), value = postViewModel.walletConnectSecret, onValueChange = { postViewModel.walletConnectSecret = it }, @@ -585,7 +583,7 @@ fun UpdateZapAmountDialog( ), placeholder = { Text( - text = stringResource(R.string.wallet_connect_service_secret_placeholder), + text = stringRes(R.string.wallet_connect_service_secret_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -614,9 +612,9 @@ fun UpdateZapAmountDialog( }, contentDescription = if (showPassword) { - stringResource(R.string.show_password) + stringRes(R.string.show_password) } else { - stringResource( + stringRes( R.string.hide_password, ) }, @@ -654,7 +652,7 @@ fun authenticate( fun keyguardPrompt() { val intent = keyguardManager.createConfirmDeviceCredentialIntent( - context.getString(R.string.app_name_release), + stringRes(context, R.string.app_name_release), title, ) @@ -672,8 +670,9 @@ fun authenticate( BiometricManager.Authenticators.DEVICE_CREDENTIAL val promptInfo = - BiometricPrompt.PromptInfo.Builder() - .setTitle(context.getString(R.string.app_name_release)) + BiometricPrompt.PromptInfo + .Builder() + .setTitle(stringRes(context, R.string.app_name_release)) .setSubtitle(title) .setAllowedAuthenticators(authenticators) .build() @@ -693,8 +692,9 @@ fun authenticate( BiometricPrompt.ERROR_LOCKOUT -> keyguardPrompt() else -> onError( - context.getString(R.string.biometric_authentication_failed), - context.getString( + stringRes(context, R.string.biometric_authentication_failed), + stringRes( + context, R.string.biometric_authentication_failed_explainer_with_error, errString, ), @@ -705,8 +705,8 @@ fun authenticate( override fun onAuthenticationFailed() { super.onAuthenticationFailed() onError( - context.getString(R.string.biometric_authentication_failed), - context.getString(R.string.biometric_authentication_failed_explainer), + stringRes(context, R.string.biometric_authentication_failed), + stringRes(context, R.string.biometric_authentication_failed_explainer), ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 89ef9551f7..b6914a028d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -51,7 +51,7 @@ fun UserCompose( UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav) Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) } + Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) } AboutDisplay(baseUser) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt index 75749a6078..8d21675809 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt @@ -20,9 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -30,26 +27,24 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Dp import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.ChatroomKey @Composable @@ -73,9 +68,9 @@ fun NoteAuthorPicture( modifier: Modifier = Modifier, onClick: ((User) -> Unit)? = null, ) { - WatchAuthorWithBlank(baseNote) { + WatchAuthorWithBlank(baseNote, accountViewModel = accountViewModel) { if (it == null) { - DisplayBlankAuthor(size, modifier) + DisplayBlankAuthor(size, modifier, accountViewModel) } else { ClickableUserPicture(it, size, accountViewModel, modifier, onClick) } @@ -86,6 +81,7 @@ fun NoteAuthorPicture( fun DisplayBlankAuthor( size: Dp, modifier: Modifier = Modifier, + accountViewModel: AccountViewModel, ) { val nullModifier = remember { @@ -94,8 +90,9 @@ fun DisplayBlankAuthor( RobohashAsyncImage( robot = "authornotfound", - contentDescription = stringResource(R.string.unknown_author), + contentDescription = stringRes(R.string.unknown_author), modifier = nullModifier, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } @@ -120,6 +117,7 @@ fun UserPicture( DisplayBlankAuthor( size, pictureModifier, + accountViewModel, ) } } @@ -161,23 +159,19 @@ fun ClickableUserPicture( .combinedClickable( onClick = { onClick(baseUser) }, onLongClick = { onLongClick(baseUser) }, - role = Role.Button, ) } else if (onClick != null) { Modifier .size(size) .clickable( onClick = { onClick(baseUser) }, - role = Role.Button, ) } else { Modifier.size(size) } } - Box(modifier = myModifier, contentAlignment = Alignment.TopEnd) { - BaseUserPicture(baseUser, size, accountViewModel, modifier) - } + BaseUserPicture(baseUser, size, accountViewModel, modifier, myModifier) } @Composable @@ -301,10 +295,8 @@ fun BaseUserPicture( size: Dp, accountViewModel: AccountViewModel, innerModifier: Modifier = Modifier, - outerModifier: Modifier = remember { Modifier.size(size) }, + outerModifier: Modifier = Modifier.size(size), ) { - val myIconSize by remember(size) { derivedStateOf { size.div(3.5f) } } - Box(outerModifier, contentAlignment = Alignment.TopEnd) { LoadUserProfilePicture(baseUser) { userProfilePicture, userName -> InnerUserPicture( @@ -317,7 +309,11 @@ fun BaseUserPicture( ) } - ObserveAndDisplayFollowingMark(baseUser.pubkeyHex, myIconSize, accountViewModel) + WatchUserFollows(baseUser.pubkeyHex, accountViewModel) { newFollowingState -> + if (newFollowingState) { + FollowingIcon(Modifier.size(size.div(3.5f))) + } + } } } @@ -345,43 +341,22 @@ fun InnerUserPicture( modifier.size(size).clip(shape = CircleShape) } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - RobohashFallbackAsyncImage( robot = userHex, model = userPicture, contentDescription = if (userName != null) { - stringResource(id = R.string.profile_image_of_user, userName) + stringRes(id = R.string.profile_image_of_user, userName) } else { - stringResource(id = R.string.profile_image) + stringRes(id = R.string.profile_image) }, modifier = myImageModifier, contentScale = ContentScale.Crop, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } -@Composable -fun ObserveAndDisplayFollowingMark( - userHex: String, - iconSize: Dp, - accountViewModel: AccountViewModel, -) { - WatchUserFollows(userHex, accountViewModel) { newFollowingState -> - AnimatedVisibility( - visible = newFollowingState, - enter = remember { fadeIn() }, - exit = remember { fadeOut() }, - ) { - FollowingIcon(iconSize) - } - } -} - @Composable fun WatchUserFollows( userHex: String, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 8e96921779..4d07867c41 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -39,8 +39,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.CenterVertically 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.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -61,6 +59,7 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.RoyalBlue import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -93,11 +92,14 @@ fun UserReactionsRow( ) { Row( verticalAlignment = CenterVertically, - modifier = Modifier.clickable(onClick = onClick).padding(10.dp), + modifier = + Modifier + .clickable(onClick = onClick) + .padding(10.dp), ) { Row(verticalAlignment = CenterVertically, modifier = Modifier.width(68.dp)) { Text( - text = stringResource(id = R.string.today), + text = stringRes(id = R.string.today), fontWeight = FontWeight.Bold, ) @@ -131,7 +133,7 @@ fun UserReactionsRow( private fun UserZapModel(model: UserReactionsViewModel) { Icon( imageVector = Icons.Default.Bolt, - contentDescription = stringResource(R.string.zaps), + contentDescription = stringRes(R.string.zaps), modifier = Size24Modifier, tint = BitcoinOrange, ) @@ -143,12 +145,7 @@ private fun UserZapModel(model: UserReactionsViewModel) { @Composable private fun UserReactionModel(model: UserReactionsViewModel) { - Icon( - painter = painterResource(R.drawable.ic_liked), - null, - modifier = Size20Modifier, - tint = Color.Unspecified, - ) + LikedIcon(modifier = Size20Modifier) Spacer(modifier = StdHorzSpacer) @@ -157,9 +154,7 @@ private fun UserReactionModel(model: UserReactionsViewModel) { @Composable private fun UserBoostModel(model: UserReactionsViewModel) { - Icon( - painter = painterResource(R.drawable.ic_retweeted), - null, + RepostedIcon( modifier = Size24Modifier, tint = Color.Unspecified, ) @@ -171,12 +166,7 @@ private fun UserBoostModel(model: UserReactionsViewModel) { @Composable private fun UserReplyModel(model: UserReactionsViewModel) { - Icon( - painter = painterResource(R.drawable.ic_comment), - null, - modifier = Size20Modifier, - tint = RoyalBlue, - ) + CommentIcon(Size20Modifier, RoyalBlue) Spacer(modifier = StdHorzSpacer) @@ -184,7 +174,9 @@ private fun UserReplyModel(model: UserReactionsViewModel) { } @Stable -class UserReactionsViewModel(val account: Account) : ViewModel() { +class UserReactionsViewModel( + val account: Account, +) : ViewModel() { val user: User = account.userProfile() private var _reactions = MutableStateFlow>(emptyMap()) @@ -213,11 +205,10 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { var shouldShowDecimalsInAxis = false - fun formatDate(createAt: Long): String { - return sdf.format( + fun formatDate(createAt: Long): String = + sdf.format( Instant.ofEpochSecond(createAt).atZone(ZoneId.systemDefault()).toLocalDateTime(), ) - } fun today() = sdf.format(LocalDateTime.now()) @@ -452,10 +443,10 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { super.onCleared() } - class Factory(val account: Account) : ViewModelProvider.Factory { - override fun create(modelClass: Class): UserReactionsViewModel { - return UserReactionsViewModel(account) as UserReactionsViewModel - } + class Factory( + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): UserReactionsViewModel = UserReactionsViewModel(account) as UserReactionsViewModel } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 7f3db2f47b..b2c2084ce6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Context import android.util.Log -import androidx.compose.animation.Crossfade import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -40,7 +39,9 @@ import androidx.lifecycle.LifecycleOwner import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.tts.TextToSpeechHelper +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.StdButtonSizeModifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -50,9 +51,10 @@ fun NoteUsernameDisplay( baseNote: Note, weight: Modifier = Modifier, textColor: Color = Color.Unspecified, + accountViewModel: AccountViewModel, ) { WatchAuthor(baseNote) { - UsernameDisplay(it, weight, textColor = textColor) + UsernameDisplay(it, weight, textColor = textColor, accountViewModel = accountViewModel) } } @@ -77,6 +79,7 @@ fun WatchAuthor( fun WatchAuthorWithBlank( baseNote: Note, modifier: Modifier = Modifier, + accountViewModel: AccountViewModel, inner: @Composable (User?) -> Unit, ) { val noteAuthor = baseNote.author @@ -85,7 +88,7 @@ fun WatchAuthorWithBlank( } else { val authorState by baseNote.live().metadata.observeAsState() - Crossfade(targetState = authorState?.note?.author, modifier = modifier, label = "WatchAuthorWithBlank") { newAuthor -> + CrossfadeIfEnabled(targetState = authorState?.note?.author, modifier = modifier, label = "WatchAuthorWithBlank", accountViewModel = accountViewModel) { newAuthor -> inner(newAuthor) } } @@ -97,10 +100,11 @@ fun UsernameDisplay( weight: Modifier = Modifier, fontWeight: FontWeight = FontWeight.Bold, textColor: Color = Color.Unspecified, + accountViewModel: AccountViewModel, ) { val userMetadata by baseUser.live().userMetadataInfo.observeAsState(baseUser.info) - Crossfade(targetState = userMetadata, modifier = weight, label = "UsernameDisplay") { + CrossfadeIfEnabled(targetState = userMetadata, modifier = weight, label = "UsernameDisplay", accountViewModel = accountViewModel) { val name = it?.bestName() if (name != null) { UserDisplay(name, it.tags, weight, fontWeight, textColor) @@ -164,7 +168,8 @@ private fun speak( context: Context, owner: LifecycleOwner, ) { - TextToSpeechHelper.getInstance(context) + TextToSpeechHelper + .getInstance(context) .registerLifecycle(owner) .speak(message) .highlight() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt index 06dc89eb7e..33db44e8b1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import androidx.compose.animation.Crossfade import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.runtime.Composable @@ -29,6 +28,7 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @OptIn(ExperimentalFoundationApi::class) @@ -57,6 +57,7 @@ fun WatchNoteEvent( ) } }, + accountViewModel = accountViewModel, ) } @@ -65,6 +66,7 @@ fun WatchNoteEvent( baseNote: Note, onNoteEventFound: @Composable () -> Unit, onBlank: @Composable () -> Unit, + accountViewModel: AccountViewModel, ) { if (baseNote.event != null) { onNoteEventFound() @@ -72,7 +74,7 @@ fun WatchNoteEvent( // avoid observing costs if already has an event. val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null) - Crossfade(targetState = hasEvent, label = "Event presence") { + CrossfadeIfEnabled(targetState = hasEvent, label = "Event presence", accountViewModel = accountViewModel) { if (it) { onNoteEventFound() } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 84e592d7ad..cb2a9d40a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -57,7 +57,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -78,6 +77,7 @@ import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer @@ -102,13 +102,9 @@ class ZapOptionstViewModel : ViewModel() { this.account = account } - fun canSend(): Boolean { - return value() != null - } + fun canSend(): Boolean = value() != null - fun value(): Long? { - return customAmount.text.trim().toLongOrNull() - } + fun value(): Long? = customAmount.text.trim().toLongOrNull() fun cancel() {} } @@ -131,23 +127,23 @@ fun ZapCustomDialog( listOf( Triple( LnZapEvent.ZapType.PUBLIC, - stringResource(id = R.string.zap_type_public), - stringResource(id = R.string.zap_type_public_explainer), + stringRes(id = R.string.zap_type_public), + stringRes(id = R.string.zap_type_public_explainer), ), Triple( LnZapEvent.ZapType.PRIVATE, - stringResource(id = R.string.zap_type_private), - stringResource(id = R.string.zap_type_private_explainer), + stringRes(id = R.string.zap_type_private), + stringRes(id = R.string.zap_type_private_explainer), ), Triple( LnZapEvent.ZapType.ANONYMOUS, - stringResource(id = R.string.zap_type_anonymous), - stringResource(id = R.string.zap_type_anonymous_explainer), + stringRes(id = R.string.zap_type_anonymous), + stringRes(id = R.string.zap_type_anonymous_explainer), ), Triple( LnZapEvent.ZapType.NONZAP, - stringResource(id = R.string.zap_type_nonzap), - stringResource(id = R.string.zap_type_nonzap_explainer), + stringRes(id = R.string.zap_type_nonzap), + stringRes(id = R.string.zap_type_nonzap_explainer), ), ) @@ -204,8 +200,8 @@ fun ZapCustomDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - // stringResource(R.string.new_amount_in_sats - label = { Text(text = stringResource(id = R.string.amount_in_sats)) }, + // stringRes(R.string.new_amount_in_sats + label = { Text(text = stringRes(id = R.string.amount_in_sats)) }, value = postViewModel.customAmount, onValueChange = { postViewModel.customAmount = it }, keyboardOptions = @@ -224,7 +220,7 @@ fun ZapCustomDialog( ) TextSpinner( - label = stringResource(id = R.string.zap_type), + label = stringRes(id = R.string.zap_type), placeholder = zapTypes .filter { it.first == accountViewModel.account.defaultZapType } @@ -241,17 +237,17 @@ fun ZapCustomDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - // stringResource(R.string.new_amount_in_sats + // stringRes(R.string.new_amount_in_sats label = { if ( selectedZapType == LnZapEvent.ZapType.PUBLIC || selectedZapType == LnZapEvent.ZapType.ANONYMOUS ) { - Text(text = stringResource(id = R.string.custom_zaps_add_a_message)) + Text(text = stringRes(id = R.string.custom_zaps_add_a_message)) } else if (selectedZapType == LnZapEvent.ZapType.PRIVATE) { - Text(text = stringResource(id = R.string.custom_zaps_add_a_message_private)) + Text(text = stringRes(id = R.string.custom_zaps_add_a_message_private)) } else if (selectedZapType == LnZapEvent.ZapType.NONZAP) { - Text(text = stringResource(id = R.string.custom_zaps_add_a_message_nonzap)) + Text(text = stringRes(id = R.string.custom_zaps_add_a_message_nonzap)) } }, value = postViewModel.customMessage, @@ -263,7 +259,7 @@ fun ZapCustomDialog( ), placeholder = { Text( - text = stringResource(id = R.string.custom_zaps_add_a_message_example), + text = stringRes(id = R.string.custom_zaps_add_a_message_example), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -317,7 +313,7 @@ fun ErrorMessageDialog( contentDescription = null, ) Spacer(StdHorzSpacer) - Text(stringResource(R.string.error_dialog_talk_to_user)) + Text(stringRes(R.string.error_dialog_talk_to_user)) } } Button( @@ -333,7 +329,7 @@ fun ErrorMessageDialog( contentDescription = null, ) Spacer(StdHorzSpacer) - Text(stringResource(R.string.error_dialog_button_ok)) + Text(stringRes(R.string.error_dialog_button_ok)) } } } @@ -383,17 +379,17 @@ fun PayViaIntentDialog( if (it.user != null) { BaseUserPicture(it.user, Size55dp, accountViewModel = accountViewModel) } else { - DisplayBlankAuthor(size = Size55dp) + DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel) } Spacer(modifier = DoubleHorzSpacer) Column(modifier = Modifier.weight(1f)) { if (it.user != null) { - UsernameDisplay(it.user) + UsernameDisplay(it.user, accountViewModel = accountViewModel) } else { Text( - text = stringResource(id = R.string.wallet_number, index + 1), + text = stringRes(id = R.string.wallet_number, index + 1), maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold, @@ -410,7 +406,7 @@ fun PayViaIntentDialog( ) Spacer(modifier = StdHorzSpacer) Text( - text = stringResource(id = R.string.sats), + text = stringRes(id = R.string.sats), maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold, @@ -448,9 +444,9 @@ fun payViaIntent( if (e is CancellationException) throw e // don't display ugly error messages // if (e.message != null) { - // onError(context.getString(R.string.no_wallet_found_with_error, e.message!!)) + // onError(stringRes(context, R.string.no_wallet_found_with_error, e.message!!)) // } else { - onError(context.getString(R.string.no_wallet_found)) + onError(stringRes(context, R.string.no_wallet_found)) // } } } @@ -472,9 +468,9 @@ fun PayButton( contentPadding = ZeroPadding, ) { if (isActive) { - Text(text = stringResource(R.string.pay), color = Color.White) + Text(text = stringRes(R.string.pay), color = Color.White) } else { - Text(text = stringResource(R.string.paid), color = Color.White) + Text(text = stringRes(R.string.paid), color = Color.White) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 1ede76f829..f085925ede 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -65,7 +65,10 @@ fun ZapNoteCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val baseNoteRequest by baseReqResponse.zapRequest.live().metadata.observeAsState() + val baseNoteRequest by baseReqResponse.zapRequest + .live() + .metadata + .observeAsState() var baseAuthor by remember { mutableStateOf(null) } @@ -115,7 +118,7 @@ private fun RenderZapNote( Column( modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }, ) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor) } + Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor, accountViewModel = accountViewModel) } Row(verticalAlignment = Alignment.CenterVertically) { AboutDisplay(baseAuthor) } } @@ -179,7 +182,11 @@ fun ShowFollowingOrUnfollowingButton( accountViewModel: AccountViewModel, ) { var isFollowing by remember { mutableStateOf(false) } - val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState() + val accountFollowsState by accountViewModel.account + .userProfile() + .live() + .follows + .observeAsState() LaunchedEffect(key1 = accountFollowsState) { launch(Dispatchers.Default) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt index 3a440f4d18..edb150193a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt @@ -102,7 +102,7 @@ fun ZapUserSetCompose( ) Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user) } + Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user, accountViewModel = accountViewModel) } AboutDisplay(zapSetCard.user) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt index 6a99919529..5d12dabfae 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -47,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.actions.relays.AddDMRelayListDialog import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BigPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -159,7 +159,7 @@ fun AddInboxRelayForDMCard( ) { // Title Text( - text = stringResource(id = R.string.dm_relays_not_found), + text = stringRes(id = R.string.dm_relays_not_found), style = TextStyle( fontSize = 20.sp, @@ -170,13 +170,13 @@ fun AddInboxRelayForDMCard( Spacer(modifier = StdVertSpacer) Text( - text = stringResource(id = R.string.dm_relays_not_found_description), + text = stringRes(id = R.string.dm_relays_not_found_description), ) Spacer(modifier = StdVertSpacer) Text( - text = stringResource(id = R.string.dm_relays_not_found_examples), + text = stringRes(id = R.string.dm_relays_not_found_examples), ) Spacer(modifier = StdVertSpacer) @@ -192,7 +192,7 @@ fun AddInboxRelayForDMCard( }, modifier = Modifier.fillMaxWidth(), ) { - Text(text = stringResource(id = R.string.dm_relays_not_found_create_now)) + Text(text = stringRes(id = R.string.dm_relays_not_found_create_now)) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt index e04249676c..42edc49ebb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -47,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.actions.relays.AddSearchRelayListDialog import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BigPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -159,7 +159,7 @@ fun AddInboxRelayForSearchCard( ) { // Title Text( - text = stringResource(id = R.string.search_relays_not_found), + text = stringRes(id = R.string.search_relays_not_found), style = TextStyle( fontSize = 20.sp, @@ -170,7 +170,7 @@ fun AddInboxRelayForSearchCard( Spacer(modifier = StdVertSpacer) Text( - text = stringResource(id = R.string.search_relays_not_found_description), + text = stringRes(id = R.string.search_relays_not_found_description), ) Spacer(modifier = StdVertSpacer) @@ -186,7 +186,7 @@ fun AddInboxRelayForSearchCard( }, modifier = Modifier.fillMaxWidth(), ) { - Text(text = stringResource(id = R.string.dm_relays_not_found_create_now)) + Text(text = stringRes(id = R.string.dm_relays_not_found_create_now)) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt index e926d6421e..d996c57f63 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt @@ -29,11 +29,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn @@ -73,7 +73,7 @@ fun AddButton( enabled = isActive, contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp), ) { - Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center) + Text(text = stringRes(text), color = Color.White, textAlign = TextAlign.Center) } } @@ -93,6 +93,6 @@ fun RemoveButton( enabled = isActive, contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp), ) { - Text(text = stringResource(R.string.remove), color = Color.White) + Text(text = stringRes(R.string.remove), color = Color.White) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt index acb98798c6..47bd833952 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/BoostedMark.kt @@ -23,16 +23,16 @@ package com.vitorpamplona.amethyst.ui.note.elements import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable fun BoostedMark() { Text( - stringResource(id = R.string.boosted), + stringRes(id = R.string.boosted), fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.placeholderText, maxLines = 1, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt index 3e62441f70..340750b595 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R @@ -40,6 +39,7 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.note.BaseUserPicture import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.authorNotePictureForImageHeader @@ -69,7 +69,7 @@ fun BannerImage( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), @@ -80,7 +80,7 @@ fun BannerImage( } ?: run { Image( painter = painterResource(R.drawable.profile_banner), - contentDescription = stringResource(R.string.profile_banner), + contentDescription = stringRes(R.string.profile_banner), contentScale = ContentScale.Crop, modifier = imageModifier, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt index 0981fc8468..73df0737a3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayEditStatus.kt @@ -24,11 +24,11 @@ import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.types.EditState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -38,11 +38,11 @@ fun DisplayEditStatus(editState: EditState) { text = buildAnnotatedString { if (editState.showingVersion.value == editState.originalVersionId()) { - append(stringResource(id = R.string.original)) + append(stringRes(id = R.string.original)) } else if (editState.showingVersion.value == editState.lastVersionId()) { - append(stringResource(id = R.string.edited)) + append(stringRes(id = R.string.edited)) } else { - append(stringResource(id = R.string.edited_number, editState.versionId())) + append(stringRes(id = R.string.edited_number, editState.versionId())) } }, onClick = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt index 4c3488c646..0187257137 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import com.vitorpamplona.amethyst.R @@ -37,6 +36,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.LoadOts import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import java.text.SimpleDateFormat @@ -65,7 +65,7 @@ fun DisplayOts( text = buildAnnotatedString { append( - stringResource( + stringRes( id = R.string.existed_since, timeStr, ), @@ -76,8 +76,9 @@ fun DisplayOts( SimpleDateFormat.getDateTimeInstance().format(Date(unixtimestamp * 1000)) accountViewModel.toast( - context.getString(R.string.ots_info_title), - context.getString(R.string.ots_info_description, fullDateTime), + R.string.ots_info_title, + R.string.ots_info_description, + fullDateTime, ) }, style = @@ -91,7 +92,7 @@ fun DisplayOts( }, whenPending = { Text( - stringResource(id = R.string.timestamp_pending_short), + stringRes(id = R.string.timestamp_pending_short), color = MaterialTheme.colorScheme.lessImportantLink, fontSize = Font14SP, fontWeight = FontWeight.Bold, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt index e3720abd46..e64b198223 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt @@ -48,7 +48,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -68,13 +67,16 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon import com.vitorpamplona.amethyst.ui.note.ZappedIcon import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.math.BigDecimal -@Stable data class Reward(val amount: BigDecimal) +@Stable data class Reward( + val amount: BigDecimal, +) @Composable fun DisplayReward( @@ -199,9 +201,7 @@ class AddBountyAmountViewModel : ViewModel() { nextAmount = TextFieldValue("") } - fun hasChanged(): Boolean { - return nextAmount.text.trim().toLongOrNull() != null - } + fun hasChanged(): Boolean = nextAmount.text.trim().toLongOrNull() != null } @Composable @@ -254,7 +254,7 @@ fun AddBountyAmountDialog( verticalAlignment = Alignment.CenterVertically, ) { OutlinedTextField( - label = { Text(text = stringResource(R.string.pledge_amount_in_sats)) }, + label = { Text(text = stringRes(R.string.pledge_amount_in_sats)) }, value = postViewModel.nextAmount, onValueChange = { postViewModel.nextAmount = it }, keyboardOptions = diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayUncitedHashtags.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayUncitedHashtags.kt index 551f606898..a65af11051 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayUncitedHashtags.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayUncitedHashtags.kt @@ -26,23 +26,74 @@ import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.text.AnnotatedString +import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment +import com.vitorpamplona.amethyst.service.CachedRichTextParser import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.ui.theme.lessImportantLink -import kotlinx.collections.immutable.ImmutableList +import com.vitorpamplona.quartz.events.Event +import com.vitorpamplona.quartz.events.toImmutableListOfLists +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun DisplayUncitedHashtags( + event: Event, + nav: (String) -> Unit, +) { + DisplayUncitedHashtags(event, event.content, nav) +} @OptIn(ExperimentalLayoutApi::class) @Composable fun DisplayUncitedHashtags( - hashtags: ImmutableList, - eventContent: String, + event: Event, + content: String, nav: (String) -> Unit, ) { - val unusedHashtags = - remember(eventContent) { hashtags.filter { !eventContent.contains(it, true) } } + val unusedHashtags by + produceState(initialValue = emptyList()) { + val tagsInEvent = event.hashtags() + if (tagsInEvent.isNotEmpty()) { + launch(Dispatchers.Default) { + val state = CachedRichTextParser.parseText(content, event.tags.toImmutableListOfLists()) + + val tagsInContent = + state + .paragraphs + .map { + it.words.mapNotNull { + if (it is HashTagSegment) { + it.hashtag + } else { + null + } + } + }.flatten() + + val unusedHashtags = + tagsInEvent.filterNot { eventTag -> + tagsInContent.any { contentTag -> + eventTag.equals(contentTag, true) + } + } + + if (unusedHashtags.isNotEmpty()) { + value = unusedHashtags + } + } + } + } if (unusedHashtags.isNotEmpty()) { + val style = + LocalTextStyle.current.copy( + color = MaterialTheme.colorScheme.lessImportantLink, + ) + FlowRow( modifier = HalfTopPadding, ) { @@ -50,10 +101,7 @@ fun DisplayUncitedHashtags( ClickableText( text = remember { AnnotatedString("#$hashtag ") }, onClick = { nav("Hashtag/$hashtag") }, - style = - LocalTextStyle.current.copy( - color = MaterialTheme.colorScheme.lessImportantLink, - ), + style = style, ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayZapSplits.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayZapSplits.kt index f674417375..74aafc6618 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayZapSplits.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayZapSplits.kt @@ -24,12 +24,10 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.text.AnnotatedString diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt index fbc0d82985..1c251469dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt @@ -24,12 +24,10 @@ import android.content.Intent import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -39,19 +37,20 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.core.content.ContextCompat import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.EditPostView import com.vitorpamplona.amethyst.ui.actions.NewPostView +import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.ui.note.externalLinkForNote import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size24Modifier import com.vitorpamplona.quartz.events.TextNoteEvent @@ -67,19 +66,21 @@ fun MoreOptionsButton( ) { val popupExpanded = remember { mutableStateOf(false) } - IconButton( + ClickableBox( modifier = Size24Modifier, onClick = { popupExpanded.value = true }, ) { - VerticalDotsIcon(R.string.note_options) + VerticalDotsIcon() - NoteDropDownMenu( - baseNote, - popupExpanded, - editState, - accountViewModel, - nav, - ) + if (popupExpanded.value) { + NoteDropDownMenu( + note = baseNote, + onDismiss = { popupExpanded.value = false }, + editState = editState, + accountViewModel = accountViewModel, + nav = nav, + ) + } } } @@ -96,7 +97,7 @@ data class DropDownParams( @Composable fun NoteDropDownMenu( note: Note, - popupExpanded: MutableState, + onDismiss: () -> Unit, editState: State>? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -116,8 +117,6 @@ fun NoteDropDownMenu( ) } - val onDismiss = remember(popupExpanded) { { popupExpanded.value = false } } - val wantsToEditPost = remember { mutableStateOf(false) @@ -137,7 +136,7 @@ fun NoteDropDownMenu( EditPostView( onClose = { - popupExpanded.value = false + onDismiss() wantsToEditPost.value = false }, edit = note, @@ -150,7 +149,7 @@ fun NoteDropDownMenu( if (wantsToEditDraft.value) { NewPostView( onClose = { - popupExpanded.value = false + onDismiss() wantsToEditDraft.value = false }, accountViewModel = accountViewModel, @@ -160,7 +159,7 @@ fun NoteDropDownMenu( } DropdownMenu( - expanded = popupExpanded.value, + expanded = true, onDismissRequest = onDismiss, ) { val clipboardManager = LocalClipboardManager.current @@ -177,7 +176,7 @@ fun NoteDropDownMenu( if (!state.isFollowingAuthor) { DropdownMenuItem( - text = { Text(stringResource(R.string.follow)) }, + text = { Text(stringRes(R.string.follow)) }, onClick = { val author = note.author ?: return@DropdownMenuItem accountViewModel.follow(author) @@ -187,7 +186,7 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) } DropdownMenuItem( - text = { Text(stringResource(R.string.copy_text)) }, + text = { Text(stringRes(R.string.copy_text)) }, onClick = { scope.launch(Dispatchers.IO) { accountViewModel.decrypt(note) { clipboardManager.setText(AnnotatedString(it)) } @@ -196,7 +195,7 @@ fun NoteDropDownMenu( }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.copy_user_pubkey)) }, + text = { Text(stringRes(R.string.copy_user_pubkey)) }, onClick = { scope.launch(Dispatchers.IO) { clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}")) @@ -205,7 +204,7 @@ fun NoteDropDownMenu( }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.copy_note_id)) }, + text = { Text(stringRes(R.string.copy_note_id)) }, onClick = { scope.launch(Dispatchers.IO) { clipboardManager.setText(AnnotatedString("nostr:" + note.toNEvent())) @@ -214,7 +213,7 @@ fun NoteDropDownMenu( }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.quick_action_share)) }, + text = { Text(stringRes(R.string.quick_action_share)) }, onClick = { val sendIntent = Intent().apply { @@ -226,12 +225,12 @@ fun NoteDropDownMenu( ) putExtra( Intent.EXTRA_TITLE, - actContext.getString(R.string.quick_action_share_browser_link), + stringRes(actContext, R.string.quick_action_share_browser_link), ) } val shareIntent = - Intent.createChooser(sendIntent, appContext.getString(R.string.quick_action_share)) + Intent.createChooser(sendIntent, stringRes(actContext, R.string.quick_action_share)) ContextCompat.startActivity(actContext, shareIntent, null) onDismiss() }, @@ -239,7 +238,7 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (state.isLoggedUser && note.isDraft()) { DropdownMenuItem( - text = { Text(stringResource(R.string.edit_draft)) }, + text = { Text(stringRes(R.string.edit_draft)) }, onClick = { wantsToEditDraft.value = true }, @@ -248,14 +247,14 @@ fun NoteDropDownMenu( if (note.event is TextNoteEvent && !note.isDraft()) { if (state.isLoggedUser) { DropdownMenuItem( - text = { Text(stringResource(R.string.edit_post)) }, + text = { Text(stringRes(R.string.edit_post)) }, onClick = { wantsToEditPost.value = true }, ) } else { DropdownMenuItem( - text = { Text(stringResource(R.string.propose_an_edit)) }, + text = { Text(stringRes(R.string.propose_an_edit)) }, onClick = { wantsToEditPost.value = true }, @@ -263,7 +262,7 @@ fun NoteDropDownMenu( } } DropdownMenuItem( - text = { Text(stringResource(R.string.broadcast)) }, + text = { Text(stringRes(R.string.broadcast)) }, onClick = { accountViewModel.broadcast(note) onDismiss() @@ -272,14 +271,14 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (accountViewModel.account.hasPendingAttestations(note)) { DropdownMenuItem( - text = { Text(stringResource(R.string.timestamp_pending)) }, + text = { Text(stringRes(R.string.timestamp_pending)) }, onClick = { onDismiss() }, ) } else { DropdownMenuItem( - text = { Text(stringResource(R.string.timestamp_it)) }, + text = { Text(stringRes(R.string.timestamp_it)) }, onClick = { accountViewModel.timestamp(note) onDismiss() @@ -289,7 +288,7 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (state.isPrivateBookmarkNote) { DropdownMenuItem( - text = { Text(stringResource(R.string.remove_from_private_bookmarks)) }, + text = { Text(stringRes(R.string.remove_from_private_bookmarks)) }, onClick = { accountViewModel.removePrivateBookmark(note) onDismiss() @@ -297,7 +296,7 @@ fun NoteDropDownMenu( ) } else { DropdownMenuItem( - text = { Text(stringResource(R.string.add_to_private_bookmarks)) }, + text = { Text(stringRes(R.string.add_to_private_bookmarks)) }, onClick = { accountViewModel.addPrivateBookmark(note) onDismiss() @@ -306,7 +305,7 @@ fun NoteDropDownMenu( } if (state.isPublicBookmarkNote) { DropdownMenuItem( - text = { Text(stringResource(R.string.remove_from_public_bookmarks)) }, + text = { Text(stringRes(R.string.remove_from_public_bookmarks)) }, onClick = { accountViewModel.removePublicBookmark(note) onDismiss() @@ -314,7 +313,7 @@ fun NoteDropDownMenu( ) } else { DropdownMenuItem( - text = { Text(stringResource(R.string.add_to_public_bookmarks)) }, + text = { Text(stringRes(R.string.add_to_public_bookmarks)) }, onClick = { accountViewModel.addPublicBookmark(note) onDismiss() @@ -324,7 +323,7 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (state.showSensitiveContent == null || state.showSensitiveContent == true) { DropdownMenuItem( - text = { Text(stringResource(R.string.content_warning_hide_all_sensitive_content)) }, + text = { Text(stringRes(R.string.content_warning_hide_all_sensitive_content)) }, onClick = { scope.launch(Dispatchers.IO) { accountViewModel.hideSensitiveContent() @@ -335,7 +334,7 @@ fun NoteDropDownMenu( } if (state.showSensitiveContent == null || state.showSensitiveContent == false) { DropdownMenuItem( - text = { Text(stringResource(R.string.content_warning_show_all_sensitive_content)) }, + text = { Text(stringRes(R.string.content_warning_show_all_sensitive_content)) }, onClick = { scope.launch(Dispatchers.IO) { accountViewModel.disableContentWarnings() @@ -346,7 +345,7 @@ fun NoteDropDownMenu( } if (state.showSensitiveContent != null) { DropdownMenuItem( - text = { Text(stringResource(R.string.content_warning_see_warnings)) }, + text = { Text(stringRes(R.string.content_warning_see_warnings)) }, onClick = { scope.launch(Dispatchers.IO) { accountViewModel.seeContentWarnings() @@ -358,7 +357,7 @@ fun NoteDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (state.isLoggedUser) { DropdownMenuItem( - text = { Text(stringResource(R.string.request_deletion)) }, + text = { Text(stringRes(R.string.request_deletion)) }, onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note) @@ -368,7 +367,7 @@ fun NoteDropDownMenu( ) } else { DropdownMenuItem( - text = { Text(stringResource(R.string.block_report)) }, + text = { Text(stringRes(R.string.block_report)) }, onClick = { reportDialogShowing = true }, ) } @@ -388,8 +387,16 @@ fun WatchBookmarksFollowsAndAccount( accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit, ) { - val followState by accountViewModel.userProfile().live().follows.observeAsState() - val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState() + val followState by accountViewModel + .userProfile() + .live() + .follows + .observeAsState() + val bookmarkState by accountViewModel + .userProfile() + .live() + .bookmarks + .observeAsState() val showSensitiveContent by accountViewModel.showSensitiveContentChanges.observeAsState( accountViewModel.account.showSensitiveContent, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt index dd536c4f97..de50e9a977 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt @@ -31,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow import com.vitorpamplona.amethyst.R @@ -41,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.nip05 @@ -90,7 +90,7 @@ fun ForkInformationRowLightColor( ClickableText( text = buildAnnotatedString { - append(stringResource(id = R.string.forked_from)) + append(stringRes(id = R.string.forked_from)) append(" ") }, onClick = { nav(route) }, @@ -139,7 +139,7 @@ fun ForkInformationRow( val author = note.author ?: return val meta by author.live().userMetadataInfo.observeAsState(author.info) - Text(stringResource(id = R.string.forked_from)) + Text(stringRes(id = R.string.forked_from)) Spacer(modifier = StdHorzSpacer) val userMetadata by author.live().userMetadataInfo.observeAsState() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt index 41ff8fe337..7bbf84b532 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.note.elements import android.content.Context -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -50,7 +49,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString @@ -68,6 +66,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.ThemeType import com.vitorpamplona.amethyst.service.ZapPaymentHandler +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.ClickableText import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.navigation.routeFor @@ -81,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon import com.vitorpamplona.amethyst.ui.note.ZappedIcon import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -226,7 +226,7 @@ fun ZapTheDevsCard( horizontalArrangement = Arrangement.SpaceBetween, ) { Text( - text = stringResource(id = R.string.zap_the_devs_title), + text = stringRes(id = R.string.zap_the_devs_title), style = TextStyle( fontSize = 20.sp, @@ -247,7 +247,7 @@ fun ZapTheDevsCard( ClickableText( text = buildAnnotatedString { - append(stringResource(id = R.string.zap_the_devs_description, BuildConfig.VERSION_NAME)) + append(stringRes(id = R.string.zap_the_devs_description, BuildConfig.VERSION_NAME)) append(" ") withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) { append("#value4value") @@ -270,14 +270,14 @@ fun ZapTheDevsCard( text = buildAnnotatedString { withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) { - append(stringResource(id = R.string.version_name, BuildConfig.VERSION_NAME.substringBefore("-"))) + append(stringRes(id = R.string.version_name, BuildConfig.VERSION_NAME.substringBefore("-"))) } - append(" " + stringResource(id = R.string.brought_to_you_by)) + append(" " + stringRes(id = R.string.brought_to_you_by)) }, onClick = { nav(route) }, ) } else { - Text(stringResource(id = R.string.this_version_brought_to_you_by)) + Text(stringRes(id = R.string.this_version_brought_to_you_by)) } Spacer(modifier = StdVertSpacer) @@ -377,7 +377,7 @@ fun ZapDonationButton( if (showErrorMessageDialog != null) { ErrorMessageDialog( - title = stringResource(id = R.string.error_dialog_zap_error), + title = stringRes(id = R.string.error_dialog_zap_error), textContent = showErrorMessageDialog ?: "", onClickStartMessage = { baseNote.author?.let { @@ -424,8 +424,7 @@ fun ZapDonationButton( targetValue = zappingProgress, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, label = "ZapIconIndicator", - ) - .value, + ).value, modifier = remember { Modifier.size(animationSize) }, strokeWidth = 2.dp, color = grayTint, @@ -443,7 +442,7 @@ fun ZapDonationButton( } } - Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") { + CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) { if (it) { ZappedIcon(iconSizeModifier) } else { @@ -455,9 +454,9 @@ fun ZapDonationButton( } if (hasZapped) { - Text(text = stringResource(id = R.string.thank_you)) + Text(text = stringRes(id = R.string.thank_you)) } else { - Text(text = stringResource(id = R.string.donate_now)) + Text(text = stringRes(id = R.string.donate_now)) } } } @@ -481,13 +480,13 @@ fun customZapClick( if (accountViewModel.account.zapAmountChoices.isEmpty()) { accountViewModel.toast( - context.getString(R.string.error_dialog_zap_error), - context.getString(R.string.no_zap_amount_setup_long_press_to_change), + stringRes(context, R.string.error_dialog_zap_error), + stringRes(context, R.string.no_zap_amount_setup_long_press_to_change), ) } else if (!accountViewModel.isWriteable()) { accountViewModel.toast( - context.getString(R.string.error_dialog_zap_error), - context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps), + stringRes(context, R.string.error_dialog_zap_error), + stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps), ) } else if (accountViewModel.account.zapAmountChoices.size == 1) { val amount = accountViewModel.account.zapAmountChoices.first() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt index 770275404d..b0b1bd6f5b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -66,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.note.LinkIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size16Modifier import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -101,7 +101,7 @@ fun RenderAppDefinition( AsyncImage( model = it.banner, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), contentScale = ContentScale.FillWidth, modifier = Modifier @@ -123,7 +123,7 @@ fun RenderAppDefinition( } else { Image( painter = painterResource(R.drawable.profile_banner), - contentDescription = stringResource(id = R.string.profile_banner), + contentDescription = stringRes(id = R.string.profile_banner), contentScale = ContentScale.FillWidth, modifier = Modifier @@ -157,8 +157,7 @@ fun RenderAppDefinition( 3.dp, MaterialTheme.colorScheme.background, CircleShape, - ) - .clip(shape = CircleShape) + ).clip(shape = CircleShape) .fillMaxSize() .background(MaterialTheme.colorScheme.background) .combinedClickable( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt index 1c59001dcb..d7875628e0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt @@ -123,7 +123,7 @@ fun AudioTrackHeader( ) { ClickableUserPicture(it.second, 25.dp, accountViewModel) Spacer(Modifier.width(5.dp)) - UsernameDisplay(it.second, Modifier.weight(1f)) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) Spacer(Modifier.width(5.dp)) it.first.role?.let { Text( @@ -240,8 +240,7 @@ fun AudioHeader( if (noteEvent.hasHashtags()) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - val hashtags = remember(noteEvent) { noteEvent.hashtags().toImmutableList() } - DisplayUncitedHashtags(hashtags, content ?: "", nav) + DisplayUncitedHashtags(noteEvent, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt index 1d48419f10..ad50282b91 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt @@ -49,7 +49,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.quartz.events.BadgeAwardEvent @@ -86,7 +86,10 @@ fun BadgeDisplay(baseNote: Note) { launch(Dispatchers.IO) { imageResult?.let { val backgroundColor = - it.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199) + it.drawable + .toBitmap(200, 200) + .copy(Bitmap.Config.ARGB_8888, false) + .get(0, 199) val colorFromImage = Color(backgroundColor) val textBackground = if (colorFromImage.luminance() > 0.5) { @@ -109,8 +112,7 @@ fun BadgeDisplay(baseNote: Note) { 5.dp, MaterialTheme.colorScheme.mediumImportanceLink, CutCornerShape(20), - ) - .background(backgroundFromImage.first), + ).background(backgroundFromImage.first), ) { RenderBadge( image, @@ -138,7 +140,7 @@ private fun RenderBadge( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.badge_award_image_for, name ?: "", ), @@ -191,7 +193,7 @@ fun RenderBadgeAward( val noteEvent = note.event as? BadgeAwardEvent ?: return var awardees by remember { mutableStateOf>(listOf()) } - Text(text = stringResource(R.string.award_granted_to)) + Text(text = stringRes(R.string.award_granted_to)) LaunchedEffect(key1 = note) { accountViewModel.loadUsers(noteEvent.awardees()) { awardees = it } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt index 48e6f76262..63eacda3ff 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt index 905dd8283e..81e944f37b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt @@ -35,7 +35,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -45,6 +44,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.SmallBorder @@ -81,7 +81,7 @@ fun RenderClassifieds( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 5386e101de..99c355e377 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -40,13 +40,13 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage @@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier import com.vitorpamplona.amethyst.ui.theme.RowColSpacing @@ -79,7 +80,6 @@ import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.Participant import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import java.util.Locale @Composable @@ -90,9 +90,10 @@ fun RenderCommunity( ) { if (baseNote is AddressableNote) { Row( - MaterialTheme.colorScheme.innerPostModifier.clickable { - routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } - }.padding(Size10dp), + MaterialTheme.colorScheme.innerPostModifier + .clickable { + routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } + }.padding(Size10dp), ) { ShortCommunityHeader( baseNote = baseNote, @@ -117,7 +118,7 @@ fun LongCommunityHeader( Row( lineModifier, ) { - val rulesLabel = stringResource(id = R.string.rules) + val rulesLabel = stringRes(id = R.string.rules) val summary = remember(noteState) { val subject = noteEvent.subject()?.ifEmpty { null } @@ -147,7 +148,7 @@ fun LongCommunityHeader( val background = remember { mutableStateOf(defaultBackground) } TranslatableRichTextViewer( - content = summary ?: stringResource(id = R.string.community_no_descriptor), + content = summary ?: stringRes(id = R.string.community_no_descriptor), canPreview = false, quotesLeft = 1, tags = EmptyTagList, @@ -160,8 +161,8 @@ fun LongCommunityHeader( if (summary != null && noteEvent.hasHashtags()) { DisplayUncitedHashtags( - hashtags = remember(key1 = noteEvent) { noteEvent.hashtags().toImmutableList() }, - eventContent = summary, + event = noteEvent, + content = summary, nav = nav, ) } @@ -180,7 +181,7 @@ fun LongCommunityHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.owner), + text = stringRes(id = R.string.owner), maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.width(75.dp), @@ -188,7 +189,7 @@ fun LongCommunityHeader( Spacer(DoubleHorzSpacer) NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp) Spacer(DoubleHorzSpacer) - NoteUsernameDisplay(baseNote, remember { Modifier.weight(1f) }) + NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) } var participantUsers by @@ -228,7 +229,7 @@ fun LongCommunityHeader( Spacer(DoubleHorzSpacer) ClickableUserPicture(it.second, Size25dp, accountViewModel) Spacer(DoubleHorzSpacer) - UsernameDisplay(it.second, remember { Modifier.weight(1f) }) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) } } @@ -237,7 +238,7 @@ fun LongCommunityHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.created_at), + text = stringRes(id = R.string.created_at), maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.width(75.dp), @@ -258,20 +259,16 @@ fun ShortCommunityHeader( val noteEvent = remember(noteState) { noteState?.note?.event as? CommunityDefinitionEvent } ?: return - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - Row(verticalAlignment = Alignment.CenterVertically) { noteEvent.image()?.let { RobohashFallbackAsyncImage( robot = baseNote.idHex, model = it, - contentDescription = stringResource(R.string.profile_image), + contentDescription = stringRes(R.string.profile_image), contentScale = ContentScale.Crop, modifier = HeaderPictureModifier, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } @@ -361,8 +358,7 @@ fun WatchAddressableNoteFollows( accountViewModel.userFollows .map { it.user.latestContactList?.isTaggedAddressableNote(note.idHex) ?: false } .distinctUntilChanged() - } - .observeAsState(false) + }.observeAsState(false) onFollowChanges(showFollowingMark) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt index 6c5a29d040..1f0c006060 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.types -import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -49,6 +48,7 @@ import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.ShowMoreButton import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.elements.AddButton @@ -195,10 +195,9 @@ private fun EmojiListOptions( .metadata .map { usersEmojiList.event?.isTaggedAddressableNote(emojiPackNote.idHex) } .distinctUntilChanged() - } - .observeAsState() + }.observeAsState() - Crossfade(targetState = hasAddedThis, label = "EmojiListOptions") { + CrossfadeIfEnabled(targetState = hasAddedThis, label = "EmojiListOptions", accountViewModel = accountViewModel) { if (it != true) { AddButton { accountViewModel.addEmojiPack(usersEmojiList, emojiPackNote) } } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt index 26eab2f870..70321e2ee8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.types -import androidx.compose.animation.Crossfade import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -31,6 +30,7 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView @@ -106,7 +106,7 @@ private fun ObserverAndRenderNIP95( mutableStateOf(newContent) } - Crossfade(targetState = content) { + CrossfadeIfEnabled(targetState = content, accountViewModel = accountViewModel) { if (it != null) { SensitivityWarning(note = header, accountViewModel = accountViewModel) { ZoomableContentView( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt index 27b8f7681f..53d701fd1c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt @@ -38,7 +38,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R @@ -51,6 +50,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.QuoteBorder @@ -66,8 +66,6 @@ import com.vitorpamplona.quartz.events.GitPatchEvent import com.vitorpamplona.quartz.events.GitRepositoryEvent import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun RenderGitPatchEvent( @@ -107,7 +105,7 @@ private fun RenderShortRepositoryHeader( ) { val title = remember(noteEvent) { noteEvent.name() ?: noteEvent.dTag() } Text( - text = stringResource(id = R.string.git_repository, title), + text = stringRes(id = R.string.git_repository, title), style = MaterialTheme.typography.titleLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -195,11 +193,11 @@ private fun RenderGitPatchEvent( } if (note.event?.hasHashtags() == true) { - val hashtags = - remember(note.event) { - note.event?.hashtags()?.toImmutableList() ?: persistentListOf() - } - DisplayUncitedHashtags(hashtags, eventContent, nav) + DisplayUncitedHashtags( + event = noteEvent, + content = eventContent, + nav = nav, + ) } } } @@ -299,11 +297,7 @@ private fun RenderGitIssueEvent( } if (note.event?.hasHashtags() == true) { - val hashtags = - remember(note.event) { - note.event?.hashtags()?.toImmutableList() ?: persistentListOf() - } - DisplayUncitedHashtags(hashtags, eventContent, nav) + DisplayUncitedHashtags(noteEvent, eventContent, nav) } } } @@ -344,7 +338,7 @@ private fun RenderGitRepositoryEvent( ) { Column { Text( - text = stringResource(id = R.string.git_repository, title), + text = stringRes(id = R.string.git_repository, title), style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -365,7 +359,7 @@ private fun RenderGitRepositoryEvent( web?.let { Row(Modifier.fillMaxWidth().padding(top = Size5dp)) { Text( - text = stringResource(id = R.string.git_web_address), + text = stringRes(id = R.string.git_web_address), maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -380,7 +374,7 @@ private fun RenderGitRepositoryEvent( clone?.let { Row(Modifier.fillMaxWidth().padding(top = Size5dp)) { Text( - text = stringResource(id = R.string.git_clone_address), + text = stringRes(id = R.string.git_clone_address), maxLines = 1, overflow = TextOverflow.Ellipsis, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index cd5ed6250b..6d0a16f351 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.types -import androidx.compose.animation.Crossfade import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -39,13 +38,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay @@ -55,6 +54,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckIfUrlIsOnline import com.vitorpamplona.amethyst.ui.screen.loggedIn.CrossfadeCheckIfUrlIsOnline import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.LiveActivitiesEvent @@ -116,7 +116,7 @@ fun RenderLiveActivityEventInner( Spacer(modifier = StdHorzSpacer) - Crossfade(targetState = status, label = "RenderLiveActivityEventInner") { + CrossfadeIfEnabled(targetState = status, label = "RenderLiveActivityEventInner", accountViewModel = accountViewModel) { when (it) { LiveActivitiesEvent.STATUS_LIVE -> { media?.let { CrossfadeCheckIfUrlIsOnline(it, accountViewModel) { LiveFlag() } } @@ -171,7 +171,7 @@ fun RenderLiveActivityEventInner( .height(100.dp), ) { Text( - text = stringResource(id = R.string.live_stream_is_offline), + text = stringRes(id = R.string.live_stream_is_offline), color = MaterialTheme.colorScheme.onBackground, fontWeight = FontWeight.Bold, ) @@ -187,7 +187,7 @@ fun RenderLiveActivityEventInner( .height(100.dp), ) { Text( - text = stringResource(id = R.string.live_stream_has_ended), + text = stringRes(id = R.string.live_stream_has_ended), color = MaterialTheme.colorScheme.onBackground, fontWeight = FontWeight.Bold, ) @@ -205,7 +205,7 @@ fun RenderLiveActivityEventInner( ) { ClickableUserPicture(it.second, 25.dp, accountViewModel) Spacer(StdHorzSpacer) - UsernameDisplay(it.second, Modifier.weight(1f)) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) Spacer(StdHorzSpacer) it.first.role?.let { Text( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt index e0f3eceac4..414b201036 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt index df493c64c7..b62266f97a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage @@ -41,6 +40,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -90,7 +90,7 @@ private fun LongFormHeader( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt index a4d6f68125..02a060d57e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.types -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -35,17 +34,12 @@ import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent -import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import com.vitorpamplona.quartz.events.BaseTextNoteEvent -import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.EmptyTagList +import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun RenderNIP90ContentDiscoveryResponse( @@ -59,39 +53,7 @@ fun RenderNIP90ContentDiscoveryResponse( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val noteEvent = note.event - - val showReply by - remember(note) { - derivedStateOf { - noteEvent is BaseTextNoteEvent && !makeItShort && unPackReply && (note.replyTo != null || noteEvent.hasAnyTaggedUser()) - } - } - - if (showReply) { - val replyingDirectlyTo = - remember(note) { - if (noteEvent is BaseTextNoteEvent) { - val replyingTo = noteEvent.replyingToAddressOrEvent() - if (replyingTo != null) { - val newNote = accountViewModel.getNoteIfExists(replyingTo) - if (newNote != null && newNote.channelHex() == null && newNote.event?.kind() != CommunityDefinitionEvent.KIND) { - newNote - } else { - note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } - } - } else { - note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } - } - } else { - note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } - } - } - if (replyingDirectlyTo != null && unPackReply) { - ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav) - Spacer(modifier = StdVertSpacer) - } - } + val noteEvent = note.event as? Event ?: return LoadDecryptedContent( note, @@ -118,9 +80,6 @@ fun RenderNIP90ContentDiscoveryResponse( } } - val isAuthorTheLoggedUser = - remember(note.event) { accountViewModel.isLoggedUser(note.author) } - SensitivityWarning( note = note, accountViewModel = accountViewModel, @@ -143,12 +102,8 @@ fun RenderNIP90ContentDiscoveryResponse( ) } - if (note.event?.hasHashtags() == true) { - val hashtags = - remember(note.event) { - note.event?.hashtags()?.toImmutableList() ?: persistentListOf() - } - DisplayUncitedHashtags(hashtags, eventContent, nav) + if (noteEvent.hasHashtags()) { + DisplayUncitedHashtags(noteEvent, eventContent, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Poll.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Poll.kt index 27db72f57c..eedd9003fd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Poll.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Poll.kt @@ -45,7 +45,6 @@ import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.toImmutableList @Composable fun RenderPoll( @@ -126,8 +125,7 @@ fun RenderPoll( } if (noteEvent.hasHashtags()) { - val hashtags = remember { noteEvent.hashtags().toImmutableList() } - DisplayUncitedHashtags(hashtags, eventContent, nav) + DisplayUncitedHashtags(noteEvent, eventContent, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt index ba026b5ca7..a9b660371c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt @@ -32,7 +32,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R @@ -44,6 +43,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomHeader +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.replyModifier @@ -52,8 +52,6 @@ import com.vitorpamplona.quartz.events.ChatroomKeyable import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun RenderPrivateMessage( @@ -122,11 +120,7 @@ fun RenderPrivateMessage( } if (noteEvent.hasHashtags()) { - val hashtags = - remember(note.event?.id()) { - note.event?.hashtags()?.toImmutableList() ?: persistentListOf() - } - DisplayUncitedHashtags(hashtags, eventContent, nav) + DisplayUncitedHashtags(noteEvent, eventContent, nav) } } } @@ -134,7 +128,7 @@ fun RenderPrivateMessage( val recipient = noteEvent.recipientPubKeyBytes()?.toNpub() ?: "Someone" TranslatableRichTextViewer( - stringResource( + stringRes( id = R.string.private_conversation_notification, "@${note.author?.pubkeyNpub()}", "@$recipient", diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt index c3bc3621b3..79bf456d7b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -39,6 +38,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent @@ -68,7 +68,7 @@ fun RenderPostApproval( } Text( - text = stringResource(id = R.string.community_approved_posts), + text = stringRes(id = R.string.community_approved_posts), fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt index 1de1b59f60..01b0f1beb5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt @@ -26,12 +26,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.ReportEvent @@ -52,21 +52,25 @@ fun RenderReport( base .map { when (it.reportType) { - ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) - ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity) - ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech) - ReportEvent.ReportType.SPAM -> stringResource(R.string.spam) - ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation) - ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior) - ReportEvent.ReportType.OTHER -> stringResource(R.string.other) + ReportEvent.ReportType.EXPLICIT -> stringRes(R.string.explicit_content) + ReportEvent.ReportType.NUDITY -> stringRes(R.string.nudity) + ReportEvent.ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech) + ReportEvent.ReportType.SPAM -> stringRes(R.string.spam) + ReportEvent.ReportType.IMPERSONATION -> stringRes(R.string.impersonation) + ReportEvent.ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior) + ReportEvent.ReportType.OTHER -> stringRes(R.string.other) } - } - .toSet() + }.toSet() .joinToString(", ") val content = remember { - reportType + (note.event?.content()?.ifBlank { null }?.let { ": $it" } ?: "") + reportType + ( + note.event + ?.content() + ?.ifBlank { null } + ?.let { ": $it" } ?: "" + ) } TranslatableRichTextViewer( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Text.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Text.kt index 775b1f7abe..afd11a0d99 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Text.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Text.kt @@ -46,10 +46,9 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.BaseTextNoteEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.EmptyTagList +import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun RenderTextEvent( @@ -63,7 +62,7 @@ fun RenderTextEvent( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val noteEvent = note.event + val noteEvent = note.event as? Event ?: return val showReply by remember(note) { @@ -101,31 +100,26 @@ fun RenderTextEvent( note, accountViewModel, ) { body -> - val eventContent by + val eventContent = remember(note.event) { - derivedStateOf { - val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null } - val newBody = - if (editState.value is GenericLoadable.Loaded) { - val state = - (editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow - state?.value?.event?.content() ?: body - } else { - body - } - - if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) { - "### $subject\n$newBody" + val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null } + val newBody = + if (editState.value is GenericLoadable.Loaded) { + val state = + (editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow + state?.value?.event?.content() ?: body } else { - newBody + body } + + if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) { + "### $subject\n$newBody" + } else { + newBody } } - val isAuthorTheLoggedUser = - remember(note.event) { accountViewModel.isLoggedUser(note.author) } - - if (makeItShort && isAuthorTheLoggedUser) { + if (makeItShort && accountViewModel.isLoggedUser(note.author)) { Text( text = eventContent, color = MaterialTheme.colorScheme.placeholderText, @@ -137,7 +131,6 @@ fun RenderTextEvent( note = note, accountViewModel = accountViewModel, ) { - val modifier = remember(note) { Modifier.fillMaxWidth() } val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList } @@ -145,7 +138,7 @@ fun RenderTextEvent( content = eventContent, canPreview = canPreview && !makeItShort, quotesLeft = quotesLeft, - modifier = modifier, + modifier = Modifier.fillMaxWidth(), tags = tags, backgroundColor = backgroundColor, id = note.idHex, @@ -155,12 +148,8 @@ fun RenderTextEvent( ) } - if (note.event?.hasHashtags() == true) { - val hashtags = - remember(note.event) { - note.event?.hashtags()?.toImmutableList() ?: persistentListOf() - } - DisplayUncitedHashtags(hashtags, eventContent, nav) + if (noteEvent.hasHashtags()) { + DisplayUncitedHashtags(noteEvent, eventContent, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt index b231681116..a83e6e20a9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp @@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.NoteBody import com.vitorpamplona.amethyst.ui.note.observeEdits import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -100,7 +100,7 @@ fun RenderTextModificationEvent( ) { Column(Modifier.fillMaxWidth().padding(Size10dp)) { Text( - text = stringResource(id = R.string.proposal_to_edit), + text = stringRes(id = R.string.proposal_to_edit), style = TextStyle( fontSize = 18.sp, @@ -170,7 +170,8 @@ fun RenderTextModificationEvent( Column( modifier = - MaterialTheme.colorScheme.innerPostModifier.padding(Size10dp) + MaterialTheme.colorScheme.innerPostModifier + .padding(Size10dp) .clickable { routeFor( baseNote, @@ -215,7 +216,7 @@ fun RenderTextModificationEvent( onClick = { wantsToEditPost.value = true }, modifier = Modifier.fillMaxWidth(), ) { - Text(text = stringResource(id = R.string.accept_the_suggestion)) + Text(text = stringRes(id = R.string.accept_the_suggestion)) } } } @@ -223,7 +224,7 @@ fun RenderTextModificationEvent( } @Stable -class EditState() { +class EditState { private var modificationsList: List = persistentListOf() private var modificationToShowIndex: Int = -1 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt index 18636729a0..080aca811b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -54,11 +53,11 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.VideoEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists -import kotlinx.collections.immutable.toImmutableList @Composable fun VideoDisplay( @@ -132,7 +131,7 @@ fun VideoDisplay( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), @@ -178,17 +177,13 @@ fun VideoDisplay( accountViewModel = accountViewModel, nav = nav, ) - } - if (event.hasHashtags()) { - Row( - Modifier.fillMaxWidth(), - ) { - DisplayUncitedHashtags( - remember(event) { event.hashtags().toImmutableList() }, - summary ?: "", - nav, - ) + if (event.hasHashtags()) { + Row( + Modifier.fillMaxWidth(), + ) { + DisplayUncitedHashtags(event, summary, nav) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Wiki.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Wiki.kt index b40c18193b..fc462e94f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Wiki.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Wiki.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage @@ -42,6 +41,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -93,7 +93,7 @@ private fun WikiNoteHeader( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt index f79e090995..8f18de38c7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt @@ -24,11 +24,11 @@ import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.res.stringResource import com.google.zxing.client.android.Intents import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanOptions import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.uriToRoute import kotlinx.coroutines.CancellationException @@ -60,7 +60,7 @@ fun SimpleQrCodeScanner(onScan: (String?) -> Unit) { val scanOptions = ScanOptions().apply { setDesiredBarcodeFormats(ScanOptions.QR_CODE) - setPrompt(stringResource(id = R.string.point_to_the_qr_code)) + setPrompt(stringRes(id = R.string.point_to_the_qr_code)) setBeepEnabled(false) setOrientationLocked(false) addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt index 0cb474347f..981efabe25 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.qrcode -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -45,7 +44,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -57,6 +55,7 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.quartz.events.UserMetadata @@ -77,6 +76,7 @@ fun ShowQRDialogPreview() { ShowQRDialog( user = user, loadProfilePicture = false, + loadRobohash = false, onScan = {}, onClose = {}, ) @@ -86,6 +86,7 @@ fun ShowQRDialogPreview() { fun ShowQRDialog( user: User, loadProfilePicture: Boolean, + loadRobohash: Boolean, onScan: (String) -> Unit, onClose: () -> Unit, ) { @@ -118,13 +119,15 @@ fun ShowQRDialog( RobohashFallbackAsyncImage( robot = user.pubkeyHex, model = user.profilePicture(), - contentDescription = stringResource(R.string.profile_image), + contentDescription = stringRes(R.string.profile_image), modifier = - Modifier.width(100.dp) + Modifier + .width(100.dp) .height(100.dp) .clip(shape = CircleShape) .border(3.dp, MaterialTheme.colorScheme.background, CircleShape), loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } Row( @@ -157,7 +160,7 @@ fun ShowQRDialog( containerColor = MaterialTheme.colorScheme.primary, ), ) { - Text(text = stringResource(R.string.scan_qr)) + Text(text = stringRes(R.string.scan_qr)) } } } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index 7a9dd2bc67..0d1aa111b7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModelStore @@ -57,6 +56,7 @@ import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.signers.NostrSignerExternal import kotlinx.coroutines.CancellationException @@ -149,7 +149,10 @@ fun LoggedInPage( ) } else { result.data?.let { - accountViewModel.runOnIO { accountViewModel.account.signer.launcher.newResult(it) } + accountViewModel.runOnIO { + accountViewModel.account.signer.launcher + .newResult(it) + } } } }, @@ -196,7 +199,8 @@ fun LoggedInPage( contentResolver = { Amethyst.instance.contentResolver }, ) onDispose { - accountViewModel.account.signer.launcher.clearLauncher() + accountViewModel.account.signer.launcher + .clearLauncher() lifeCycleOwner.lifecycle.removeObserver(observer) } } @@ -205,7 +209,9 @@ fun LoggedInPage( MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel) } -class AccountCentricViewModelStore(val account: Account) : ViewModelStoreOwner { +class AccountCentricViewModelStore( + val account: Account, +) : ViewModelStoreOwner { override val viewModelStore = ViewModelStore() } @@ -216,6 +222,6 @@ fun LoadingAccounts() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.loading_account)) + Text(stringRes(R.string.loading_account)) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 03c5f8d3d2..18e4803c28 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.HttpClientManager +import com.vitorpamplona.amethyst.service.Nip05NostrAddressVerifier import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.KeyPair @@ -57,7 +58,7 @@ import java.util.regex.Pattern val EMAIL_PATTERN = Pattern.compile(".+@.+\\.[a-z]+") @Stable -class AccountStateViewModel() : ViewModel() { +class AccountStateViewModel : ViewModel() { var serviceManager: ServiceManager? = null private val _accountContent = MutableStateFlow(AccountState.Loading) @@ -144,15 +145,6 @@ class AccountStateViewModel() : ViewModel() { proxyPort = proxyPort, signer = NostrSignerInternal(keyPair), ) - } else if (EMAIL_PATTERN.matcher(key).matches()) { - val keyPair = KeyPair() - // TODO: Evaluate NIP-5 - Account( - keyPair, - proxy = proxy, - proxyPort = proxyPort, - signer = NostrSignerInternal(keyPair), - ) } else { val keyPair = KeyPair(Hex.decode(key)) Account( @@ -243,6 +235,18 @@ class AccountStateViewModel() : ViewModel() { onError(null) } } + } else if (EMAIL_PATTERN.matcher(key).matches()) { + Nip05NostrAddressVerifier().verifyNip05( + key, + onSuccess = { publicKey -> + loginSync(Hex.decode(publicKey).toNpub(), useProxy, proxyPort, loginWithExternalSigner, packageName) { + onError(null) + } + }, + onError = { + onError(it) + }, + ) } else { loginSync(key, useProxy, proxyPort, loginWithExternalSigner, packageName) { onError(null) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index 8d0a140b83..ead1f9cd9a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Row @@ -41,6 +40,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.BuildConfig +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.note.BadgeCompose import com.vitorpamplona.amethyst.ui.note.MessageSetCompose @@ -111,10 +111,11 @@ fun RenderCardFeed( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( modifier = Modifier.fillMaxSize(), targetState = feedState, animationSpec = tween(durationMillis = 100), + accountViewModel = accountViewModel, ) { state -> when (state) { is CardFeedState.Empty -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt index a4afc4aadf..b0dd65c34b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -38,6 +37,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.FeedPadding @@ -86,7 +86,7 @@ fun RenderChatroomFeedView( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state -> when (state) { is FeedState.Empty -> { FeedEmpty { viewModel.invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index 1d88fe089b..56d30493f0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -34,6 +33,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.ChatroomHeaderCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -58,9 +58,10 @@ private fun CrossFadeState( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Empty -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 9c1299b904..fba5655849 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -52,11 +51,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -77,21 +77,6 @@ fun RefresheableFeedView( } } -@Composable -fun DVMStatusView( - viewModel: FeedViewModel, - routeForLastRead: String?, - enablePullRefresh: Boolean = true, - scrollStateKey: String? = null, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - viewModel.invalidateData() - SaveableFeedState(viewModel, scrollStateKey) { listState -> - RenderFeedState(viewModel, accountViewModel, listState, nav, routeForLastRead) - } -} - @Composable fun RefresheableBox( viewModel: InvalidatableViewModel, @@ -120,12 +105,10 @@ fun RefresheableBox( val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) val modifier = - remember { - if (enablePullRefresh) { - Modifier.fillMaxSize().pullRefresh(pullRefreshState) - } else { - Modifier.fillMaxSize() - } + if (enablePullRefresh) { + Modifier.fillMaxSize().pullRefresh(pullRefreshState) + } else { + Modifier.fillMaxSize() } Box(modifier) { @@ -135,7 +118,7 @@ fun RefresheableBox( PullRefreshIndicator( refreshing = refreshing, state = pullRefreshState, - modifier = remember { Modifier.align(Alignment.TopCenter) }, + modifier = Modifier.align(Alignment.TopCenter), ) } } @@ -191,9 +174,10 @@ fun RenderFeedState( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Empty -> onEmpty() @@ -275,7 +259,7 @@ fun LoadingFeed() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.loading_feed)) + Text(stringRes(R.string.loading_feed)) } } @@ -289,13 +273,13 @@ fun FeedError( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text("${stringResource(R.string.error_loading_replies)} $errorMessage") + Text("${stringRes(R.string.error_loading_replies)} $errorMessage") Spacer(modifier = StdVertSpacer) Button( modifier = Modifier.align(Alignment.CenterHorizontally), onClick = onRefresh, ) { - Text(text = stringResource(R.string.try_again)) + Text(text = stringRes(R.string.try_again)) } } } @@ -307,8 +291,8 @@ fun FeedEmpty(onRefresh: () -> Unit) { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.feed_is_empty)) + Text(stringRes(R.string.feed_is_empty)) Spacer(modifier = StdVertSpacer) - OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) } + OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index 4d531ac3b7..0a2c8d2b25 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -32,6 +31,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -45,7 +45,7 @@ fun LnZapFeedView( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state -> when (state) { is LnZapFeedState.Empty -> { FeedEmpty { viewModel.invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt index 83f506cfd3..129ad0ebb9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/SharedPreferencesViewModel.kt @@ -42,7 +42,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Stable -class SettingsState() { +class SettingsState { var theme by mutableStateOf(ThemeType.SYSTEM) var language by mutableStateOf(null) @@ -53,7 +53,7 @@ class SettingsState() { var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS) var dontShowPushNotificationSelector by mutableStateOf(false) var dontAskForNotificationPermissions by mutableStateOf(false) - var featureSet by mutableStateOf(FeatureSetType.COMPLETE) + var featureSet by mutableStateOf(FeatureSetType.SIMPLIFIED) var isOnMobileData: State = mutableStateOf(false) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt index c3561e544c..d35ba75c4b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -35,22 +34,25 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding @Composable fun StringFeedView( viewModel: StringFeedViewModel, + accountViewModel: AccountViewModel, pre: (@Composable () -> Unit)? = null, post: (@Composable () -> Unit)? = null, inner: @Composable (String) -> Unit, ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state -> when (state) { is StringFeedState.Empty -> { StringFeedEmpty(pre, post) { viewModel.invalidateData() } @@ -82,8 +84,8 @@ fun StringFeedEmpty( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.feed_is_empty)) - OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) } + Text(stringRes(R.string.feed_is_empty)) + OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) } } post?.let { it() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 8542945a71..f63b2fcdff 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -47,7 +47,6 @@ import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -60,7 +59,6 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization @@ -137,6 +135,7 @@ import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThinSendButton +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder @@ -403,13 +402,11 @@ private fun FullBleedNoteCompose( Column(modifier = Modifier.padding(start = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - NoteUsernameDisplay(baseNote, Modifier.weight(1f)) + NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) - val isCommunityPost by + val isCommunityPost = remember(baseNote) { - derivedStateOf { - baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true - } + baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true } if (isCommunityPost) { @@ -737,14 +734,14 @@ private fun RenderClassifiedsReaderForThread( ) { Icon( painter = painterResource(R.drawable.ic_dm), - stringResource(R.string.send_a_direct_message), + stringRes(R.string.send_a_direct_message), modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.primary, ) Spacer(modifier = StdHorzSpacer) - Text(stringResource(id = R.string.send_the_seller_a_message)) + Text(stringRes(id = R.string.send_the_seller_a_message)) } Row( @@ -759,12 +756,12 @@ private fun RenderClassifiedsReaderForThread( val msg = if (sellerName != null) { - stringResource( + stringRes( id = R.string.hi_seller_is_this_still_available, sellerName, ) } else { - stringResource(id = R.string.hi_there_is_this_still_available) + stringRes(id = R.string.hi_there_is_this_still_available) } var message by remember { mutableStateOf(TextFieldValue(msg)) } @@ -781,7 +778,7 @@ private fun RenderClassifiedsReaderForThread( modifier = Modifier.weight(1f, true), placeholder = { Text( - text = stringResource(R.string.reply_here), + text = stringRes(R.string.reply_here), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -816,7 +813,7 @@ private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) { AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), @@ -907,7 +904,7 @@ private fun RenderWikiHeaderForThread( AsyncImage( model = it, contentDescription = - stringResource( + stringRes( R.string.preview_card_image_for, it, ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index bc1ee84fed..bc9812d174 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -29,6 +28,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -52,7 +52,7 @@ fun UserFeedView( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state -> when (state) { is UserFeedState.Empty -> { FeedEmpty { viewModel.invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 8e3b745651..8021d6965b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -72,7 +72,6 @@ import androidx.compose.ui.platform.LocalAutofill import androidx.compose.ui.platform.LocalAutofillTree import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -93,6 +92,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.note.authenticate +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -143,7 +143,7 @@ fun AccountBackupDialog( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val content1 = stringResource(R.string.account_backup_tips2_md) + val content1 = stringRes(R.string.account_backup_tips2_md) val astNode1 = remember { @@ -163,7 +163,7 @@ fun AccountBackupDialog( Spacer(modifier = Modifier.height(30.dp)) - val content = stringResource(R.string.account_backup_tips3_md) + val content = stringRes(R.string.account_backup_tips3_md) val astNode = remember { @@ -196,8 +196,7 @@ fun AccountBackupDialog( Modifier .onGloballyPositioned { coordinates -> autofillNode.boundingBox = coordinates.boundsInWindow() - } - .onFocusChanged { focusState -> + }.onFocusChanged { focusState -> autofill?.run { if (focusState.isFocused) { requestAutofillForNode(autofillNode) @@ -221,7 +220,7 @@ fun AccountBackupDialog( ), placeholder = { Text( - text = stringResource(R.string.ncryptsec_password), + text = stringRes(R.string.ncryptsec_password), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -233,9 +232,9 @@ fun AccountBackupDialog( if (showCharsPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, contentDescription = if (showCharsPassword) { - stringResource(R.string.show_password) + stringRes(R.string.show_password) } else { - stringResource( + stringRes( R.string.hide_password, ) }, @@ -271,8 +270,7 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) { val scope = rememberCoroutineScope() val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - result: ActivityResult -> + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { copyNSec(context, scope, accountViewModel.account, clipboardManager) } @@ -282,7 +280,7 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) { modifier = Modifier.padding(horizontal = 3.dp), onClick = { authenticate( - title = context.getString(R.string.copy_my_secret_key), + title = stringRes(context, R.string.copy_my_secret_key), context = context, keyguardLauncher = keyguardLauncher, onApproved = { copyNSec(context, scope, accountViewModel.account, clipboardManager) }, @@ -300,11 +298,11 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) { tint = MaterialTheme.colorScheme.onPrimary, imageVector = Icons.Default.Key, contentDescription = - stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), + stringRes(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), modifier = Modifier.padding(end = 5.dp), ) Text( - stringResource(id = R.string.copy_my_secret_key), + stringRes(id = R.string.copy_my_secret_key), color = MaterialTheme.colorScheme.onPrimary, ) } @@ -320,8 +318,7 @@ private fun EncryptNSecCopyButton( val scope = rememberCoroutineScope() val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - result: ActivityResult -> + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) } @@ -331,7 +328,7 @@ private fun EncryptNSecCopyButton( modifier = Modifier.padding(horizontal = 3.dp), onClick = { authenticate( - title = context.getString(R.string.copy_my_secret_key), + title = stringRes(context, R.string.copy_my_secret_key), context = context, keyguardLauncher = keyguardLauncher, onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) }, @@ -345,11 +342,11 @@ private fun EncryptNSecCopyButton( Icon( imageVector = Icons.Default.Key, contentDescription = - stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), + stringRes(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), modifier = Modifier.padding(end = 5.dp), ) Text( - stringResource(id = R.string.encrypt_and_copy_my_secret_key), + stringRes(id = R.string.encrypt_and_copy_my_secret_key), ) } } @@ -374,12 +371,12 @@ private fun copyNSec( account.keyPair.privKey?.let { clipboardManager.setText(AnnotatedString(it.toNsec())) scope.launch { - Toast.makeText( - context, - context.getString(R.string.secret_key_copied_to_clipboard), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.secret_key_copied_to_clipboard), + Toast.LENGTH_SHORT, + ).show() } } } @@ -393,12 +390,12 @@ private fun encryptCopyNSec( ) { if (password.value.text.isBlank()) { scope.launch { - Toast.makeText( - context, - context.getString(R.string.password_is_required), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.password_is_required), + Toast.LENGTH_SHORT, + ).show() } } else { accountViewModel.account.keyPair.privKey?.let { @@ -406,21 +403,21 @@ private fun encryptCopyNSec( if (key != null) { clipboardManager.setText(AnnotatedString(key)) scope.launch { - Toast.makeText( - context, - context.getString(R.string.secret_key_copied_to_clipboard), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.secret_key_copied_to_clipboard), + Toast.LENGTH_SHORT, + ).show() } } else { scope.launch { - Toast.makeText( - context, - context.getString(R.string.failed_to_encrypt_key), - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + context, + stringRes(context, R.string.failed_to_encrypt_key), + Toast.LENGTH_SHORT, + ).show() } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index e0833cf901..23371bf3c1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.CombinedZap import com.vitorpamplona.amethyst.ui.screen.SettingsState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.Nip11RelayInformation @@ -110,7 +111,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combineTransform -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.joinAll @@ -269,6 +269,8 @@ class AccountViewModel( accountChoices: Account.LiveHiddenUsers, followUsers: Set, ): NoteComposeReportState { + checkNotInMainThread() + val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex val isFromLoggedInFollow = note.author?.let { followUsers.contains(it.pubkeyHex) } ?: true val isPostHidden = note.isHiddenFor(accountChoices) @@ -310,7 +312,11 @@ class AccountViewModel( note.flow().metadata.stateFlow, note.flow().reports.stateFlow, ) { hiddenUsers, followingUsers, metadata, reports -> - emit(isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users)) + val isAcceptable = + withContext(Dispatchers.IO) { + isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users) + } + emit(isAcceptable) }.stateIn( viewModelScope, SharingStarted.Eagerly, @@ -1297,8 +1303,9 @@ class AccountViewModel( } } else { onDone( - context.getString(R.string.no_lightning_address_set), - context.getString( + stringRes(context, R.string.no_lightning_address_set), + stringRes( + context, R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, account.userProfile().toBestDisplayName(), ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt index cfdda33048..824a17ba6e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt @@ -36,12 +36,12 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.NostrBookmarkPrivateFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrBookmarkPublicFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.TabRowHeight import kotlinx.coroutines.launch @@ -93,12 +93,12 @@ private fun RenderBookmarkScreen( Tab( selected = pagerState.currentPage == 0, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(text = stringResource(R.string.private_bookmarks)) }, + text = { Text(text = stringRes(R.string.private_bookmarks)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(text = stringResource(R.string.public_bookmarks)) }, + text = { Text(text = stringRes(R.string.public_bookmarks)) }, ) } HorizontalPager(state = pagerState) { page -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 7648f7244a..2052fe115b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -84,7 +84,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -104,6 +103,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LiveActivitiesChannel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -137,6 +137,7 @@ import com.vitorpamplona.amethyst.ui.note.timeAgoShort import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -452,7 +453,7 @@ fun EditFieldRow( modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.reply_here), + text = stringRes(R.string.reply_here), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -710,8 +711,7 @@ fun ShowVideoStreaming( .map { val activity = it.channel as? LiveActivitiesChannel activity?.info - } - .distinctUntilChanged() + }.distinctUntilChanged() .observeAsState(baseChannel.info) streamingInfo?.let { event -> @@ -764,11 +764,6 @@ fun ShortChannelHeader( val channelState by baseChannel.live.observeAsState() val channel = channelState?.channel ?: return - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - Row(verticalAlignment = Alignment.CenterVertically) { if (channel is LiveActivitiesChannel) { channel.creator?.let { @@ -784,10 +779,11 @@ fun ShortChannelHeader( RobohashFallbackAsyncImage( robot = channel.idHex, model = it, - contentDescription = stringResource(R.string.profile_image), + contentDescription = stringRes(R.string.profile_image), contentScale = ContentScale.Crop, modifier = HeaderPictureModifier, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } } @@ -858,7 +854,7 @@ fun LongChannelHeader( } TranslatableRichTextViewer( - content = summary ?: stringResource(id = R.string.groups_no_descriptor), + content = summary ?: stringRes(id = R.string.groups_no_descriptor), canPreview = false, quotesLeft = 1, tags = tags, @@ -869,12 +865,12 @@ fun LongChannelHeader( ) } - if (baseChannel is LiveActivitiesChannel && baseChannel.info?.hasHashtags() == true) { - val hashtags = - remember(baseChannel.info) { - baseChannel.info?.hashtags()?.toImmutableList() ?: persistentListOf() + if (baseChannel is LiveActivitiesChannel && summary != null) { + baseChannel.info?.let { + if (it.hasHashtags()) { + DisplayUncitedHashtags(it, summary, nav) } - DisplayUncitedHashtags(hashtags, summary ?: "", nav) + } } } @@ -895,7 +891,7 @@ fun LongChannelHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.owner), + text = stringRes(id = R.string.owner), maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.width(75.dp), @@ -903,7 +899,7 @@ fun LongChannelHeader( Spacer(DoubleHorzSpacer) NoteAuthorPicture(note, nav, accountViewModel, Size25dp) Spacer(DoubleHorzSpacer) - NoteUsernameDisplay(note, remember { Modifier.weight(1f) }) + NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel) } Row( @@ -911,7 +907,7 @@ fun LongChannelHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.created_at), + text = stringRes(id = R.string.created_at), maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.width(75.dp), @@ -938,8 +934,7 @@ fun LongChannelHeader( ?.participants() ?.mapNotNull { part -> LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } - } - ?.toImmutableList() + }?.toImmutableList() if ( newParticipantUsers != null && !equalImmutableLists(newParticipantUsers, participantUsers) @@ -965,7 +960,7 @@ fun LongChannelHeader( Spacer(DoubleHorzSpacer) ClickableUserPicture(it.second, Size25dp, accountViewModel) Spacer(DoubleHorzSpacer) - UsernameDisplay(it.second, remember { Modifier.weight(1f) }) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) } } } @@ -976,7 +971,7 @@ fun NormalTimeAgo( baseNote: Note, modifier: Modifier, ) { - val nowStr = stringResource(id = R.string.now) + val nowStr = stringRes(id = R.string.now) val time by remember(baseNote) { derivedStateOf { timeAgoShort(baseNote.createdAt() ?: 0, nowStr) } } @@ -1105,7 +1100,7 @@ private fun LiveChannelActionOptions( @Composable fun LiveFlag() { Text( - text = stringResource(id = R.string.live_stream_live_tag), + text = stringRes(id = R.string.live_stream_live_tag), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp, @@ -1122,7 +1117,7 @@ fun LiveFlag() { @Composable fun EndedFlag() { Text( - text = stringResource(id = R.string.live_stream_ended_tag), + text = stringRes(id = R.string.live_stream_ended_tag), color = Color.White, fontWeight = FontWeight.Bold, modifier = @@ -1138,7 +1133,7 @@ fun EndedFlag() { @Composable fun OfflineFlag() { Text( - text = stringResource(id = R.string.live_stream_offline_tag), + text = stringRes(id = R.string.live_stream_offline_tag), color = Color.White, fontWeight = FontWeight.Bold, modifier = @@ -1155,14 +1150,15 @@ fun OfflineFlag() { fun ScheduledFlag(starts: Long?) { val startsIn = starts?.let { - SimpleDateFormat.getDateTimeInstance( - DateFormat.SHORT, - DateFormat.SHORT, - ).format(Date(starts * 1000)) + SimpleDateFormat + .getDateTimeInstance( + DateFormat.SHORT, + DateFormat.SHORT, + ).format(Date(starts * 1000)) } Text( - text = startsIn ?: stringResource(id = R.string.live_stream_planned_tag), + text = startsIn ?: stringRes(id = R.string.live_stream_planned_tag), color = Color.White, fontWeight = FontWeight.Bold, modifier = liveStreamTag, @@ -1188,7 +1184,7 @@ private fun NoteCopyButton(note: Channel) { Icon( tint = Color.White, imageVector = Icons.Default.Share, - contentDescription = stringResource(R.string.copies_the_note_id_to_the_clipboard_for_sharing), + contentDescription = stringRes(R.string.copies_the_note_id_to_the_clipboard_for_sharing), ) DropdownMenu( @@ -1198,7 +1194,7 @@ private fun NoteCopyButton(note: Channel) { val clipboardManager = LocalClipboardManager.current DropdownMenuItem( - text = { Text(stringResource(R.string.copy_channel_id_note_to_the_clipboard)) }, + text = { Text(stringRes(R.string.copy_channel_id_note_to_the_clipboard)) }, onClick = { clipboardManager.setText(AnnotatedString("nostr:" + note.idNote())) popupExpanded = false @@ -1230,7 +1226,7 @@ private fun EditButton( Icon( tint = Color.White, imageVector = Icons.Default.EditNote, - contentDescription = stringResource(R.string.edits_the_channel_metadata), + contentDescription = stringRes(R.string.edits_the_channel_metadata), ) } } @@ -1248,7 +1244,7 @@ fun JoinChatButton( onClick = { scope.launch(Dispatchers.IO) { accountViewModel.account.follow(channel) } }, contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.join), color = Color.White) + Text(text = stringRes(R.string.join), color = Color.White) } } @@ -1265,7 +1261,7 @@ fun LeaveChatButton( onClick = { scope.launch(Dispatchers.IO) { accountViewModel.account.unfollow(channel) } }, contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.leave), color = Color.White) + Text(text = stringRes(R.string.leave), color = Color.White) } } @@ -1287,7 +1283,7 @@ fun JoinCommunityButton( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.join), color = Color.White) + Text(text = stringRes(R.string.join), color = Color.White) } } @@ -1309,6 +1305,6 @@ fun LeaveCommunityButton( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.leave), color = Color.White) + Text(text = stringRes(R.string.leave), color = Color.White) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt index 8b010ec6e4..290f9b8c1c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt @@ -58,7 +58,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -72,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView import com.vitorpamplona.amethyst.ui.screen.FeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.TabRowHeight @@ -118,7 +118,10 @@ fun ChatroomListScreen( } } -data class RouteId(val route: String, val id: String) +data class RouteId( + val route: String, + val id: String, +) @Composable fun ChatroomListTwoPane( @@ -171,7 +174,8 @@ fun ChatroomListTwoPane( } HorizontalDivider( modifier = - Modifier.fillMaxHeight() // fill the max height + Modifier + .fillMaxHeight() // fill the max height .width(DividerThickness), ) } @@ -256,7 +260,7 @@ fun ChatroomListScreenOnlyList( tabs.forEachIndexed { index, tab -> Tab( selected = pagerState.currentPage == index, - text = { Text(text = stringResource(tab.resource)) }, + text = { Text(text = stringRes(tab.resource)) }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } }, ) } @@ -268,7 +272,7 @@ fun ChatroomListScreenOnlyList( ) { Icon( imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(id = R.string.more_options), + contentDescription = stringRes(id = R.string.more_options), tint = MaterialTheme.colorScheme.placeholderText, ) @@ -327,21 +331,21 @@ fun ChatroomTabMenu( ) { DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) { DropdownMenuItem( - text = { Text(stringResource(R.string.mark_all_known_as_read)) }, + text = { Text(stringRes(R.string.mark_all_known_as_read)) }, onClick = { onMarkKnownAsRead() onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.mark_all_new_as_read)) }, + text = { Text(stringRes(R.string.mark_all_new_as_read)) }, onClick = { onMarkNewAsRead() onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.mark_all_as_read)) }, + text = { Text(stringRes(R.string.mark_all_as_read)) }, onClick = { onMarkKnownAsRead() onMarkNewAsRead() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index aedacc4ed8..8ebe5a13d0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import android.widget.Toast -import androidx.compose.animation.Crossfade import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -69,7 +68,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.TextFieldValue @@ -89,6 +87,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrChatroomDataSource import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel import com.vitorpamplona.amethyst.ui.actions.PostButton import com.vitorpamplona.amethyst.ui.actions.ServerOption @@ -108,6 +107,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier @@ -118,7 +118,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size34dp import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText -import com.vitorpamplona.quartz.encoders.RelayUrlFormatter import com.vitorpamplona.quartz.events.ChatMessageEvent import com.vitorpamplona.quartz.events.ChatroomKey import com.vitorpamplona.quartz.events.findURLs @@ -426,7 +425,7 @@ fun PrivateMessageEditFieldRow( modifier = Modifier.fillMaxWidth(), placeholder = { Text( - text = stringResource(R.string.reply_here), + text = stringRes(R.string.reply_here), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -553,10 +552,10 @@ fun NewFeatureNIP17AlertDialog( val scope = rememberCoroutineScope() QuickActionAlertDialog( - title = stringResource(R.string.new_feature_nip17_might_not_be_available_title), - textContent = stringResource(R.string.new_feature_nip17_might_not_be_available_description), + title = stringRes(R.string.new_feature_nip17_might_not_be_available_title), + textContent = stringRes(R.string.new_feature_nip17_might_not_be_available_description), buttonIconResource = R.drawable.incognito, - buttonText = stringResource(R.string.new_feature_nip17_activate), + buttonText = stringRes(R.string.new_feature_nip17_activate), onClickDoOnce = { scope.launch(Dispatchers.IO) { onConfirm() } onDismiss() @@ -585,7 +584,7 @@ fun ThinSendButton( ) { Icon( imageVector = Icons.Default.Send, - contentDescription = stringResource(id = R.string.accessibility_send), + contentDescription = stringRes(id = R.string.accessibility_send), modifier = Size20Modifier, ) } @@ -646,7 +645,7 @@ fun ChatroomHeader( ) Column(modifier = Modifier.padding(start = 10.dp)) { - UsernameDisplay(baseUser) + UsernameDisplay(baseUser, accountViewModel = accountViewModel) } } } @@ -678,7 +677,7 @@ fun GroupChatroomHeader( ) Column(modifier = Modifier.padding(start = 10.dp)) { - RoomNameOnlyDisplay(room, Modifier, FontWeight.Bold, accountViewModel.userProfile()) + RoomNameOnlyDisplay(room, Modifier, FontWeight.Bold, accountViewModel) DisplayUserSetAsSubject(room, accountViewModel, FontWeight.Normal) } } @@ -708,7 +707,7 @@ private fun EditRoomSubjectButton( Icon( tint = Color.White, imageVector = Icons.Default.EditNote, - contentDescription = stringResource(R.string.edits_the_channel_metadata), + contentDescription = stringRes(R.string.edits_the_channel_metadata), ) } } @@ -769,13 +768,13 @@ fun NewSubjectView( Spacer(modifier = Modifier.height(15.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.messages_new_message_subject)) }, + label = { Text(text = stringRes(R.string.messages_new_message_subject)) }, modifier = Modifier.fillMaxWidth(), value = groupName.value, onValueChange = { groupName.value = it }, placeholder = { Text( - text = stringResource(R.string.messages_new_message_subject_caption), + text = stringRes(R.string.messages_new_message_subject_caption), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -789,7 +788,7 @@ fun NewSubjectView( Spacer(modifier = Modifier.height(15.dp)) OutlinedTextField( - label = { Text(text = stringResource(R.string.messages_new_subject_message)) }, + label = { Text(text = stringRes(R.string.messages_new_subject_message)) }, modifier = Modifier .fillMaxWidth() @@ -798,7 +797,7 @@ fun NewSubjectView( onValueChange = { message.value = it }, placeholder = { Text( - text = stringResource(R.string.messages_new_subject_message_placeholder), + text = stringRes(R.string.messages_new_subject_message_placeholder), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -829,7 +828,7 @@ fun LongRoomHeader( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.messages_group_descriptor), + text = stringRes(id = R.string.messages_group_descriptor), fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -862,47 +861,23 @@ fun LongRoomHeader( } } -@Composable -fun DMRelayLine( - baseUser: User, - accountViewModel: AccountViewModel, -) { - ObserveRelayListForDMs(pubkey = baseUser.pubkeyHex, accountViewModel = accountViewModel) { - val relayList = it?.relays() - if (relayList.isNullOrEmpty()) { - Text( - text = stringResource(id = R.string.dm_relays_regular), - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } else { - Text( - text = stringResource(id = R.string.dm_relays_through, relayList.joinToString(", ") { RelayUrlFormatter.displayUrl(it) }), - color = MaterialTheme.colorScheme.placeholderText, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } -} - @Composable fun RoomNameOnlyDisplay( room: ChatroomKey, modifier: Modifier, fontWeight: FontWeight = FontWeight.Bold, - loggedInUser: User, + accountViewModel: AccountViewModel, ) { val roomSubject by - loggedInUser + accountViewModel + .userProfile() .live() .messages .map { it.user.privateChatrooms[room]?.subject } .distinctUntilChanged() - .observeAsState(loggedInUser.privateChatrooms[room]?.subject) + .observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject) - Crossfade(targetState = roomSubject, modifier) { + CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) { if (it != null && it.isNotBlank()) { DisplayRoomSubject(it, fontWeight) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ConnectOrbotDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ConnectOrbotDialog.kt index 0aa0ebf056..9f57100379 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ConnectOrbotDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ConnectOrbotDialog.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -54,6 +53,7 @@ import com.halilibo.richtext.markdown.BasicMarkdown import com.halilibo.richtext.ui.material3.RichText import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.RichTextDefaults import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -81,7 +81,7 @@ fun ConnectOrbotDialog( ) { CloseButton(onPress = { onClose() }) - val toastMessage = stringResource(R.string.invalid_port_number) + val toastMessage = stringRes(R.string.invalid_port_number) UseOrbotButton( onPost = { @@ -115,7 +115,7 @@ fun ConnectOrbotDialog( ) Row { - val content1 = stringResource(R.string.connect_through_your_orbot_setup_markdown) + val content1 = stringRes(R.string.connect_through_your_orbot_setup_markdown) val astNode1 = remember { @@ -144,7 +144,7 @@ fun ConnectOrbotDialog( capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Number, ), - label = { Text(text = stringResource(R.string.orbot_socks_port)) }, + label = { Text(text = stringRes(R.string.orbot_socks_port)) }, placeholder = { Text( text = "9050", @@ -178,6 +178,6 @@ fun UseOrbotButton( containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray, ), ) { - Text(text = stringResource(R.string.use_orbot), color = Color.White) + Text(text = stringRes(R.string.use_orbot), color = Color.White) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt index 3120345fe0..250eca54f2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column @@ -52,13 +51,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.ChannelCardCompose import com.vitorpamplona.amethyst.ui.screen.FeedEmpty @@ -77,6 +76,7 @@ import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.TabRowHeight @@ -146,8 +146,7 @@ fun DiscoverScreen( ScrollStateKeys.DISCOVER_CHATS, ChannelCreateEvent.KIND, ), - ) - .toImmutableList(), + ).toImmutableList(), ) } @@ -204,7 +203,7 @@ private fun DiscoverPages( tabs.forEachIndexed { index, tab -> Tab( selected = pagerState.currentPage == index, - text = { Text(text = stringResource(tab.resource)) }, + text = { Text(text = stringRes(tab.resource)) }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } }, ) } @@ -213,8 +212,7 @@ private fun DiscoverPages( HorizontalPager(state = pagerState) { page -> RefresheableBox(tabs[page].viewModel, true) { if (tabs[page].viewModel is NostrDiscoverMarketplaceFeedViewModel) { - SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { - listState -> + SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState -> RenderDiscoverFeed( viewModel = tabs[page].viewModel, routeForLastRead = tabs[page].routeForLastRead, @@ -225,8 +223,7 @@ private fun DiscoverPages( ) } } else { - SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { - listState -> + SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState -> RenderDiscoverFeed( viewModel = tabs[page].viewModel, routeForLastRead = tabs[page].routeForLastRead, @@ -252,10 +249,11 @@ private fun RenderDiscoverFeed( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), label = "RenderDiscoverFeed", + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Empty -> { @@ -292,10 +290,11 @@ private fun RenderDiscoverFeed( ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), label = "RenderDiscoverFeed", + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Empty -> { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt index 492ec612e2..0a502c6665 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt index 725154bbdc..4063ceef19 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt @@ -35,7 +35,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner @@ -182,7 +181,11 @@ fun GeoHashActionOptions( tag: String, accountViewModel: AccountViewModel, ) { - val userState by accountViewModel.userProfile().live().follows.observeAsState() + val userState by accountViewModel + .userProfile() + .live() + .follows + .observeAsState() val isFollowingTag by remember(userState) { derivedStateOf { userState?.user?.isFollowingGeohashCached(tag) ?: false } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index 1c30be2594..d6392e561c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization @@ -75,6 +74,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.StringFeedView +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -162,7 +162,7 @@ fun HiddenUsersScreen( }, ) - Text(stringResource(R.string.warn_when_posts_have_reports_from_your_follows)) + Text(stringRes(R.string.warn_when_posts_have_reports_from_your_follows)) } Row(verticalAlignment = Alignment.CenterVertically) { @@ -174,7 +174,7 @@ fun HiddenUsersScreen( }, ) - Text(stringResource(R.string.filter_spam_from_strangers)) + Text(stringRes(R.string.filter_spam_from_strangers)) } ScrollableTabRow( @@ -188,17 +188,17 @@ fun HiddenUsersScreen( Tab( selected = pagerState.currentPage == 0, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(text = stringResource(R.string.blocked_users)) }, + text = { Text(text = stringRes(R.string.blocked_users)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(text = stringResource(R.string.spamming_users)) }, + text = { Text(text = stringRes(R.string.spamming_users)) }, ) Tab( selected = pagerState.currentPage == 2, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } }, - text = { Text(text = stringResource(R.string.hidden_words)) }, + text = { Text(text = stringRes(R.string.hidden_words)) }, ) } HorizontalPager(state = pagerState) { page -> @@ -219,6 +219,7 @@ private fun HiddenWordsFeed( RefresheableBox(hiddenWordsViewModel, false) { StringFeedView( hiddenWordsViewModel, + accountViewModel, post = { AddMuteWordTextField(accountViewModel) }, ) { MutedWordHeader(tag = it, account = accountViewModel) @@ -235,11 +236,11 @@ private fun AddMuteWordTextField(accountViewModel: AccountViewModel) { OutlinedTextField( value = currentWordToAdd.value, onValueChange = { currentWordToAdd.value = it }, - label = { Text(text = stringResource(R.string.hide_new_word_label)) }, + label = { Text(text = stringRes(R.string.hide_new_word_label)) }, modifier = Modifier.fillMaxWidth().padding(10.dp), placeholder = { Text( - text = stringResource(R.string.hide_new_word_label), + text = stringRes(R.string.hide_new_word_label), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -353,7 +354,7 @@ fun HideWordButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.block_only), color = Color.White) + Text(text = stringRes(R.string.block_only), color = Color.White) } } @@ -372,7 +373,7 @@ fun ShowWordButton( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center) + Text(text = stringRes(text), color = Color.White, textAlign = TextAlign.Center) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index d422a0f971..a1017655e1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn -import androidx.compose.animation.Crossfade import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -47,7 +46,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -55,6 +53,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.OnlineChecker +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog import com.vitorpamplona.amethyst.ui.screen.FeedViewModel @@ -66,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow @@ -118,8 +118,7 @@ private fun AssembleHomeTabs( Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES, ), - ) - .toImmutableList(), + ).toImmutableList(), ) } @@ -185,7 +184,7 @@ private fun HomePages( tabs.forEachIndexed { index, tab -> Tab( selected = pagerState.currentPage == index, - text = { Text(text = stringResource(tab.resource)) }, + text = { Text(text = stringRes(tab.resource)) }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } }, ) } @@ -240,9 +239,9 @@ fun HomeFeedEmpty(onRefresh: () -> Unit) { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.feed_is_empty)) + Text(stringRes(R.string.feed_is_empty)) Spacer(modifier = StdVertSpacer) - OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) } + OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) } } } @@ -289,9 +288,10 @@ fun CrossfadeCheckIfUrlIsOnline( } } - Crossfade( + CrossfadeIfEnabled( targetState = online, label = "CheckIfUrlIsOnline", + accountViewModel = accountViewModel, ) { if (it) { whenOnline() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt index 51143cfc32..3ef9c16504 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt @@ -36,13 +36,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.navigation.Route +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.ChannelCreateEvent import com.vitorpamplona.quartz.events.ChatroomKeyable import com.vitorpamplona.quartz.events.EventInterface @@ -113,7 +113,7 @@ fun LoadRedirectScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(stringResource(R.string.looking_for_event, baseNote.idHex)) + Text(stringRes(R.string.looking_for_event, baseNote.idHex)) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 83b3a2c929..e1deea68b3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -64,16 +64,15 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.vitorpamplona.amethyst.R @@ -108,6 +107,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.encoders.RelayUrlFormatter import kotlinx.coroutines.launch import kotlin.math.abs @@ -141,7 +141,6 @@ fun MainScreen( } val navController = rememberNavController() - val navState = navController.currentBackStackEntryAsState() val orientation = LocalConfiguration.current.orientation val currentDrawerState = drawerState.currentValue @@ -165,9 +164,6 @@ fun MainScreen( } } - DisplayErrorMessages(accountViewModel) - DisplayNotifyMessages(accountViewModel, nav) - val navPopBack = remember(navController) { { @@ -176,7 +172,10 @@ fun MainScreen( } } - val followLists: FollowListViewModel = + DisplayErrorMessages(accountViewModel) + DisplayNotifyMessages(accountViewModel, nav) + + val followListsViewModel: FollowListViewModel = viewModel( key = "FollowListViewModel", factory = FollowListViewModel.Factory(accountViewModel.account), @@ -297,52 +296,6 @@ fun MainScreen( } } - val bottomBarHeightPx = with(LocalDensity.current) { 50.dp.roundToPx().toFloat() } - val bottomBarOffsetHeightPx = remember { mutableFloatStateOf(0f) } - val shouldShow = remember { mutableStateOf(true) } - - val nestedScrollConnection = - remember { - object : NestedScrollConnection { - override fun onPreScroll( - available: Offset, - source: NestedScrollSource, - ): Offset { - val newOffset = bottomBarOffsetHeightPx.floatValue + available.y - - if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) { - val newBottomBarOffset = - if (navState.value?.destination?.route !in InvertedLayouts) { - newOffset.coerceIn(-bottomBarHeightPx, 0f) - } else { - newOffset.coerceIn(0f, bottomBarHeightPx) - } - - if (newBottomBarOffset != bottomBarOffsetHeightPx.floatValue) { - bottomBarOffsetHeightPx.floatValue = newBottomBarOffset - } - } else { - if (abs(bottomBarOffsetHeightPx.floatValue) > 0.1) { - bottomBarOffsetHeightPx.floatValue = 0f - } - } - - val newShouldShow = abs(bottomBarOffsetHeightPx.floatValue) < bottomBarHeightPx / 2.0f - - if (shouldShow.value != newShouldShow) { - shouldShow.value = newShouldShow - } - - return Offset.Zero - } - } - } - - WatchNavStateToUpdateBarVisibility(navState) { - bottomBarOffsetHeightPx.floatValue = 0f - shouldShow.value = true - } - ModalNavigationDrawer( drawerState = drawerState, drawerContent = { @@ -350,87 +303,29 @@ fun MainScreen( BackHandler(enabled = drawerState.isOpen) { scope.launch { drawerState.close() } } }, content = { - Scaffold( - // containerColor = Color.Unspecified, - modifier = - Modifier - .statusBarsPadding() - .nestedScroll(nestedScrollConnection), - bottomBar = { - AnimatedContent( - targetState = shouldShow.value, - transitionSpec = AnimatedContentTransitionScope::bottomBarTransitionSpec, - label = "BottomBarAnimatedContent", - ) { isVisible -> - if (isVisible) { - AppBottomBar(accountViewModel, navState, navBottomRow) - } - } - }, - topBar = { - AnimatedContent( - targetState = shouldShow.value, - transitionSpec = AnimatedContentTransitionScope::topBarTransitionSpec, - label = "TopBarAnimatedContent", - ) { isVisible -> - if (isVisible) { - AppTopBar( - followLists, - navState, - drawerState, - accountViewModel, - nav = nav, - navPopBack, - ) - } - } - }, - floatingActionButton = { - AnimatedVisibility( - visible = shouldShow.value, - enter = remember { scaleIn() }, - exit = remember { scaleOut() }, - ) { - Box( - modifier = Modifier.defaultMinSize(minWidth = 55.dp, minHeight = 55.dp), - ) { - FloatingButtons( - navState, - accountViewModel, - accountStateViewModel, - nav, - navBottomRow, - ) - } - } - }, - ) { - Column( - modifier = - Modifier.padding( - top = it.calculateTopPadding(), - bottom = it.calculateBottomPadding(), - ), - ) { - AppNavigation( - homeFeedViewModel = homeFeedViewModel, - repliesFeedViewModel = repliesFeedViewModel, - knownFeedViewModel = knownFeedViewModel, - newFeedViewModel = newFeedViewModel, - videoFeedViewModel = videoFeedViewModel, - discoverNip89FeedViewModel = discoverNIP89FeedViewModel, - discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel = discoveryChatFeedViewModel, - notifFeedViewModel = notifFeedViewModel, - userReactionsStatsModel = userReactionsStatsModel, - navController = navController, - accountViewModel = accountViewModel, - sharedPreferencesViewModel = sharedPreferencesViewModel, - ) - } - } + MainScaffold( + navController = navController, + navBottomRow = navBottomRow, + navPopBack = navPopBack, + openDrawer = { scope.launch { drawerState.open() } }, + accountStateViewModel = accountStateViewModel, + userReactionsStatsModel = userReactionsStatsModel, + followListsViewModel = followListsViewModel, + homeFeedViewModel = homeFeedViewModel, + repliesFeedViewModel = repliesFeedViewModel, + knownFeedViewModel = knownFeedViewModel, + newFeedViewModel = newFeedViewModel, + videoFeedViewModel = videoFeedViewModel, + discoverNIP89FeedViewModel = discoverNIP89FeedViewModel, + discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, + discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, + discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, + discoveryChatFeedViewModel = discoveryChatFeedViewModel, + notifFeedViewModel = notifFeedViewModel, + sharedPreferencesViewModel = sharedPreferencesViewModel, + accountViewModel = accountViewModel, + nav = nav, + ) }, ) @@ -456,6 +351,168 @@ fun MainScreen( } } +@Composable +private fun MainScaffold( + navController: NavHostController, + navBottomRow: (Route, Boolean) -> Unit, + navPopBack: () -> Unit, + openDrawer: () -> Unit, + accountStateViewModel: AccountStateViewModel, + userReactionsStatsModel: UserReactionsViewModel, + followListsViewModel: FollowListViewModel, + homeFeedViewModel: NostrHomeFeedViewModel, + repliesFeedViewModel: NostrHomeRepliesFeedViewModel, + knownFeedViewModel: NostrChatroomListKnownFeedViewModel, + newFeedViewModel: NostrChatroomListNewFeedViewModel, + videoFeedViewModel: NostrVideoFeedViewModel, + discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel, + discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel, + discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel, + discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel, + discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel, + notifFeedViewModel: NotificationViewModel, + sharedPreferencesViewModel: SharedPreferencesViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val navState = navController.currentBackStackEntryAsState() + + val shouldShow = remember { mutableStateOf(true) } + + val modifier = + if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) { + val bottomBarHeightPx = with(LocalDensity.current) { 50.dp.roundToPx().toFloat() } + val bottomBarOffsetHeightPx = remember { mutableFloatStateOf(0f) } + + val nestedScrollConnection = + remember { + object : NestedScrollConnection { + override fun onPreScroll( + available: Offset, + source: NestedScrollSource, + ): Offset { + val newOffset = bottomBarOffsetHeightPx.floatValue + available.y + + if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) { + val newBottomBarOffset = + if (navState.value?.destination?.route !in InvertedLayouts) { + newOffset.coerceIn(-bottomBarHeightPx, 0f) + } else { + newOffset.coerceIn(0f, bottomBarHeightPx) + } + + if (newBottomBarOffset != bottomBarOffsetHeightPx.floatValue) { + bottomBarOffsetHeightPx.floatValue = newBottomBarOffset + } + } else { + if (abs(bottomBarOffsetHeightPx.floatValue) > 0.1) { + bottomBarOffsetHeightPx.floatValue = 0f + } + } + + val newShouldShow = abs(bottomBarOffsetHeightPx.floatValue) < bottomBarHeightPx / 2.0f + + if (shouldShow.value != newShouldShow) { + shouldShow.value = newShouldShow + } + + return Offset.Zero + } + } + } + + WatchNavStateToUpdateBarVisibility(navState) { + bottomBarOffsetHeightPx.floatValue = 0f + shouldShow.value = true + } + + Modifier + .statusBarsPadding() + .nestedScroll(nestedScrollConnection) + } else { + Modifier + .statusBarsPadding() + } + + Scaffold( + modifier = modifier, + bottomBar = { + AnimatedContent( + targetState = shouldShow.value, + transitionSpec = AnimatedContentTransitionScope::bottomBarTransitionSpec, + label = "BottomBarAnimatedContent", + ) { isVisible -> + if (isVisible) { + AppBottomBar(accountViewModel, navState, navBottomRow) + } + } + }, + topBar = { + AnimatedContent( + targetState = shouldShow.value, + transitionSpec = AnimatedContentTransitionScope::topBarTransitionSpec, + label = "TopBarAnimatedContent", + ) { isVisible -> + if (isVisible) { + AppTopBar( + followListsViewModel, + navState, + openDrawer, + accountViewModel, + nav = nav, + navPopBack, + ) + } + } + }, + floatingActionButton = { + AnimatedVisibility( + visible = shouldShow.value, + enter = remember { scaleIn() }, + exit = remember { scaleOut() }, + ) { + Box( + modifier = Modifier.defaultMinSize(minWidth = 55.dp, minHeight = 55.dp), + ) { + FloatingButtons( + navState, + accountViewModel, + accountStateViewModel, + nav, + navBottomRow, + ) + } + } + }, + ) { + Column( + modifier = + Modifier.padding( + top = it.calculateTopPadding(), + bottom = it.calculateBottomPadding(), + ), + ) { + AppNavigation( + homeFeedViewModel = homeFeedViewModel, + repliesFeedViewModel = repliesFeedViewModel, + knownFeedViewModel = knownFeedViewModel, + newFeedViewModel = newFeedViewModel, + videoFeedViewModel = videoFeedViewModel, + discoverNip89FeedViewModel = discoverNIP89FeedViewModel, + discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, + discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, + discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, + discoveryChatFeedViewModel = discoveryChatFeedViewModel, + notifFeedViewModel = notifFeedViewModel, + userReactionsStatsModel = userReactionsStatsModel, + navController = navController, + accountViewModel = accountViewModel, + sharedPreferencesViewModel = sharedPreferencesViewModel, + ) + } + } +} + @OptIn(ExperimentalAnimationApi::class) private fun AnimatedContentTransitionScope.topBarTransitionSpec(): ContentTransform = topBarAnimation @@ -471,7 +528,6 @@ val bottomBarAnimation: ContentTransform = @Composable private fun DisplayErrorMessages(accountViewModel: AccountViewModel) { - val context = LocalContext.current val openDialogMsg = accountViewModel.toasts.collectAsStateWithLifecycle(null) openDialogMsg.value?.let { obj -> @@ -479,15 +535,15 @@ private fun DisplayErrorMessages(accountViewModel: AccountViewModel) { is ResourceToastMsg -> if (obj.params != null) { InformationDialog( - context.getString(obj.titleResId), - context.getString(obj.resourceId, *obj.params), + stringRes(obj.titleResId), + stringRes(obj.resourceId, *obj.params), ) { accountViewModel.clearToasts() } } else { InformationDialog( - context.getString(obj.titleResId), - context.getString(obj.resourceId), + stringRes(obj.titleResId), + stringRes(obj.resourceId), ) { accountViewModel.clearToasts() } @@ -515,7 +571,7 @@ private fun DisplayNotifyMessages( openDialogMsg.value?.firstOrNull()?.let { request -> NotifyRequestDialog( title = - stringResource( + stringRes( id = R.string.payment_required_title, RelayUrlFormatter.displayUrl(request.relayUrl), ), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt index 3ec2a88da4..f5f1e09d3e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -51,7 +50,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -66,6 +64,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.ZapPaymentHandler +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.DVMCard @@ -84,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewMo import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp import com.vitorpamplona.amethyst.ui.theme.QuoteBorder @@ -115,8 +115,9 @@ fun NIP90ContentDiscoveryScreen( NIP90ContentDiscoveryScreen(baseNote, accountViewModel, nav) }, onBlank = { - FeedEmptyWithStatus(baseNote, stringResource(R.string.dvm_looking_for_app), accountViewModel, nav) + FeedEmptyWithStatus(baseNote, stringRes(R.string.dvm_looking_for_app), accountViewModel, nav) }, + accountViewModel, ) } } @@ -165,7 +166,7 @@ fun NIP90ContentDiscoveryScreen( } else { // TODO: Make a good splash screen with loading animation for this DVM. // FeedDVM(appDefinition, null, accountViewModel, nav) - FeedEmptyWithStatus(appDefinition, stringResource(R.string.dvm_requesting_job), accountViewModel, nav) + FeedEmptyWithStatus(appDefinition, stringRes(R.string.dvm_requesting_job), accountViewModel, nav) } } } @@ -227,7 +228,7 @@ fun ObserverDvmStatusResponse( } } else { // TODO: Make a good splash screen with loading animation for this DVM. - FeedEmptyWithStatus(appDefinition, stringResource(R.string.dvm_waiting_status), accountViewModel, nav) + FeedEmptyWithStatus(appDefinition, stringRes(R.string.dvm_waiting_status), accountViewModel, nav) } } @@ -330,8 +331,8 @@ fun FeedDVM( val invoice = amountTag?.lnInvoice - val thankYou = stringResource(id = R.string.dvm_waiting_to_confirm_payment) - val nwcPaymentRequest = stringResource(id = R.string.nwc_payment_request) + val thankYou = stringRes(id = R.string.dvm_waiting_to_confirm_payment) + val nwcPaymentRequest = stringRes(id = R.string.nwc_payment_request) if (invoice != null) { val context = LocalContext.current @@ -346,7 +347,8 @@ fun FeedDVM( onResponse = { response -> currentStatus = if (response is PayInvoiceErrorResponse) { - context.getString( + stringRes( + context, R.string.wallet_connect_pay_invoice_error_error, response.error?.message ?: response.error?.code?.toString() ?: "Error parsing error message", @@ -483,7 +485,7 @@ fun ZapDVMButton( if (showErrorMessageDialog != null) { ErrorMessageDialog( - title = stringResource(id = R.string.error_dialog_zap_error), + title = stringRes(id = R.string.error_dialog_zap_error), textContent = showErrorMessageDialog ?: "", onClickStartMessage = { baseNote.author?.let { @@ -530,8 +532,7 @@ fun ZapDVMButton( targetValue = zappingProgress, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, label = "ZapIconIndicator", - ) - .value, + ).value, modifier = remember { Modifier.size(animationSize) }, strokeWidth = 2.dp, color = grayTint, @@ -549,7 +550,7 @@ fun ZapDVMButton( } } - Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") { + CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) { if (it) { ZappedIcon(iconSizeModifier) } else { @@ -561,9 +562,9 @@ fun ZapDVMButton( } if (hasZapped) { - Text(text = stringResource(id = R.string.thank_you)) + Text(text = stringRes(id = R.string.thank_you)) } else { - Text(text = "Zap " + (amount / 1000).toString() + " sats to the DVM") // stringResource(id = R.string.donate_now)) + Text(text = "Zap " + (amount / 1000).toString() + " sats to the DVM") // stringRes(id = R.string.donate_now)) } } } @@ -632,8 +633,7 @@ fun observeAppDefinition(appDefinitionNote: Note): DVMCard { description = noteEvent?.appMetaData()?.about ?: "", cover = noteEvent?.appMetaData()?.image?.ifBlank { null }, ) - } - .distinctUntilChanged() + }.distinctUntilChanged() .observeAsState( DVMCard( name = noteEvent.appMetaData()?.name ?: "", diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 62bfe7fda6..7761117580 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -20,12 +20,9 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.scrollBy @@ -98,7 +95,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -118,10 +114,12 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.InformationDialog import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji @@ -155,10 +153,12 @@ import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import com.vitorpamplona.amethyst.ui.theme.Size100dp import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size16Modifier import com.vitorpamplona.amethyst.ui.theme.Size25Modifier @@ -166,6 +166,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.amethyst.ui.theme.userProfileBorderModifier import com.vitorpamplona.quartz.events.AppDefinitionEvent import com.vitorpamplona.quartz.events.BadgeDefinitionEvent import com.vitorpamplona.quartz.events.BadgeProfilesEvent @@ -429,8 +430,7 @@ private fun RenderSurface( } } }, - ) - .fillMaxHeight() + ).fillMaxHeight() }, ) { RenderScreen( @@ -550,8 +550,7 @@ fun UpdateThreadsAndRepliesWhenBlockUnblock( accountViewModel.account.liveHiddenUsers .map { it.hiddenUsers.contains(baseUser.pubkeyHex) || it.spammers.contains(baseUser.pubkeyHex) - } - .observeAsState(accountViewModel.account.isHidden(baseUser)) + }.observeAsState(accountViewModel.account.isHidden(baseUser)) LaunchedEffect(key1 = isHidden) { threadsViewModel.invalidateData() @@ -569,8 +568,8 @@ private fun CreateAndRenderTabs( val tabs = listOf<@Composable (() -> Unit)?>( - { Text(text = stringResource(R.string.notes)) }, - { Text(text = stringResource(R.string.replies)) }, + { Text(text = stringRes(R.string.notes)) }, + { Text(text = stringRes(R.string.replies)) }, { FollowTabHeader(baseUser) }, { FollowersTabHeader(baseUser) }, { ZapTabHeader(baseUser) }, @@ -597,10 +596,14 @@ private fun RelaysTabHeader(baseUser: User) { val userStateRelayInfo by baseUser.live().relayInfo.observeAsState() val userRelays = remember(userStateRelayInfo) { - userStateRelayInfo?.user?.latestContactList?.relays()?.size ?: "--" + userStateRelayInfo + ?.user + ?.latestContactList + ?.relays() + ?.size ?: "--" } - Text(text = "$userRelaysBeingUsed / $userRelays ${stringResource(R.string.relays)}") + Text(text = "$userRelaysBeingUsed / $userRelays ${stringRes(R.string.relays)}") } @Composable @@ -618,7 +621,7 @@ private fun ReportsTabHeader(baseUser: User) { } } - Text(text = "$userReports ${stringResource(R.string.reports)}") + Text(text = "$userReports ${stringRes(R.string.reports)}") } @Composable @@ -631,7 +634,7 @@ private fun FollowedTagsTabHeader(baseUser: User) { } } - Text(text = "$usertags ${stringResource(R.string.followed_tags)}") + Text(text = "$usertags ${stringRes(R.string.followed_tags)}") } @Composable @@ -656,7 +659,7 @@ private fun BookmarkTabHeader(baseUser: User) { } } - Text(text = "$userBookmarks ${stringResource(R.string.bookmarks)}") + Text(text = "$userBookmarks ${stringRes(R.string.bookmarks)}") } @Composable @@ -673,7 +676,7 @@ private fun ZapTabHeader(baseUser: User) { } } - Text(text = "${showAmountAxis(zapAmount)} ${stringResource(id = R.string.zaps)}") + Text(text = "${showAmountAxis(zapAmount)} ${stringRes(id = R.string.zaps)}") } @Composable @@ -681,7 +684,7 @@ private fun FollowersTabHeader(baseUser: User) { val userState by baseUser.live().followers.observeAsState() var followerCount by remember { mutableStateOf("--") } - val text = stringResource(R.string.followers) + val text = stringRes(R.string.followers) LaunchedEffect(key1 = userState) { launch(Dispatchers.IO) { @@ -701,7 +704,7 @@ private fun FollowTabHeader(baseUser: User) { val userState by baseUser.live().follows.observeAsState() var followCount by remember { mutableStateOf("--") } - val text = stringResource(R.string.follows) + val text = stringRes(R.string.follows) LaunchedEffect(key1 = userState) { launch(Dispatchers.IO) { @@ -752,7 +755,7 @@ private fun ProfileHeader( Icon( tint = MaterialTheme.colorScheme.placeholderText, imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(R.string.more_options), + contentDescription = stringRes(R.string.more_options), ) UserProfileDropDownMenu( @@ -780,13 +783,8 @@ private fun ProfileHeader( ClickableUserPicture( baseUser = baseUser, accountViewModel = accountViewModel, - size = 100.dp, - modifier = - Modifier.border( - 3.dp, - MaterialTheme.colorScheme.background, - CircleShape, - ), + size = Size100dp, + modifier = MaterialTheme.colorScheme.userProfileBorderModifier, onClick = { if (baseUser.profilePicture() != null) { zoomImageDialogOpen = true @@ -922,31 +920,28 @@ fun WatchIsHiddenUser( accountViewModel.account.liveHiddenUsers .map { it.hiddenUsers.contains(baseUser.pubkeyHex) || it.spammers.contains(baseUser.pubkeyHex) - } - .observeAsState(accountViewModel.account.isHidden(baseUser)) + }.observeAsState(accountViewModel.account.isHidden(baseUser)) content(isHidden) } -fun getIdentityClaimIcon(identity: IdentityClaim): Int { - return when (identity) { +fun getIdentityClaimIcon(identity: IdentityClaim): Int = + when (identity) { is TwitterIdentity -> R.drawable.twitter is TelegramIdentity -> R.drawable.telegram is MastodonIdentity -> R.drawable.mastodon is GitHubIdentity -> R.drawable.github else -> R.drawable.github } -} -fun getIdentityClaimDescription(identity: IdentityClaim): Int { - return when (identity) { +fun getIdentityClaimDescription(identity: IdentityClaim): Int = + when (identity) { is TwitterIdentity -> R.string.twitter is TelegramIdentity -> R.string.telegram is MastodonIdentity -> R.string.mastodon is GitHubIdentity -> R.string.github else -> R.drawable.github } -} @Composable private fun DrawAdditionalInfo( @@ -962,11 +957,6 @@ private fun DrawAdditionalInfo( val uri = LocalUriHandler.current val clipboardManager = LocalClipboardManager.current - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - user.toBestDisplayName().let { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 7.dp)) { CreateTextWithEmoji( @@ -996,7 +986,7 @@ private fun DrawAdditionalInfo( ) { Icon( imageVector = Icons.Default.ContentCopy, - contentDescription = stringResource(id = R.string.copy_npub_to_clipboard), + contentDescription = stringRes(id = R.string.copy_npub_to_clipboard), modifier = Size15Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) @@ -1007,7 +997,8 @@ private fun DrawAdditionalInfo( if (dialogOpen) { ShowQRDialog( user, - automaticallyShowProfilePicture, + accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onScan = { dialogOpen = false nav(it) @@ -1022,7 +1013,7 @@ private fun DrawAdditionalInfo( ) { Icon( painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringResource(id = R.string.show_npub_as_a_qr_code), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), modifier = Size15Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) @@ -1039,7 +1030,7 @@ private fun DrawAdditionalInfo( Icon( tint = MaterialTheme.colorScheme.placeholderText, imageVector = Icons.Default.Link, - contentDescription = stringResource(R.string.website), + contentDescription = stringRes(R.string.website), modifier = Modifier.size(16.dp), ) @@ -1073,7 +1064,7 @@ private fun DrawAdditionalInfo( Icon( tint = Color.Unspecified, painter = painterResource(id = getIdentityClaimIcon(identity)), - contentDescription = stringResource(getIdentityClaimDescription(identity)), + contentDescription = stringRes(getIdentityClaimDescription(identity)), modifier = Modifier.size(16.dp), ) @@ -1110,7 +1101,7 @@ private fun DrawAdditionalInfo( } } - DisplayAppRecommendations(appRecommendations, nav) + DisplayAppRecommendations(appRecommendations, accountViewModel, nav) } @Composable @@ -1128,7 +1119,7 @@ fun DisplayLNAddress( if (showErrorMessageDialog != null) { ErrorMessageDialog( - title = stringResource(id = R.string.error_dialog_zap_error), + title = stringRes(id = R.string.error_dialog_zap_error), textContent = showErrorMessageDialog ?: "", onClickStartMessage = { scope.launch(Dispatchers.IO) { @@ -1143,7 +1134,7 @@ fun DisplayLNAddress( var showInfoMessageDialog by remember { mutableStateOf(null) } if (showInfoMessageDialog != null) { InformationDialog( - title = context.getString(R.string.payment_successful), + title = stringRes(context, R.string.payment_successful), textContent = showInfoMessageDialog ?: "", ) { showInfoMessageDialog = null @@ -1180,12 +1171,12 @@ fun DisplayLNAddress( if (accountViewModel.account.hasWalletConnectSetup()) { accountViewModel.account.sendZapPaymentRequestFor(it, null, onSent = {}) { response -> if (response is PayInvoiceSuccessResponse) { - showInfoMessageDialog = context.getString(R.string.payment_successful) + showInfoMessageDialog = stringRes(context, R.string.payment_successful) } else if (response is PayInvoiceErrorResponse) { showErrorMessageDialog = response.error?.message ?: response.error?.code?.toString() - ?: context.getString(R.string.error_parsing_error_message) + ?: stringRes(context, R.string.error_parsing_error_message) } } } else { @@ -1204,20 +1195,22 @@ fun DisplayLNAddress( @OptIn(ExperimentalLayoutApi::class) private fun DisplayAppRecommendations( appRecommendations: NostrUserAppRecommendationsFeedViewModel, + accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { val feedState by appRecommendations.feedContent.collectAsStateWithLifecycle() LaunchedEffect(key1 = Unit) { appRecommendations.invalidateData() } - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Loaded -> { Column { - Text(stringResource(id = R.string.recommended_apps)) + Text(stringRes(id = R.string.recommended_apps)) FlowRow( verticalArrangement = Arrangement.Center, @@ -1279,17 +1272,17 @@ private fun DisplayBadges( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - LoadAddressableNote( aTag = BadgeProfilesEvent.createAddressTag(baseUser.pubkeyHex), accountViewModel = accountViewModel, ) { note -> if (note != null) { - WatchAndRenderBadgeList(note, automaticallyShowProfilePicture, nav) + WatchAndRenderBadgeList( + note = note, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + nav = nav, + ) } } } @@ -1298,6 +1291,7 @@ private fun DisplayBadges( private fun WatchAndRenderBadgeList( note: AddressableNote, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, ) { val badgeList by @@ -1308,7 +1302,7 @@ private fun WatchAndRenderBadgeList( .distinctUntilChanged() .observeAsState() - badgeList?.let { list -> RenderBadgeList(list, loadProfilePicture, nav) } + badgeList?.let { list -> RenderBadgeList(list, loadProfilePicture, loadRobohash, nav) } } @Composable @@ -1316,13 +1310,14 @@ private fun WatchAndRenderBadgeList( private fun RenderBadgeList( list: ImmutableList, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, ) { FlowRow( verticalArrangement = Arrangement.Center, modifier = Modifier.padding(vertical = 5.dp), ) { - list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, loadProfilePicture, nav) } + list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, loadProfilePicture, loadRobohash, nav) } } } @@ -1330,6 +1325,7 @@ private fun RenderBadgeList( private fun LoadAndRenderBadge( badgeAwardEventHex: String, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, ) { var baseNote by remember(badgeAwardEventHex) { mutableStateOf(LocalCache.getNoteIfExists(badgeAwardEventHex)) } @@ -1342,37 +1338,40 @@ private fun LoadAndRenderBadge( } } - baseNote?.let { ObserveAndRenderBadge(it, loadProfilePicture, nav) } + baseNote?.let { ObserveAndRenderBadge(it, loadProfilePicture, loadRobohash, nav) } } @Composable private fun ObserveAndRenderBadge( it: Note, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, ) { val badgeAwardState by it.live().metadata.observeAsState() val baseBadgeDefinition by remember(badgeAwardState) { derivedStateOf { badgeAwardState?.note?.replyTo?.firstOrNull() } } - baseBadgeDefinition?.let { BadgeThumb(it, loadProfilePicture, nav, Size35dp) } + baseBadgeDefinition?.let { BadgeThumb(it, loadProfilePicture, loadRobohash, nav, Size35dp) } } @Composable fun BadgeThumb( note: Note, loadProfilePicture: Boolean, + loadRobohash: Boolean, nav: (String) -> Unit, size: Dp, pictureModifier: Modifier = Modifier, ) { - BadgeThumb(note, loadProfilePicture, size, pictureModifier) { nav("Note/${note.idHex}") } + BadgeThumb(note, loadProfilePicture, loadRobohash, size, pictureModifier) { nav("Note/${note.idHex}") } } @Composable fun BadgeThumb( baseNote: Note, loadProfilePicture: Boolean, + loadRobohash: Boolean, size: Dp, pictureModifier: Modifier = Modifier, onClick: ((String) -> Unit)? = null, @@ -1384,7 +1383,7 @@ fun BadgeThumb( .height(size) }, ) { - WatchAndRenderBadgeImage(baseNote, loadProfilePicture, size, pictureModifier, onClick) + WatchAndRenderBadgeImage(baseNote, loadProfilePicture, loadRobohash, size, pictureModifier, onClick) } } @@ -1392,6 +1391,7 @@ fun BadgeThumb( private fun WatchAndRenderBadgeImage( baseNote: Note, loadProfilePicture: Boolean, + loadRobohash: Boolean, size: Dp, pictureModifier: Modifier, onClick: ((String) -> Unit)?, @@ -1409,19 +1409,20 @@ private fun WatchAndRenderBadgeImage( if (image == null) { RobohashAsyncImage( robot = "authornotfound", - contentDescription = stringResource(R.string.unknown_author), + contentDescription = stringRes(R.string.unknown_author), modifier = remember { pictureModifier .width(size) .height(size) }, + loadRobohash = loadRobohash, ) } else { RobohashFallbackAsyncImage( robot = eventId, model = image!!, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), modifier = remember { pictureModifier @@ -1437,6 +1438,7 @@ private fun WatchAndRenderBadgeImage( } }, loadProfilePicture = loadProfilePicture, + loadRobohash = loadRobohash, ) } } @@ -1456,7 +1458,7 @@ fun DrawBanner( if (!banner.isNullOrBlank()) { AsyncImage( model = banner, - contentDescription = stringResource(id = R.string.profile_image), + contentDescription = stringRes(id = R.string.profile_image), contentScale = ContentScale.FillWidth, modifier = Modifier @@ -1478,7 +1480,7 @@ fun DrawBanner( } else { Image( painter = painterResource(R.drawable.profile_banner), - contentDescription = stringResource(id = R.string.profile_banner), + contentDescription = stringRes(id = R.string.profile_banner), contentScale = ContentScale.FillWidth, modifier = Modifier @@ -1754,7 +1756,7 @@ private fun MessageButton( ) { Icon( painter = painterResource(R.drawable.ic_dm), - stringResource(R.string.send_a_direct_message), + stringRes(R.string.send_a_direct_message), modifier = Modifier.size(20.dp), tint = Color.White, ) @@ -1791,7 +1793,7 @@ private fun InnerEditButton(onClick: () -> Unit) { Icon( tint = Color.White, imageVector = Icons.Default.EditNote, - contentDescription = stringResource(R.string.edits_the_user_s_metadata), + contentDescription = stringRes(R.string.edits_the_user_s_metadata), ) } } @@ -1808,7 +1810,7 @@ fun UnfollowButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.unfollow), color = Color.White) + Text(text = stringRes(R.string.unfollow), color = Color.White) } } @@ -1827,7 +1829,7 @@ fun FollowButton( ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center) + Text(text = stringRes(text), color = Color.White, textAlign = TextAlign.Center) } } @@ -1843,7 +1845,7 @@ fun ShowUserButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringResource(R.string.unblock), color = Color.White) + Text(text = stringRes(R.string.unblock), color = Color.White) } } @@ -1861,7 +1863,7 @@ fun UserProfileDropDownMenu( val clipboardManager = LocalClipboardManager.current DropdownMenuItem( - text = { Text(stringResource(R.string.copy_user_id)) }, + text = { Text(stringRes(R.string.copy_user_id)) }, onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())) onDismiss() @@ -1872,7 +1874,7 @@ fun UserProfileDropDownMenu( HorizontalDivider(thickness = DividerThickness) if (accountViewModel.account.isHidden(user)) { DropdownMenuItem( - text = { Text(stringResource(R.string.unblock_user)) }, + text = { Text(stringRes(R.string.unblock_user)) }, onClick = { accountViewModel.show(user) onDismiss() @@ -1880,7 +1882,7 @@ fun UserProfileDropDownMenu( ) } else { DropdownMenuItem( - text = { Text(stringResource(id = R.string.block_hide_user)) }, + text = { Text(stringRes(id = R.string.block_hide_user)) }, onClick = { accountViewModel.hide(user) onDismiss() @@ -1889,35 +1891,35 @@ fun UserProfileDropDownMenu( } HorizontalDivider(thickness = DividerThickness) DropdownMenuItem( - text = { Text(stringResource(id = R.string.report_spam_scam)) }, + text = { Text(stringRes(id = R.string.report_spam_scam)) }, onClick = { accountViewModel.report(user, ReportEvent.ReportType.SPAM) onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.report_hateful_speech)) }, + text = { Text(stringRes(R.string.report_hateful_speech)) }, onClick = { accountViewModel.report(user, ReportEvent.ReportType.PROFANITY) onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(id = R.string.report_impersonation)) }, + text = { Text(stringRes(id = R.string.report_impersonation)) }, onClick = { accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION) onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(R.string.report_nudity_porn)) }, + text = { Text(stringRes(R.string.report_nudity_porn)) }, onClick = { accountViewModel.report(user, ReportEvent.ReportType.NUDITY) onDismiss() }, ) DropdownMenuItem( - text = { Text(stringResource(id = R.string.report_illegal_behaviour)) }, + text = { Text(stringRes(id = R.string.report_illegal_behaviour)) }, onClick = { accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL) onDismiss() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt index 4186d07203..03121a3609 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -61,6 +60,7 @@ import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.WarningColor import com.vitorpamplona.quartz.events.ReportEvent @@ -75,14 +75,14 @@ fun ReportNoteDialog( ) { val reportTypes = listOf( - Pair(ReportEvent.ReportType.SPAM, stringResource(R.string.report_dialog_spam)), - Pair(ReportEvent.ReportType.PROFANITY, stringResource(R.string.report_dialog_profanity)), + Pair(ReportEvent.ReportType.SPAM, stringRes(R.string.report_dialog_spam)), + Pair(ReportEvent.ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)), Pair( ReportEvent.ReportType.IMPERSONATION, - stringResource(R.string.report_dialog_impersonation), + stringRes(R.string.report_dialog_impersonation), ), - Pair(ReportEvent.ReportType.NUDITY, stringResource(R.string.report_dialog_nudity)), - Pair(ReportEvent.ReportType.ILLEGAL, stringResource(R.string.report_dialog_illegal)), + Pair(ReportEvent.ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)), + Pair(ReportEvent.ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)), ) val reasonOptions = remember { reportTypes.map { TitleExplainer(it.second) }.toImmutableList() } @@ -96,7 +96,7 @@ fun ReportNoteDialog( Scaffold( topBar = { TopAppBar( - title = { Text(text = stringResource(id = R.string.report_dialog_title)) }, + title = { Text(text = stringRes(id = R.string.report_dialog_title)) }, navigationIcon = { IconButton(onClick = onDismiss) { ArrowBackIcon() } }, colors = TopAppBarDefaults.topAppBarColors( @@ -111,14 +111,14 @@ fun ReportNoteDialog( verticalArrangement = Arrangement.SpaceAround, ) { SpacerH16() - SectionHeader(text = stringResource(id = R.string.block_only)) + SectionHeader(text = stringRes(id = R.string.block_only)) SpacerH16() Text( - text = stringResource(R.string.report_dialog_blocking_a_user), + text = stringRes(R.string.report_dialog_blocking_a_user), ) SpacerH16() ActionButton( - text = stringResource(R.string.report_dialog_block_hide_user_btn), + text = stringRes(R.string.report_dialog_block_hide_user_btn), icon = Icons.Default.Block, onClick = { note.author?.let { accountViewModel.hide(it) } @@ -130,13 +130,13 @@ fun ReportNoteDialog( HorizontalDivider(color = MaterialTheme.colorScheme.onSurface, thickness = DividerThickness) SpacerH16() - SectionHeader(text = stringResource(R.string.report_dialog_report_btn)) + SectionHeader(text = stringRes(R.string.report_dialog_report_btn)) SpacerH16() - Text(stringResource(R.string.report_dialog_reminder_public)) + Text(stringRes(R.string.report_dialog_reminder_public)) SpacerH16() TextSpinner( - label = stringResource(R.string.report_dialog_select_reason_label), - placeholder = stringResource(R.string.report_dialog_select_reason_placeholder), + label = stringRes(R.string.report_dialog_select_reason_label), + placeholder = stringRes(R.string.report_dialog_select_reason_placeholder), options = reasonOptions, onSelect = { selectedReason = it }, modifier = Modifier.fillMaxWidth(), @@ -146,15 +146,15 @@ fun ReportNoteDialog( value = additionalReason, onValueChange = { additionalReason = it }, placeholder = { - Text(text = stringResource(R.string.report_dialog_additional_reason_placeholder)) + Text(text = stringRes(R.string.report_dialog_additional_reason_placeholder)) }, - label = { Text(stringResource(R.string.report_dialog_additional_reason_label)) }, + label = { Text(stringRes(R.string.report_dialog_additional_reason_label)) }, modifier = Modifier.fillMaxWidth(), ) SpacerH16() ActionButton( - text = stringResource(R.string.report_dialog_post_report_btn), + text = stringRes(R.string.report_dialog_post_report_btn), icon = Icons.Default.Report, enabled = selectedReason in 0..reportTypes.lastIndex, onClick = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 03a81f620f..04587f1089 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -58,7 +58,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp @@ -71,6 +70,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User @@ -86,6 +86,7 @@ import com.vitorpamplona.amethyst.ui.note.SearchIcon import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForSearchAndDisplayIfNotFound +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -168,7 +169,9 @@ fun WatchAccountForSearchScreen(accountViewModel: AccountViewModel) { } @Stable -class SearchBarViewModel(val account: Account) : ViewModel() { +class SearchBarViewModel( + val account: Account, +) : ViewModel() { var searchValue by mutableStateOf("") private var _searchResultsUsers = MutableStateFlow>(emptyList()) @@ -198,12 +201,14 @@ class SearchBarViewModel(val account: Account) : ViewModel() { _hashtagResults.emit(findHashtags(searchValue)) _searchResultsUsers.emit( - LocalCache.findUsersStartingWith(searchValue) + LocalCache + .findUsersStartingWith(searchValue) .sortedWith(compareBy({ account.isFollowing(it) }, { it.toBestDisplayName() })) .reversed(), ) _searchResultsNotes.emit( - LocalCache.findNotesStartingWith(searchValue) + LocalCache + .findNotesStartingWith(searchValue) .sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .reversed(), ) @@ -236,10 +241,10 @@ class SearchBarViewModel(val account: Account) : ViewModel() { fun isSearchingFun() = searchValue.isNotBlank() - class Factory(val account: Account) : ViewModelProvider.Factory { - override fun create(modelClass: Class): SearchBarViewModel { - return SearchBarViewModel(account) as SearchBarViewModel - } + class Factory( + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): SearchBarViewModel = SearchBarViewModel(account) as SearchBarViewModel } } @@ -325,7 +330,7 @@ private fun SearchTextField( KeyboardOptions.Default.copy( capitalization = KeyboardCapitalization.Sentences, ), - leadingIcon = { SearchIcon(modifier = Size20Modifier, Color.Unspecified) }, + leadingIcon = { SearchIcon(modifier = Size20Modifier, MaterialTheme.colorScheme.placeholderText) }, modifier = Modifier .weight(1f, true) @@ -333,7 +338,7 @@ private fun SearchTextField( .focusRequester(focusRequester), placeholder = { Text( - text = stringResource(R.string.npub_hex_username), + text = stringRes(R.string.npub_hex_username), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -377,11 +382,6 @@ private fun DisplaySearchResults( val hasNewMessages = remember { mutableStateOf(false) } - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - LazyColumn( modifier = Modifier.fillMaxHeight(), contentPadding = FeedPadding, @@ -426,7 +426,8 @@ private fun DisplaySearchResults( channelLastTime = null, channelLastContent = item.summary(), hasNewMessages = hasNewMessages, - loadProfilePicture = automaticallyShowProfilePicture, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onClick = { nav("Channel/${item.idHex}") }, ) @@ -500,7 +501,7 @@ fun UserLine( Column( modifier = Modifier.padding(start = 10.dp).weight(1f), ) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) } + Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) } AboutDisplay(baseUser) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt index aeb9f87f83..d550af6eb9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -53,8 +52,10 @@ import com.vitorpamplona.amethyst.model.parseBooleanType import com.vitorpamplona.amethyst.model.parseConnectivityType import com.vitorpamplona.amethyst.model.parseFeatureSetType import com.vitorpamplona.amethyst.model.parseThemeType +import com.vitorpamplona.amethyst.ui.checkLanguage import com.vitorpamplona.amethyst.ui.components.PushNotificationSettingsRow import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20dp @@ -125,28 +126,29 @@ fun getLanguageIndex( fun SettingsScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) { val selectedItens = persistentListOf( - TitleExplainer(stringResource(ConnectivityType.ALWAYS.resourceId)), - TitleExplainer(stringResource(ConnectivityType.WIFI_ONLY.resourceId)), - TitleExplainer(stringResource(ConnectivityType.NEVER.resourceId)), + TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)), + TitleExplainer(stringRes(ConnectivityType.WIFI_ONLY.resourceId)), + TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)), ) val themeItens = persistentListOf( - TitleExplainer(stringResource(ThemeType.SYSTEM.resourceId)), - TitleExplainer(stringResource(ThemeType.LIGHT.resourceId)), - TitleExplainer(stringResource(ThemeType.DARK.resourceId)), + TitleExplainer(stringRes(ThemeType.SYSTEM.resourceId)), + TitleExplainer(stringRes(ThemeType.LIGHT.resourceId)), + TitleExplainer(stringRes(ThemeType.DARK.resourceId)), ) val booleanItems = persistentListOf( - TitleExplainer(stringResource(ConnectivityType.ALWAYS.resourceId)), - TitleExplainer(stringResource(ConnectivityType.NEVER.resourceId)), + TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)), + TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)), ) val featureItems = persistentListOf( - TitleExplainer(stringResource(FeatureSetType.COMPLETE.resourceId)), - TitleExplainer(stringResource(FeatureSetType.SIMPLIFIED.resourceId)), + TitleExplainer(stringRes(FeatureSetType.COMPLETE.resourceId)), + TitleExplainer(stringRes(FeatureSetType.SIMPLIFIED.resourceId)), + TitleExplainer(stringRes(FeatureSetType.PERFORMANCE.resourceId)), ) val showImagesIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyShowImages.screenCode @@ -168,7 +170,8 @@ fun SettingsScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) { sharedPreferencesViewModel.sharedPrefs.featureSet.screenCode Column( - Modifier.fillMaxSize() + Modifier + .fillMaxSize() .padding(top = Size10dp, start = Size20dp, end = Size20dp) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, @@ -180,6 +183,9 @@ fun SettingsScreen(sharedPreferencesViewModel: SharedPreferencesViewModel) { languageIndex, ) { sharedPreferencesViewModel.updateLanguage(languageEntries[languageList[it].title]) + languageEntries[languageList[it].title]?.let { + checkLanguage(it) + } } Spacer(modifier = HalfVertSpacer) @@ -280,12 +286,12 @@ fun SettingsRow( verticalArrangement = Arrangement.spacedBy(3.dp), ) { Text( - text = stringResource(name), + text = stringRes(name), maxLines = 1, overflow = TextOverflow.Ellipsis, ) Text( - text = stringResource(description), + text = stringRes(description), style = MaterialTheme.typography.bodySmall, color = Color.Gray, maxLines = 2, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 0c230a69e7..db66f5e303 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -40,7 +39,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -53,7 +51,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle @@ -63,7 +60,9 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.NostrVideoDataSource +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.NewPostView +import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.BoostReaction @@ -73,7 +72,6 @@ import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.RenderAllRelayList import com.vitorpamplona.amethyst.ui.note.ReplyReaction -import com.vitorpamplona.amethyst.ui.note.ViewCountReaction import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay @@ -88,16 +86,17 @@ import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AuthorInfoVideoFeed import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.Size22Modifier import com.vitorpamplona.amethyst.ui.theme.Size35Modifier import com.vitorpamplona.amethyst.ui.theme.Size35dp -import com.vitorpamplona.amethyst.ui.theme.Size39Modifier import com.vitorpamplona.amethyst.ui.theme.Size40Modifier import com.vitorpamplona.amethyst.ui.theme.Size40dp import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.VideoReactionColumnPadding -import com.vitorpamplona.amethyst.ui.theme.onBackgroundColorFilter import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent @@ -176,10 +175,11 @@ fun RenderPage( ) { val feedState by videoFeedView.feedContent.collectAsStateWithLifecycle() - Crossfade( + CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), label = "RenderPage", + accountViewModel = accountViewModel, ) { state -> when (state) { is FeedState.Empty -> { @@ -315,10 +315,10 @@ private fun RenderAuthorInformation( verticalArrangement = Arrangement.Center, ) { Row(verticalAlignment = Alignment.CenterVertically) { - NoteUsernameDisplay(note, remember { Modifier.weight(1f) }) + NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel) VideoUserOptionAction(note, accountViewModel, nav) } - if (accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED) { + if (accountViewModel.settings.featureSet == FeatureSetType.COMPLETE) { Row(verticalAlignment = Alignment.CenterVertically) { ObserveDisplayNip05Status( note.author!!, @@ -345,26 +345,27 @@ private fun VideoUserOptionAction( nav: (String) -> Unit, ) { val popupExpanded = remember { mutableStateOf(false) } - val enablePopup = remember { { popupExpanded.value = true } } - IconButton( - modifier = remember { Modifier.size(22.dp) }, - onClick = enablePopup, + ClickableBox( + modifier = Size22Modifier, + onClick = { popupExpanded.value = true }, ) { Icon( imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(id = R.string.more_options), - modifier = remember { Modifier.size(20.dp) }, + contentDescription = stringRes(id = R.string.more_options), + modifier = Size20Modifier, tint = MaterialTheme.colorScheme.placeholderText, ) - NoteDropDownMenu( - note, - popupExpanded, - null, - accountViewModel, - nav, - ) + if (popupExpanded.value) { + NoteDropDownMenu( + note, + { popupExpanded.value = false }, + null, + accountViewModel, + nav, + ) + } } } @@ -443,11 +444,5 @@ fun ReactionsColumn( animationSize = Size35dp, nav = nav, ) - ViewCountReaction( - note = baseNote, - grayTint = MaterialTheme.colorScheme.onBackground, - barChartModifier = Size39Modifier, - viewCountColorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter, - ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index de25e75d49..ad9e64492d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -75,7 +75,6 @@ import androidx.compose.ui.platform.LocalAutofillTree import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.input.ImeAction @@ -98,6 +97,7 @@ import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size35dp @@ -164,12 +164,12 @@ fun LoginPage( onResult = { result -> if (result.resultCode != Activity.RESULT_OK) { scope.launch(Dispatchers.Main) { - Toast.makeText( - Amethyst.instance, - "Sign request rejected", - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + Amethyst.instance, + "Sign request rejected", + Toast.LENGTH_SHORT, + ).show() } } else { result.data?.let { externalSignerLauncher.newResult(it) } @@ -189,12 +189,12 @@ fun LoginPage( if (e is CancellationException) throw e Log.e("Signer", "Error opening Signer app", e) scope.launch(Dispatchers.Main) { - Toast.makeText( - Amethyst.instance, - R.string.error_opening_external_signer, - Toast.LENGTH_SHORT, - ) - .show() + Toast + .makeText( + Amethyst.instance, + R.string.error_opening_external_signer, + Toast.LENGTH_SHORT, + ).show() } } }, @@ -215,11 +215,11 @@ fun LoginPage( val packageName = if (split.size > 1) split[1] else "" key.value = TextFieldValue(pubkey) if (!acceptedTerms.value) { - termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required) + termsAcceptanceIsRequired = stringRes(context, R.string.acceptance_of_terms_is_required) } if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) + errorMessage = stringRes(context, R.string.key_is_required) } if (acceptedTerms.value && key.value.text.isNotBlank()) { @@ -230,7 +230,7 @@ fun LoginPage( true, packageName, ) { - errorMessage = context.getString(R.string.invalid_key) + errorMessage = stringRes(context, R.string.invalid_key) } } } @@ -248,7 +248,7 @@ fun LoginPage( ) { Image( imageVector = CustomHashTagIcons.Amethyst, - contentDescription = stringResource(R.string.app_logo), + contentDescription = stringRes(R.string.app_logo), modifier = Modifier.size(150.dp), contentScale = ContentScale.Inside, ) @@ -279,8 +279,7 @@ fun LoginPage( Modifier .onGloballyPositioned { coordinates -> autofillNodeKey.boundingBox = coordinates.boundsInWindow() - } - .onFocusChanged { focusState -> + }.onFocusChanged { focusState -> autofill?.run { if (focusState.isFocused) { requestAutofillForNode(autofillNodeKey) @@ -304,7 +303,7 @@ fun LoginPage( ), placeholder = { Text( - text = stringResource(R.string.nsec_npub_hex_private_key), + text = stringRes(R.string.nsec_npub_hex_private_key), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -316,9 +315,9 @@ fun LoginPage( if (showCharsKey) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, contentDescription = if (showCharsKey) { - stringResource(R.string.show_password) + stringRes(R.string.show_password) } else { - stringResource( + stringRes( R.string.hide_password, ) }, @@ -339,7 +338,7 @@ fun LoginPage( Icon( painter = painterResource(R.drawable.ic_qrcode), contentDescription = - stringResource( + stringRes( R.string.login_with_qr_code, ), modifier = Modifier.size(24.dp), @@ -353,15 +352,15 @@ fun LoginPage( KeyboardActions( onGo = { if (!acceptedTerms.value) { - termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required) + termsAcceptanceIsRequired = stringRes(context, R.string.acceptance_of_terms_is_required) } if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) + errorMessage = stringRes(context, R.string.key_is_required) } if (needsPassword.value && password.value.text.isBlank()) { - errorMessage = context.getString(R.string.password_is_required) + errorMessage = stringRes(context, R.string.password_is_required) } if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) { @@ -370,9 +369,9 @@ fun LoginPage( processingLogin = false errorMessage = if (it != null) { - context.getString(R.string.invalid_key_with_message, it) + stringRes(context, R.string.invalid_key_with_message, it) } else { - context.getString(R.string.invalid_key) + stringRes(context, R.string.invalid_key) } } } @@ -395,8 +394,7 @@ fun LoginPage( Modifier .onGloballyPositioned { coordinates -> autofillNodePassword.boundingBox = coordinates.boundsInWindow() - } - .onFocusChanged { focusState -> + }.onFocusChanged { focusState -> autofill?.run { if (focusState.isFocused) { requestAutofillForNode(autofillNodePassword) @@ -420,7 +418,7 @@ fun LoginPage( ), placeholder = { Text( - text = stringResource(R.string.ncryptsec_password), + text = stringRes(R.string.ncryptsec_password), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -432,9 +430,9 @@ fun LoginPage( if (showCharsPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, contentDescription = if (showCharsPassword) { - stringResource(R.string.show_password) + stringRes(R.string.show_password) } else { - stringResource( + stringRes( R.string.hide_password, ) }, @@ -448,15 +446,15 @@ fun LoginPage( KeyboardActions( onGo = { if (!acceptedTerms.value) { - termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required) + termsAcceptanceIsRequired = stringRes(context, R.string.acceptance_of_terms_is_required) } if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) + errorMessage = stringRes(context, R.string.key_is_required) } if (needsPassword.value && password.value.text.isBlank()) { - errorMessage = context.getString(R.string.password_is_required) + errorMessage = stringRes(context, R.string.password_is_required) } if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) { @@ -465,9 +463,9 @@ fun LoginPage( processingLogin = false errorMessage = if (it != null) { - context.getString(R.string.invalid_key_with_message, it) + stringRes(context, R.string.invalid_key_with_message, it) } else { - context.getString(R.string.invalid_key) + stringRes(context, R.string.invalid_key) } } } @@ -488,7 +486,7 @@ fun LoginPage( }, ) - Text(stringResource(R.string.connect_via_tor)) + Text(stringRes(R.string.connect_via_tor)) } if (connectOrbotDialogOpen) { @@ -500,12 +498,12 @@ fun LoginPage( }, onError = { scope.launch { - Toast.makeText( - context, - it, - Toast.LENGTH_LONG, - ) - .show() + Toast + .makeText( + context, + it, + Toast.LENGTH_LONG, + ).show() } }, proxyPort, @@ -527,11 +525,11 @@ fun LoginPage( val annotatedTermsString = buildAnnotatedString { - withStyle(regularText) { append(stringResource(R.string.i_accept_the)) } + withStyle(regularText) { append(stringRes(R.string.i_accept_the)) } withStyle(clickableTextStyle) { pushStringAnnotation("openTerms", "") - append(stringResource(R.string.terms_of_use)) + append(stringRes(R.string.terms_of_use)) pop() } } @@ -539,8 +537,7 @@ fun LoginPage( ClickableText( text = annotatedTermsString, ) { spanOffset -> - annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { - span -> + annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span -> if (span.tag == "openTerms") { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") @@ -567,15 +564,15 @@ fun LoginPage( onClick = { if (!acceptedTerms.value) { termsAcceptanceIsRequired = - context.getString(R.string.acceptance_of_terms_is_required) + stringRes(context, R.string.acceptance_of_terms_is_required) } if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) + errorMessage = stringRes(context, R.string.key_is_required) } if (needsPassword.value && password.value.text.isBlank()) { - errorMessage = context.getString(R.string.password_is_required) + errorMessage = stringRes(context, R.string.password_is_required) } if (acceptedTerms.value && key.value.text.isNotBlank() && !(needsPassword.value && password.value.text.isBlank())) { @@ -584,9 +581,9 @@ fun LoginPage( processingLogin = false errorMessage = if (it != null) { - context.getString(R.string.invalid_key_with_message, it) + stringRes(context, R.string.invalid_key_with_message, it) } else { - context.getString(R.string.invalid_key) + stringRes(context, R.string.invalid_key) } } } @@ -599,7 +596,7 @@ fun LoginPage( LoadingAnimation() Spacer(modifier = DoubleHorzSpacer) } - Text(stringResource(R.string.login)) + Text(stringRes(R.string.login)) } } } @@ -611,7 +608,7 @@ fun LoginPage( onClick = { if (!acceptedTerms.value) { termsAcceptanceIsRequired = - context.getString(R.string.acceptance_of_terms_is_required) + stringRes(context, R.string.acceptance_of_terms_is_required) return@Button } @@ -622,7 +619,7 @@ fun LoginPage( modifier = Modifier.height(50.dp), ) { Text( - text = stringResource(R.string.login_with_external_signer), + text = stringRes(R.string.login_with_external_signer), modifier = Modifier.padding(horizontal = 40.dp), ) } @@ -631,7 +628,7 @@ fun LoginPage( Spacer(modifier = Modifier.height(Size40dp)) - Text(text = stringResource(R.string.don_t_have_an_account)) + Text(text = stringRes(R.string.don_t_have_an_account)) Spacer(modifier = Modifier.height(Size20dp)) @@ -642,7 +639,7 @@ fun LoginPage( modifier = Modifier.height(50.dp), ) { Text( - text = stringResource(R.string.sign_up), + text = stringRes(R.string.sign_up), modifier = Modifier.padding(horizontal = Size40dp), ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/SignUpScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/SignUpScreen.kt index d157892444..718e49f1fb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/SignUpScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/SignUpScreen.kt @@ -54,7 +54,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.input.ImeAction @@ -70,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.service.PackageUtils import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size35dp @@ -118,18 +118,18 @@ fun SignUpPage( ) { Image( imageVector = CustomHashTagIcons.Amethyst, - contentDescription = stringResource(R.string.app_logo), + contentDescription = stringRes(R.string.app_logo), modifier = Modifier.size(150.dp), contentScale = ContentScale.Inside, ) Spacer(modifier = Modifier.height(Size40dp)) - Text(text = stringResource(R.string.welcome), style = MaterialTheme.typography.titleLarge) + Text(text = stringRes(R.string.welcome), style = MaterialTheme.typography.titleLarge) Spacer(modifier = Modifier.height(Size20dp)) - Text(text = stringResource(R.string.how_should_we_call_you), style = MaterialTheme.typography.titleMedium) + Text(text = stringRes(R.string.how_should_we_call_you), style = MaterialTheme.typography.titleMedium) Spacer(modifier = Modifier.height(Size20dp)) @@ -144,7 +144,7 @@ fun SignUpPage( ), placeholder = { Text( - text = stringResource(R.string.my_awesome_name), + text = stringRes(R.string.my_awesome_name), color = MaterialTheme.colorScheme.placeholderText, ) }, @@ -153,17 +153,15 @@ fun SignUpPage( onGo = { if (!acceptedTerms.value) { termsAcceptanceIsRequired = - context.getString(R.string.acceptance_of_terms_is_required) + stringRes(context, R.string.acceptance_of_terms_is_required) } if (displayName.value.text.isBlank()) { - errorMessage = context.getString(R.string.name_is_required) + errorMessage = stringRes(context, R.string.name_is_required) } if (acceptedTerms.value && displayName.value.text.isNotBlank()) { - accountStateViewModel.login(displayName.value.text, useProxy.value, proxyPort.value.toInt()) { - errorMessage = context.getString(R.string.invalid_key) - } + accountStateViewModel.newKey(useProxy.value, proxyPort.value.toInt(), displayName.value.text) } }, ), @@ -190,11 +188,11 @@ fun SignUpPage( val annotatedTermsString = buildAnnotatedString { - withStyle(regularText) { append(stringResource(R.string.i_accept_the)) } + withStyle(regularText) { append(stringRes(R.string.i_accept_the)) } withStyle(clickableTextStyle) { pushStringAnnotation("openTerms", "") - append(stringResource(R.string.terms_of_use)) + append(stringRes(R.string.terms_of_use)) pop() } } @@ -202,8 +200,7 @@ fun SignUpPage( ClickableText( text = annotatedTermsString, ) { spanOffset -> - annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { - span -> + annotatedTermsString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span -> if (span.tag == "openTerms") { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") @@ -232,7 +229,7 @@ fun SignUpPage( }, ) - Text(stringResource(R.string.connect_via_tor)) + Text(stringRes(R.string.connect_via_tor)) } if (connectOrbotDialogOpen) { @@ -244,12 +241,12 @@ fun SignUpPage( }, onError = { scope.launch { - Toast.makeText( - context, - it, - Toast.LENGTH_LONG, - ) - .show() + Toast + .makeText( + context, + it, + Toast.LENGTH_LONG, + ).show() } }, proxyPort, @@ -264,11 +261,11 @@ fun SignUpPage( enabled = acceptedTerms.value, onClick = { if (!acceptedTerms.value) { - termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required) + termsAcceptanceIsRequired = stringRes(context, R.string.acceptance_of_terms_is_required) } if (displayName.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) + errorMessage = stringRes(context, R.string.key_is_required) } if (acceptedTerms.value && displayName.value.text.isNotBlank()) { @@ -279,7 +276,7 @@ fun SignUpPage( modifier = Modifier.height(50.dp), ) { Text( - text = stringResource(R.string.create_account), + text = stringRes(R.string.create_account), modifier = Modifier.padding(horizontal = Size40dp), ) } @@ -287,7 +284,7 @@ fun SignUpPage( Spacer(modifier = Modifier.height(Size40dp)) - Text(text = stringResource(R.string.already_have_an_account)) + Text(text = stringRes(R.string.already_have_an_account)) Spacer(modifier = Modifier.height(Size20dp)) @@ -298,7 +295,7 @@ fun SignUpPage( modifier = Modifier.height(50.dp), ) { Text( - text = stringResource(R.string.login), + text = stringRes(R.string.login), modifier = Modifier.padding(horizontal = Size40dp), ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 4a5ddcc9f0..dd4d233a63 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -97,6 +97,7 @@ val Size35dp = 35.dp val Size40dp = 40.dp val Size55dp = 55.dp val Size75dp = 75.dp +val Size100dp = 100.dp val Size110dp = 110.dp val Size165dp = 165.dp @@ -122,7 +123,7 @@ val VertPadding = Modifier.padding(vertical = 10.dp) val MaxWidthWithHorzPadding = Modifier.fillMaxWidth().padding(horizontal = 10.dp) -val Size6Modifier = Modifier.size(6.dp) +val Size5Modifier = Modifier.size(5.dp) val Size10Modifier = Modifier.size(10.dp) val Size15Modifier = Modifier.size(15.dp) val Size16Modifier = Modifier.size(16.dp) @@ -156,7 +157,7 @@ val VideoReactionColumnPadding = Modifier.padding(bottom = 75.dp) val DividerThickness = 0.25.dp val ReactionRowHeight = Modifier.padding(vertical = 7.dp).height(24.dp) -val ReactionRowHeightWithPadding = Modifier.padding(vertical = 7.dp).height(24.dp).padding(start = 10.dp) +val ReactionRowHeightWithPadding = Modifier.padding(vertical = 7.dp).height(24.dp).padding(horizontal = 10.dp) val ReactionRowHeightChat = Modifier.height(25.dp) val UserNameRowHeight = Modifier.fillMaxWidth() val UserNameMaxRowHeight = Modifier.fillMaxWidth() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index d96062a030..440a16c1e2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -191,6 +191,20 @@ val LightChannelNotePictureModifier = .clip(shape = CircleShape) .border(2.dp, LightColorPalette.background, CircleShape) +val LightProfilePictureBorder = + Modifier.border( + 3.dp, + DarkColorPalette.background, + CircleShape, + ) + +val DarkProfilePictureBorder = + Modifier.border( + 3.dp, + LightColorPalette.background, + CircleShape, + ) + val LightRelayIconModifier = Modifier .size(Size13dp) @@ -379,6 +393,9 @@ val ColorScheme.innerPostModifier: Modifier val ColorScheme.channelNotePictureModifier: Modifier get() = if (isLight) LightChannelNotePictureModifier else DarkChannelNotePictureModifier +val ColorScheme.userProfileBorderModifier: Modifier + get() = if (isLight) LightProfilePictureBorder else DarkProfilePictureBorder + val ColorScheme.relayIconModifier: Modifier get() = if (isLight) LightRelayIconModifier else DarkRelayIconModifier @@ -400,8 +417,7 @@ val ColorScheme.chartStyle: ChartStyle defaultColors.entity1Color, defaultColors.entity2Color, defaultColors.entity3Color, - ) - .map(::Color), + ).map(::Color), elevationOverlayColor = Color(defaultColors.elevationOverlayColor), ) } diff --git a/app/src/main/res/drawable-hdpi/ic_comment.png b/app/src/main/res/drawable-hdpi/ic_comment.png deleted file mode 100644 index 0ae259d47e..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_comment.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_like.png b/app/src/main/res/drawable-hdpi/ic_like.png deleted file mode 100644 index 1ff6f5772c..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_like.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_liked.png b/app/src/main/res/drawable-hdpi/ic_liked.png deleted file mode 100644 index b5247d64c5..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_liked.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_retweet.png b/app/src/main/res/drawable-hdpi/ic_retweet.png deleted file mode 100644 index 89cb87ed64..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_retweet.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_retweeted.png b/app/src/main/res/drawable-hdpi/ic_retweeted.png deleted file mode 100644 index c7e2b0abff..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_retweeted.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_search.png b/app/src/main/res/drawable-hdpi/ic_search.png deleted file mode 100644 index 353391cafa..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_share.png b/app/src/main/res/drawable-hdpi/ic_share.png deleted file mode 100644 index cd4b166ea2..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_comment.png b/app/src/main/res/drawable-mdpi/ic_comment.png deleted file mode 100644 index 893bb49f96..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_comment.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_like.png b/app/src/main/res/drawable-mdpi/ic_like.png deleted file mode 100644 index babf38b7b0..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_like.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_liked.png b/app/src/main/res/drawable-mdpi/ic_liked.png deleted file mode 100644 index 194ff9f950..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_liked.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweet.png b/app/src/main/res/drawable-mdpi/ic_retweet.png deleted file mode 100644 index 0e36ae06ca..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_retweet.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweeted.png b/app/src/main/res/drawable-mdpi/ic_retweeted.png deleted file mode 100644 index 39545e5000..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_retweeted.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_search.png b/app/src/main/res/drawable-mdpi/ic_search.png deleted file mode 100644 index 0267232735..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_share.png b/app/src/main/res/drawable-mdpi/ic_share.png deleted file mode 100644 index f08ff4f534..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_comment.png b/app/src/main/res/drawable-xhdpi/ic_comment.png deleted file mode 100644 index 6687355e0c..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_comment.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_like.png b/app/src/main/res/drawable-xhdpi/ic_like.png deleted file mode 100644 index 89dc58fdc6..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_like.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_liked.png b/app/src/main/res/drawable-xhdpi/ic_liked.png deleted file mode 100644 index d6f1bb5c41..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_liked.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweet.png b/app/src/main/res/drawable-xhdpi/ic_retweet.png deleted file mode 100644 index dbcd7480ec..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_retweet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweeted.png b/app/src/main/res/drawable-xhdpi/ic_retweeted.png deleted file mode 100644 index 2b1222f982..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_retweeted.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search.png b/app/src/main/res/drawable-xhdpi/ic_search.png deleted file mode 100644 index 1da2ab0b47..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_share.png b/app/src/main/res/drawable-xhdpi/ic_share.png deleted file mode 100644 index 97c9b2e959..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_comment.png b/app/src/main/res/drawable-xxhdpi/ic_comment.png deleted file mode 100644 index b73c0d8806..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_comment.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_like.png b/app/src/main/res/drawable-xxhdpi/ic_like.png deleted file mode 100644 index 0ad7f67cda..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_like.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_liked.png b/app/src/main/res/drawable-xxhdpi/ic_liked.png deleted file mode 100644 index ab0b6d881c..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_liked.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweet.png b/app/src/main/res/drawable-xxhdpi/ic_retweet.png deleted file mode 100644 index ec69417278..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_retweet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweeted.png b/app/src/main/res/drawable-xxhdpi/ic_retweeted.png deleted file mode 100644 index 5a3025a57b..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_retweeted.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search.png b/app/src/main/res/drawable-xxhdpi/ic_search.png deleted file mode 100644 index 1b10188797..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share.png b/app/src/main/res/drawable-xxhdpi/ic_share.png deleted file mode 100644 index 63728b0ed4..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_comment.png b/app/src/main/res/drawable-xxxhdpi/ic_comment.png deleted file mode 100644 index 924df61350..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_comment.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_like.png b/app/src/main/res/drawable-xxxhdpi/ic_like.png deleted file mode 100644 index 2883a6428f..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_like.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_liked.png b/app/src/main/res/drawable-xxxhdpi/ic_liked.png deleted file mode 100644 index 526a708089..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_liked.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweet.png b/app/src/main/res/drawable-xxxhdpi/ic_retweet.png deleted file mode 100644 index afe333eda7..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_retweet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweeted.png b/app/src/main/res/drawable-xxxhdpi/ic_retweeted.png deleted file mode 100644 index 4306931b2f..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_retweeted.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search.png b/app/src/main/res/drawable-xxxhdpi/ic_search.png deleted file mode 100644 index 9c50ac7b2d..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_share.png b/app/src/main/res/drawable-xxxhdpi/ic_share.png deleted file mode 100644 index 1b556d7dd0..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_share.png and /dev/null differ diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0f16d07bfe..ffc1055b66 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -449,6 +449,7 @@ Nikdy Hotovo Zjednodušené + Výkonost Systém Světlý Tmavý diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 90a71c1625..7a28822936 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -454,6 +454,7 @@ anz der Bedingungen ist erforderlich Nie Fertig Vereinfacht + Leistung System Hell Dunkel diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index de844f632b..e5b2b33275 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -113,6 +113,7 @@ Dirección web Dirección LN Dirección LN (antigua) + Guardar en la galería Imagen guardada en la galería Error al guardar la imagen Cargar imagen @@ -407,6 +408,7 @@ Idiomas Etiquetas Política de publicación + Errores y avisos de este relé Longitud del mensaje Suscripciones Filtros @@ -449,6 +451,7 @@ Nunca Completo Simplificado + Rendimiento Sistema Claro Oscuro @@ -500,6 +503,7 @@ Cuándo cargar imágenes Copiar al portapapeles Copiar npub al portapapeles + Compartir o guardar Copiar URL al portapapeles Copiar ID de la nota al portapapeles Creado @@ -512,6 +516,10 @@ Mensaje para el usuario OK No se pudo llegar a %1$s: %2$s + Error al ensamblar la url NIP-11 para %1$s: %2$s + No se pudo contactar %1$s: %2$s + Error al analizar la respuesta de %1$s: %2$s + El relé rechazó la solicitud %1$s: %2$s No se pudo llegar a %1$s: %2$s Error al analizar el resultado de %1$s: %2$s %1$s falló con el código %2$s diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index 5b632d029c..31b18062b0 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -113,6 +113,7 @@ URL del sitio web Dirección de Lightning URL de Lightning (obsoleta) + Guardar en la galería Imagen guardada en la galería Error al guardar la imagen Subir imagen @@ -407,6 +408,7 @@ Idiomas Etiquetas Política de publicación + Errores y avisos de este relé Longitud del mensaje Suscripciones Filtros @@ -449,6 +451,7 @@ Nunca Completo Simplificado + Rendimiento Sistema Claro Oscuro @@ -500,6 +503,7 @@ Cuándo cargar imágenes Copiar al portapapeles Copiar npub al portapapeles + Compartir o guardar Copiar URL al portapapeles Copiar ID de la nota al portapapeles Creado @@ -512,6 +516,10 @@ Mensaje para el usuario OK No se pudo llegar a %1$s: %2$s + Error al ensamblar la url NIP-11 para %1$s: %2$s + No se pudo contactar %1$s: %2$s + Error al analizar la respuesta de %1$s: %2$s + El relé rechazó la solicitud %1$s: %2$s No se pudo llegar a %1$s: %2$s Error al analizar el resultado de %1$s: %2$s %1$s falló con el código %2$s diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index c51b768794..a52f9e805f 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -113,6 +113,7 @@ URL del sitio web Dirección de Lightning URL de Lightning (obsoleta) + Guardar en la galería Imagen guardada en la galería Error al guardar la imagen Subir imagen @@ -407,6 +408,7 @@ Idiomas Etiquetas Política de publicación + Errores y avisos de este relé Longitud del mensaje Suscripciones Filtros @@ -449,6 +451,7 @@ Nunca Completo Simplificado + Rendimiento Sistema Claro Oscuro @@ -500,6 +503,7 @@ Cuándo cargar imágenes Copiar al portapapeles Copiar npub al portapapeles + Compartir o guardar Copiar URL al portapapeles Copiar ID de la nota al portapapeles Creado @@ -512,6 +516,10 @@ Mensaje para el usuario OK No se pudo llegar a %1$s: %2$s + Error al ensamblar la url NIP-11 para %1$s: %2$s + No se pudo contactar %1$s: %2$s + Error al analizar la respuesta de %1$s: %2$s + El relé rechazó la solicitud %1$s: %2$s No se pudo llegar a %1$s: %2$s Error al analizar el resultado de %1$s: %2$s %1$s falló con el código %2$s diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8ef284a6b6..c63292a5e3 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -8,7 +8,7 @@ Montrer quand même Ce message a été masqué car il mentionne vos utilisateurs ou mots cachés Post signalé inapproprié par - Post non trouvé + L\'événement est en cours de chargement ou ne peut pas être trouvé dans votre liste de relais Image du canal référence de l\'évènement non trouvée Impossible de déchiffrer le message @@ -451,6 +451,7 @@ Jamais Complet Simplifié + Performance Système Clair Sombre diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 10cf3587b2..9284d42163 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -8,7 +8,7 @@ फिर भी दिखाएँ इस प्रकाशित पत्र को छिपाया गया क्योंकि इसमें आपके आच्छाद्य उपयोगकर्ता अथवा शब्द उल्लेखित हैं प्रकाशित पत्र को मौन किया गया अथवा सूचित किया गया इनके द्वारा - घटना उपलब्ध किया जा रहा है अथवा आपके पुनःप्रसारक सूची में से प्राप्त नहीं + घटना उपलब्ध की जा रही है अथवा आपके पुनःप्रसारक सूची में से प्राप्त नहीं प्रणाली चित्र उद्धृत घटना अप्राप्त सन्देश का अरहस्यीकरण असफल @@ -419,13 +419,13 @@ न्यूनतम श्रमप्रमाण प्रमाणीकरण भुगतान - काशयु लिपिखण्ड + काशयु राशिखण्ड चुकाएँ ज्साप धनकोष को भेजें काशयु धनकोष में खोलें - लिपिखण्ड की अनुकृति करें + राशिखण्ड की अनुकृति करें कोई लैटनिंग पता स्थापित नहीं - लिपिखण्ड की अनुकृति की गयी टाँकाफलक में + राशिखण्ड की अनुकृति की गयी टाँकाफलक में तत्क्षणप्रसार जालरहित समाप्त @@ -451,6 +451,7 @@ कभी नहीं सम्पूर्ण सरलीकृत + प्रदर्शन यन्त्रव्यवस्था प्रकाशवान अन्धकारमय @@ -515,6 +516,10 @@ उपयोगकर्ता को संदेश भेजें ठीक है %1$s तक जा नहीं पाए : %2$s + %1$s के लिए निप॰-११ जालपता बनाने में असफल : %2$s + %1$s तक जाने में असफल : %2$s + %1$s से प्रतिवचन परखने में असफल : %2$s + पुनःप्रसारक ने %1$s अनुरोध को इनकार किया : %2$s %1$s तक जा नहीं पाए : %2$s %1$s से परिणाम परख नहीं पाए : %2$s %1$s असफल हुआ इस संकेतक के साथ : %2$s @@ -555,7 +560,7 @@ निप॰-४७ संयोजन सूत्र परखने में अपक्रम। जाँच करें यदि यह सम्यक है अपने धनकोष सेवा प्रदाता के साथ : %1$s। काशयु नहीं चुका पाए टकसाल ने यह अपक्रम संदेश उपलब्ध किया : %1$s - काशयु लिपिखण्ड का व्यय हो चुका। + काशयु राशिखण्ड का व्यय हो चुका। काशयु प्राप्त %1$s साट्स भेजे गये आपके धनकोष में। (शुल्क : %2$s साट्स) कोई अनुकूल काशयु धनकोष उपलब्ध नहीं यन्त्र में diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 78bb18d817..7672545ad6 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -113,6 +113,7 @@ Oldalam URL-je LN Cím LN URL (elavult) + Mentés a galériába A kép a galériába mentve Nem sikerült menteni a képet Kép feltöltése @@ -449,6 +450,7 @@ Soha Teljes Egyszerűsített + Teljesítmény-beállítások Rendszer Világos Sötét @@ -500,6 +502,7 @@ Mikor kell a képeket betölteni Másolás a vágólapra Npub másolása a vágólapra + Megosztás vagy Mentés Az URL vágólapra másolása A bejegyzésazonosító vágólapra másolása Létrehozva @@ -512,6 +515,10 @@ Üzenet a Felhasználónak Ok Nem sikerült elérni %1$s: %2$s + Nem sikerült összeállítani a NIP-11 URL-t a következőhöz: %1$s: %2$s + Nem sikerült elérni %1$s: %2$s + Nem sikerült a válasz elemzése a %1$s: %2$s + A csomópont elutasította a(z) %1$s kérést: %2$s Nem sikerült elérni %1$s: %2$s Nem sikerült az eredmény elemzése a %1$s: %2$s %1$s hiba a következő kóddal %2$s diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 080111c2c4..51949274ab 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -1,6 +1,6 @@ - Najedź na kod QR + Najedź na QR kod Pokaż QR kod Zdjęcie profilowe Twoje zdjęcie profilowe @@ -17,21 +17,29 @@ Spam Liczba spamu z tego transmitera Podszywanie się - Nielegalne zachowanie + Antyspołeczne zachowanie + Inne Nieznany Ikona transmitera Autor nieznany Skopiuj tekst Kopiuj identyfikator autora Kopiuj identyfikator notatki + Roześlij + Znacznik czasowy + Znacznik czasowy: Oczekujące potwierdzenia + OTS: Oczekuje + Poproś o usunięcie Zablokuj / Zgłoś Zgłoś spam/oszustwo Zgłoś podszywanie się Zgłoś niedozwoloną zawartość + Zgłoś antyspołeczne działania Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc odpowiedzieć Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby zwiększyć liczbę postów Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby polubić posty + Nie ustawiono kwoty zap-a. Naciśnij i przytrzymaj, aby zmienić Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc wysyłać zapy Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc obserwować Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc przestać obserwować @@ -39,13 +47,17 @@ Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc pokazać słowo lub zdanie Zapy Liczba wyświetleń - Zwiększ - zwiększony + Repost + reposted edytowano edytuj #%1$s oryginalny - Nowa kwota w Satsach + Zacytuj + Sklonuj + Zaproponuj zmianę + Nowa kwota w Satach Dodaj + "odpowiadając do" " i " "na kanale " Baner profilu @@ -57,7 +69,9 @@ Filtry bezpieczeństwa Wyloguj się Pokaż Więcej + Faktura LN Zapłać + Lightning-wskazówki Notatka dla odbiorcy Dziękuję bardzo! Kwota w Satsach @@ -65,43 +79,68 @@ "Błąd analizowania podglądu dla %1$s : %2$s" "Podgląd obrazu karty dla %1$s" Nowy kanał - Nazwa kanału - Moja wspaniała Grupa - Adres URL zdjęcia + Nazwa Grupy + Nazwa Grupy + Adres Url obrazka Opis - "O nas. " + "Informacja o grupie.. " Co masz na myśli? Wyślij Zapisz Utwórz Anuluj + Nie udało się przesłać obrazu Adres transmitera Wpisy + Bajtów Błędy + Liczba błędów połączenia w tej sesji + Główny kanał + Kanał wiadomości prywatnych + Kanał Czatu Publicznego Kanał Ogólny + Przeszukaj kanał Dodaj Transmiter Nazwa użytkownika + Mój nick + G Braun + Witamy serdecznie! Nazwa użytkownika + Mój login O mnie URL awatara URL banera Adres URL strony + LN Adres + Adres URL LN (nieaktualny) Zapisz w galerii Zdjęcie zapisane w galerii Nie udało się zapisać zdjęcia Dodaj zdjęcie Wgrywanie… + Użytkownik nie ma skonfigurowanego adresu LN, aby odbierać saty "odpowiedz tutaj.. " + Kopiuje ID wpisu do schowka w celu udostępnienia w Nostr + Kopiuj ID kanału (wpisu) do schowka + Edytuje metadane kanału Dołącz + Popularne + Nowe Prośby Zablokowani użytkownicy Nowe Wpisy - Konwersacje - Uwagi + Rozmowy + Wpisy Odpowiedzi + "Obserwowani" "Zgłoszenia" Więcej opcji " Transmitery" Strona www + Lightning Adres + Kopiuje Nsec ID (hasło) do schowka w celu wykonania kopii zapasowej + Skopiuj tajny klucz do schowka + Kopiuje klucz publiczny do schowka w celu udostępnienia + Skopiuj klucz publiczny (NPub) do schowka Wyślij bezpośrednią wiadomość Edytowanie metadanych użytkowników Obserwuj @@ -123,6 +162,7 @@ Wymagane jest zaakceptowanie warunków użytkowania Hasło jest wymagane Klucz jest wymagany + Nazwa jest wymagana Zaloguj się Zarejestruj się Utwórz konto @@ -132,19 +172,30 @@ Utwórz nowe konto Wygeneruj nowy klucz Wczytywanie zawartości - Wczytywanie konta + Ładowanie konta "Błąd wczytywania odpowiedzi: " Spróbuj ponownie Brak zawartości. Odśwież + stworzył (a) + z opisem i zdjęcie zmieniono nazwę czatu na + opis dla i zdjęcie do Wyjdź - Przestań obserwować + Porzuć obserwację + Kanał utworzony + "Informacje o kanale zmienione na" Czat Publiczny + odebranych wiadomości Usuń + Automatycznie + przetłumaczono z do + Najpierw pokaż w %1$s + Zawsze tłumacz na %1$s + Nigdy nie tłumacz z %1$s Adres Nostr nigdy teraz @@ -156,22 +207,52 @@ Zgłoś nienawistną mowę Zgłoś Nagość / Pornografię inne + Oznacz wszystkie popularne jako przeczytane + Oznacz wszystkie nowe jako przeczytane Oznacz wszystkie jako przeczytane + Klucze kopii zapasowej + ## Porady dotyczące kopii zapasowej i bezpieczeństwa + \n\nTwoje konto jest zabezpieczone tajnym kluczem. Kluczem jest długa sekwencja znaków, zaczynająca się od **nsec1**. Każdy, kto ma dostęp do tego tajnego klucza może publikować i zmieniać Twoją tożsamość. + \n\n- **nie** umiejszczaj swojego tajnego klucza na żadnej stronie internetowej lub oprogramowaniu, któremu nie ufasz. + \n- Deweloperzy Ametyst **nigdy** nie będą prosić o Twój tajny klucz. + \n- **Zrób ** bezpieczną kopię zapasową swojego tajnego klucza do ewentualnego odzyskania konta. Zalecamy użycie menedżera haseł. + + Dla dodatkowego bezpieczeństwa możesz zaszyfrować swój klucz hasłem. Ten klucz zaczyna się od **ncryptsec1** i nie może być użyty bez hasła. + \n\nJeśli zgubisz hasło, nie będziesz w stanie odzyskać swojego klucza. + + Nie udało się zaszyfrować Twojego klucza prywatnego + Tajny klucz (nsec) skopiowany do schowka + Kopiuj mój tajny klucz + Zaszyfruj i skopiuj mój tajny klucz + Uwierzytelnianie nie udało się + Biometria nie uwierzytelniła właściciela tego telefonu + Biometria nie uwierzytelniła właściciela tego telefonu. Błąd: %1$s Błąd + "Utworzony przez %1$s" + "Wizerunek odznaki dla %1$s" + Otrzymałeś nową odznakę + Nagroda przyznana dla + Skopiowano tekst wpisu do schowka + Skopiowano @npub autora do schowka + Skopiowane ID wpisu (@note1) do schowka Zaznacz tekst + "<Unable to decrypt private message>\n\nZostałeś wspomniany w prywatnej/zaszyfrowanej rozmowie pomiędzy %1$s i %2$s." Dodaj nowe konto Konta Wybierz Konto Dodaj konto + Konto aktywne + Ma klucz prywatny Tylko do odczytu, brak klucza prywatnego Wstecz Wybierz + Udostępnij link przeglądarki Udostępnij ID autora ID notatki Skopiuj tekst Usuń - Przestań obserwować + Porzuć Śledź Poproś o usunięcie Amethyst poprosi o usunięcie Twojej notatki z aktualnie podłączonych transmiterów. Nie ma gwarancji, że Twoja notatka zostanie trwale usunięta z tych lub z innych transmiterów, gdzie może być przechowywana. @@ -180,15 +261,17 @@ Zablokuj Zgłoś Usuń - Nie pokazuj ponownie + Nie pokazuj więcej Spam lub oszustwa - Wulgaryzmy lub nienawistne zachowanie + Wulgaryzmy lub obelgi Złośliwe podszywanie się - Nagość lub zawartość graficzna - Nielegalne zachowanie + Nagość lub obraźliwa grafika + Antyspołeczne zachowanie Zablokowanie użytkownika ukryje jego zawartość w aplikacji. Twoje notatki są nadal widoczne publicznie, w tym dla osób, które blokujesz. Zablokowani użytkownicy są wymienieni na ekranie filtrów bezpieczeństwa. Zgłoś nadużycie + Wszystkie wysłane zgłoszenia będą publicznie dostępne. + Opcjonalnie podaj dodatkowe informacje dotyczące zgłoszenia… Dodatkowe informacje Powód Wybierz powód… @@ -203,25 +286,59 @@ Dodaj do publicznych zakładek Usuń z prywatnych zakładek Usuń z publicznych zakładek + Usługa Wallet Connect Autoryzuje Nostr Secret do płacenia zapów bez opuszczania aplikacji. Strzeż pilnie i użyj prywatnego transmitera, jeśli to możliwe + Klucz publiczny Wallet Connect + Transmiter Wallet Connect + Klucz dostępu do portfela Pokaż tajny klucz + klucz prywatny nsec / hex + Kwota zobowiązania w Satach + Wyślij Ankietę + Wymagane pola: + Odbiorcy zap + Podstawowy opis ankiety… Opcja %s + Opis opcji ankiety + Pola opcjonalne: Minimalny Zap Maksymalny Zap + Konsensus (0–100)% + Zamknij po dni + Nie można głosować + Ankieta jest zamknięta dla nowych głosów + Kwota Zap + Tylko jeden głos na użytkownika jest dozwolony dla tego typu ankiety + "Szukam wydarzenia %1$s" Dodaj wiadomość publiczną Dodaj prywatną wiadomość + Dodaj wiadomość do faktury Dziękujemy za całą twoją pracę! Utwórz i Dodaj + Autorzy ankiet nie mogą głosować we własnych ankietach. + Co to oznacza? + Ta zawartość jest taka sama od początku + Ta zawartość uległa zmianie. Autor mógł nie zobaczyć ani nie zatwierdzić zmiany Dodaj zdjęcie Dodaj wideo Dodaj dokument Dodaj do wiadomości Opis zawartości + Błękitna łódź na białej piaszczystej plaży o zachodzie słońca + Typ Zap-a + Typ Zap-a dla wszystkich opcji + Publiczny Każdy może zobaczyć transakcję i wiadomość + Prywatny Nadawca i odbiorca mogą zobaczyć się nawzajem i przeczytać wiadomość + Anonimowy + Odbiorca i użytkownicy nie wiedzą, kto wysłał płatność + Bez Zapów + Brak śladu w Nostr, tylko w Lightning Serwer Plików + LnAdres lub @Użytkownik Twoje transmitery (NIP-95) Pliki są przechowywane przez Twoje retransmitery. Nowy NIP: sprawdź, czy jest obsługiwany Tor/Orbot - Ustawienia @@ -250,6 +367,10 @@ Powiadamia Cię, gdy nadejdzie prywatna wiadomość Otrzymano Zapy Powiadamia Cię, gdy ktoś prześle ci zapy + %1$s Satów + Od %1$s + dla %1$s + Powiadom: Dołącz do rozmowy ID Użytkownika lub Grupy npub, nevent lub hex @@ -261,27 +382,62 @@ Zawsze ukrywaj wrażliwe treści Zawsze pokazuj wrażliwą zawartość Zawsze pokazuj ostrzeżenia dotyczące zawartości + Zalecenia: Filtruj spam z nieznajomych Ostrzegaj, gdy posty zostały zgłoszone przez osoby które obserwujesz + Nowy Symbol reakcji + Brak wstępnie wybranych typów reakcji dla tego użytkownika. Przytrzymaj przycisk serce, aby zmienić + Zapraiser + Dodaje docelową liczbę satów do podniesienia dla tego wpisu. W zależności od aplikacji może być pokazywany to jako pasek postępu, aby zachęcić do darowizn + Docelowa kwota w Satach + Zapraiser przy: %1$s. %2$s satach do celu Odczytaj z Transmitera Zapisz do Transmitera + Ilość w bajtach, która została wysłana do tego transmitera, w tym filtry i wydarzenia + Ilość w bajtach, która została otrzymana z tego transmitera, w tym filtry i wydarzenia Wystąpił błąd podczas próby uzyskania informacji o transmiterze z %1$s Właściciel Wersja Oprogramowanie Kontakt + Obsługiwane Nipy + Opłaty za wstęp Adres URL płatności Ograniczenia Kraje Języki Tagi Polityka publikowania + Błędy i powiadomienia z tego transmitera Długość wiadomości Subskrybcje Filtry + Długość identyfikatora subskrypcji + Minimalny prefiks + Maksymalna ilość tagów wydarzeń + Długość zawartości + Minimalny poW + Autoryzacja + Płatność Cashu Token + Wykup + Wyślij do Zap Wallet + Otwórz w Portfelu Cashu + Skopiuj token + Nie ustawiono adresu Lightning + Skopiowano token do schowka + LIVE + OFFLINE + Zakończony + ZAPLANOWANE + Transmisja wyłączona + Transmisja na żywo zakończona + Wylogowanie usuwa wszystkie informacje lokalne. Upewnij się, że masz kopię zapasową kluczy prywatnych, aby uniknąć utraty konta. Czy chcesz kontynuować? Obserwowane tagi Transmitery + Przegląd Wpisów + Market + Na żywo Społeczność Czaty Zatwierdzone posty @@ -295,6 +451,7 @@ Nigdy Kompletny Uproszczony + Wydajność Automatyczny Jasny Ciemny @@ -312,6 +469,7 @@ Spamerzy Wyciszone. Kliknij, aby wyłączyć wyciszenie Dźwięk włączony. Kliknij, aby wyciszyć + Wyszukiwanie lokalne i zdalne Adres Nostr został zweryfikowany Nieudana weryfikacja adresu Nostr Sprawdzanie adresu Nostr @@ -319,78 +477,224 @@ Domyślne Wybierz Transmiter, aby kontynuować Przekaż zapy do: + Wspierający klient podzieli i przekaże zapy na LNAdres lub profil użytkownika poniżej zamiast Ciebie + Wyświetl lokalizację jako + Dodaje Geohash twojej lokalizacji do wpisu. Użytkownicy będą wiedzieli, że jesteś mniej niż 5 km od bieżącej lokalizacji Dodaje ostrzeżenie o wrażliwych treściach przed wyświetleniem treści. Jest to idealne dla dowolnych treści NSFW lub treści, które niektóre osoby mogą uznać za obraźliwe lub przeszkadzające + Nowa Funkcjonalność Aktywacja tego trybu wymaga wysłania wiadomości NIP-17 przez Ametyst. (Prezent, Zapieczętowane wiadomości bezpośrednie i grupowe). NIP-17 jest nowy, a większość klientów jeszcze go nie zaimplementowała. Upewnij się, że odbiorca używa kompatybilnego klienta. + Aktywuj + Publiczna + Nowa Grupa Publiczna lub Prywatna + Prywatna Do Temat Temat dyskusji "\@Użytkownik1, @Użytkownik2, @Użytkownik3" Członkowie tej grupy + Wyjaśnienie dla członków + Zmiana nazwy dla nowych celów. + Wklej ze schowka Dla interfejsu aplikacji Motyw ciemny, jasny lub automatyczny + Automatycznie załaduj obrazki i Gify Automatycznie odtwarzaj filmy i pliki GIF Pokaż podgląd linków + Kiedy załadować obrazki Kopiuj do schowka Kopiuj npub do schowka + Udostępnij lub Zapisz Kopiuj adres URL do schowka - Kopiuj ID notatki do schowka + Kopiuj ID wpisu do schowka + Utworzono + Zasady + Zaloguj się za pomocą Amber Zaktualizuj swój status Błąd podczas analizowania komunikatu o błędzie Głosy są oceniane na podstawie ilości zapperów. Możesz ustawić minimalną kwotę, aby uniknąć spamerów i maksymalną kwotę, aby uniknąć przejęcia ankiety przez dużych zapperów. Użyj tej samej kwoty w obu polach, aby upewnić się, że każdy głos ma taką samą wartość. Pozostaw to pole puste, aby zaakceptować dowolną kwotę. + Nie można wysłać zapa + Wyślij wiadomość użytkownikowi OK + Nieudane połączenie z %1$s: %2$s + Nie udało się skompilować adresu URL NIP-11 dla %1$s: %2$s + Nieudane połączenie z %1$s: %2$s + Nie udało się przeanalizować wyniku z %1$s: %2$s + Transmiter odrzucił żądanie %1$s: %2$s + Nieudane połączenie z %1$s: %2$s + Nie udało się przeanalizować wyniku z %1$s: %2$s + %1$s nie powiódł się z kodem %2$s + Aktywny dla: + Strona główna + DMs + Czaty Wszystkie + Szukaj Podziel i przekaż Zapy - Obsługa klientów podzieli i przekaże zapy do użytkowników dodanych tutaj, zamiast twoich + Wspierający klient podzieli i przekaże zapy do dodanego tutaj użytkownika, a nie do Ciebie Szukaj i dodaj użytkownika + Nick lub Login + Brakująca konfiguracja LN + Użytkownik %1$s nie ma skonfigurowanego adresu LN, aby odbierać saty + Procentowo 25 + Podziel zapsy z + Przesyłanie zapasów do + Nie znaleziono portfeli Lightning + Zapłacone + Portfel %1$s + Błąd podczas otwierania aplikacji podpisującej + Nie można odnaleźć aplikacji podpisującej. Sprawdź, czy aplikacja nie została odinstalowana + Odrzucono aplikację podpisującego + Upewnij się, że aplikacja podpisującego autoryzuje tę transakcję + Nie znaleziono portfeli do zapłacenia faktury z Lightning (Error: %1$s). Proszę zainstalować Lightning wallet, aby używać zapów + Nie znaleziono portfeli do zapłacenia faktury z Lightning. Proszę zainstalować Lightning wallet, aby używać zapów Ukryte słowa + Ukryj nowe słowo lub wyrażenie Zdjęcie profilowe Pokaż zdjęcia profilowe Wybierz opcję + Nie można zapłacić faktury + Nie można wypłacić + Nie można skonfigurować Wallet Connect + Błąd podczas analizowania ciągu połączeń NIP-47. Sprawdź, czy jest to poprawne z Twoim dostawcą portfela: %1$s. Błąd: %2$s + Błąd podczas analizowania ciągu połączeń NIP-47. Sprawdź u dostawcy portfela, czy jest to poprawne: %1$s. + Nie można wykupić Cashu + Mint dostarczył następujący komunikat błędu: %1$s + Tokeny Cashu zostały już wydane. + Cashu odebrano + %1$s saty zostały wysłane do Twojego portfela. (opłata: %2$s satów) + W systemie nie znaleziono kompatybilnego portfela Cashu + Nie można pobrać faktury z serwerów odbiorcy Twój dostawca połączenia z portfelem zwrócił następujący błąd: %1$s Połączenie z serwerem Tor nieudane - Nie można pobrać dokumentu transmitera + Nie można pobrać pliku z transmitera + Nie można stworzyć LNUrl z Adresu \"%1$s\". Sprawdź konfigurację użytkownika + Usługa lightning odbiorcy w %1$s jest niedostępna. Została obliczona na podstawie adresu lightning \"%2$s\". Błąd: %3$s. Sprawdź, czy serwer jest gotowy i czy adres lightning jest poprawny + Nie można rozwiązać %1$s. Sprawdź, czy jesteś połączony, czy serwer jest gotowy i czy lightning adres %2$s jest poprawny + Nie można rozwiązać %1$s. Sprawdź, czy jesteś połączony, czy serwer jest gotowy i czy lightning adres %2$s jest poprawny.\n\nWyjątkiem było: %3$s + Nie można pobrać faktury z %1$s + Błąd przetwarzania JSON z adresu lightning. Sprawdź konfigurację lightning użytkownika + Błąd przetwarzania JSON z %1$s. Sprawdź konfigurację lightning użytkownika + Adres zwrotny nie został znaleziony w konfiguracji serwera adresu lightning użytkownika + Nie znaleziono zwrotnego adresu URL z odpowiedzi %1$s + Wystąpił błąd podczas analizowania JSON z pobierania faktury z Lightning Adresu. Sprawdź konfigurację lightning użytkownika + Błąd przetwarzania pliku JSON z pobierania faktury %1$s. Sprawdź konfigurację lightning użytkownika + Nieprawidłowa kwota faktury (%1$s satów) od %2$s. Powinieno być %3$s + Nie można utworzyć faktury przed wysłaniem zapa. Portfel odbiorcy wysłał następujący błąd: %1$s + Nie można utworzyć faktury. Wiadomość od %1$s: %2$s + Nie można utworzyć faktury przed wysłaniem zapa. Element pr nie został znaleziony w powstałym JSON. + Nie można utworzyć faktury od %1$s: Element pr nie został znaleziony w powstałym JSON. \"Tylko do odczytu\" + Nie ustawiono reakcji + Wybierz aplikację UnifiedPush + Powiadomienia + Z zainstalowanych aplikacji UnifiedPush + Żaden + Wyłącz powiadomienia + Używa aplikacji %1$s + Ustawienia powiadomień + Aby otrzymywać powiadomienia, zainstaluj dowolną aplikację, która obsługuje [Unified Push](https://unifiedpush. rg/), np. [Nfty](https://ntfy.sh/). + Po zainstalowaniu, wybierz w ustawieniach aplikację, którą chcesz użyć. + + Wiadomość od %1$s + Wpis + Wyślij sprzedawcy wiadomość + Witaj %1$s, czy to nadal jest dostępne? + Cześć, czy to jest nadal dostępne? + Sprzedaj przedmiot Tytuł iPhone 13 Stan Kategoria Cena (w Satach) + 1000 Lokalizacja Miasto, Województwo, Kraj + Nowość + To zupełnie nowa jednostka, w oryginalnym pudełku Jak nowy + Jest używany, ale nie ma znaków użycia + Stan dobry + Posiada kilka powierzchownych śladów użytkowania + Średnia + Nadal jest w dobrym stanie i wciąż nadaje się do użytku + Odzież Akcesoria Elektronika Meble + Kolekcjonerstwo Książki Zwierzęta domowe Sporty Fitness Sztuka + Rzemiosło + AGD Biuro Żywność Różne Inne + Nie udało się załadować załącznika + Nie można otworzyć skompresowanego pliku + Błąd podczas kompresji plików: %1$s Błąd wysyłania: %1$s Serwer nie podał adresu URL po załadowaniu + Nie można pobrać przesłanych plików z serwera + Nie można przygotować pliku lokalnego do przesłania: %1$s + Edytuj wersję roboczą + Zaloguj się przy użyciu QR kodu + Ścieżka + Start + Szukaj + Odkryj + Wiadomości + Powiadomienia Wszystkie + Filmiki Filtry bezpieczeństwa Nowy post + Nowe Króciaki: zdjęcia lub filmiki + Nowy wpis w społeczności + Otwórz wszystkie reakcje na ten post + Zamknij wszystkie reakcje na ten post + Odpowiedz + Zrepostuj lub Zacytuj + Lubię + Zap + Zdjęcie profilowe %1$s Transmiter %1$s Rozwiń listę transmiterów + Opcje wpisu Wybór listy transmiterów + Ankieta + Wyłącz Ankietę + Faktura Bitcoin + Anuluj fakturę Bitcoin + Anuluj sprzedaż przedmiotu + Zapraiser + Anuluj Zapraiser + Lokalizacja + Usuń lokalizację + Podział Zap + Anuluj podział Zap Dodaj ostrzeżenie o treści Usuń ostrzeżenie o treści Pokaż npub jako QR kod Nieprawidłowy adres + Ametyst otrzymał URI do otwarcia, ale ten adres był nieprawidłowy: %1$s Transmitery Skrzynki Odbiorczej + Transmitery %1$s + Korzystanie ze zwykłych transmiterów Skonfiguruj prywatne transmitery odbiorcze + To ustawienie pozwala wszystkim wiedzieć, których transmiterów użyć podczas wysyłania wiadomości do Ciebie. Bez nich możesz przegapić niektóre wiadomości. Dobre opcje to:\n - inbox.nostr.wine (płatny)\n - you.nostr1.com (transmitery osobiste - płatny) Wstaw od 1 do 3 transmiterów, które będą służyć jako Twoja prywatna skrzynka odbiorcza. Transmitery DM powinny akceptować dowolne wiadomości od każdego, ale pozwalać tylko na ich pobieranie. - Skonfiguruj + Konfiguruj Transmitery wyszukujące - Skonfiguruj swoje Transmitery wyszukujące + Skonfiguruj transmitery wyszukujące + Stworzenie listy transmiterów specjalnie przeznaczonych do wyszukiwania i tagowania użytkowników poprawi te wyniki. Wstaw od 1 do 3 transmiterów, które będą używane podczas wyszukiwania treści lub tagowania użytkowników. Upewnij się, że zaimplementowano NIP-50 + Dobre opcje to:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com Ustawienia Transmiterów Publiczne transmitery domowe Ten typ transmitera przechowuje całą zawartość. Amethyst będzie wysyłać Twoje posty tutaj, a inni będą korzystać z tych transmiterów, aby znaleźć Twoje treści. Wstaw od 1 do 3 transmiterów. Mogą to być transmitery prywatne, płatne lub publiczne. @@ -406,13 +710,46 @@ Lista transmiterów używanych podczas wyszukiwania treści lub użytkowników. Tagowanie i wyszukiwanie nie będą działać, jeśli nie będą dostępne żadne opcje. Upewnij się, że zaimplementowano NIP-50. Lokalne Transmitery Lista transmiterów działających na tym urządzeniu. + Wspieraj deweloperów! + Twoja darowizna pomaga nam coś zmienić. Każdy sat liczy się! Przekaż darowiznę został Ci dostarczony przez: Ta wersja została Ci dostarczona przez: Wersja %1$s Dziękuję! Maksymalny Limit + Twórcy z ograniczeniami + Sklonowany z + SKLONUJ + Repozytorium Git: %1$s + Strona internetowa: + Klonuj: + OTS: %1$s + Potwierdzenie znacznika czasu + Istnieje dowód na to, że ten post został podpisany przed %1$s. Dowód został opatrzony pieczęcią w łańcuchu bloków Bitcoin w tym dniu i czasie. + Edytuj wpis + Propozycja ulepszenia wpisu + Podsumowanie zmian + Szybkie poprawki… + Zaakceptuj sugestię + Pobierz + Tekst włączony + Tekst wyłączony + Zapieczętowana wiadomość wyłączona. Kliknij, aby włączyć zapieczętowaną wiadomość + Zapieczętowana wiadomość włączona. Kliknij, aby wyłączyć zapieczętowaną wiadomość Wyślij + Odtwórz nazwę użytkownika jako dźwięk Zeskanuj QR kod + Przejdź do Alby zewnętrznego dostawcy portfela + Nie jest możliwa odpowiedź na szkic wpisu + Nie jest możliwe zacytowanie szkicu notatki + Nie jest możliwe reagowanie na szkic wpisu + Nie jest możliwy zap dla szkicu wpisu + Szkic wpisu Z sms-a + Szukanie aplikacji + Wysłano zapytanie, oczekiwanie na odpowiedź + Zapytanie o pracę z DVM + Prośba o płatność została wysłana, oczekiwanie na potwierdzenie z Twojego portfela + Oczekiwanie na potwierdzenie płatności lub wysłanie wyników DVM diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index df1720bc08..d14bf16161 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -449,6 +449,7 @@ Nunca Concluído Simplificado + Desempenho Sistema Claro Escuro diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index a9647edcfe..bd0dcf9c9d 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -448,6 +448,7 @@ Aldrig Slutförd Förenklad + Prestanda System Ljus Mörk diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 983fe5be2f..775ddbecf0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -515,6 +515,10 @@ 向用户发送消息 无法连接 %1$s: %2$s + 组合 %1$s 的 NIP-11 URL 失败: %2$s + 无法连接 %1$s: %2$s + 无法解析来自 %1$s 的响应: %2$s + 中继拒绝请求 %1$s: %2$s 无法连接 %1$s: %2$s 无法解析来自 %1$s的结果: %2$s %1$s 失败,代码 %2$s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 296d2e8cfa..b3e0e6f41c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -515,6 +515,7 @@ Complete Simplified + Performance System Light diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index 2034c3174d..4f3eb79646 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.components import android.content.res.Resources -import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -50,7 +49,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -59,7 +57,9 @@ import androidx.compose.ui.unit.dp import androidx.core.os.ConfigurationCompat import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -82,7 +82,7 @@ fun TranslatableRichTextViewer( nav: (String) -> Unit, ) { var translatedTextState by - remember(content) { mutableStateOf(TranslationConfig(content, null, null, false)) } + remember(id, content.length) { mutableStateOf(TranslationConfig(content, null, null, false)) } TranslateAndWatchLanguageChanges(content, accountViewModel) { result -> if ( @@ -94,7 +94,7 @@ fun TranslatableRichTextViewer( } } - Crossfade(targetState = translatedTextState) { + CrossfadeIfEnabled(targetState = translatedTextState, accountViewModel = accountViewModel) { RenderText( translatedTextState = it, content = content, @@ -182,11 +182,11 @@ private fun TranslationMessage( buildAnnotatedString { withStyle(clickableTextStyle) { pushStringAnnotation("langSettings", true.toString()) - append(stringResource(R.string.translations_auto)) + append(stringRes(R.string.translations_auto)) pop() } - append("-${stringResource(R.string.translations_translated_from)} ") + append("-${stringRes(R.string.translations_translated_from)} ") withStyle(clickableTextStyle) { pushStringAnnotation("showOriginal", true.toString()) @@ -194,7 +194,7 @@ private fun TranslationMessage( pop() } - append(" ${stringResource(R.string.translations_to)} ") + append(" ${stringRes(R.string.translations_to)} ") withStyle(clickableTextStyle) { pushStringAnnotation("showOriginal", false.toString()) @@ -215,8 +215,7 @@ private fun TranslationMessage( overflow = TextOverflow.Visible, maxLines = 3, ) { spanOffset -> - annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { - span -> + annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also { span -> if (span.tag == "showOriginal") { onChangeWhatToShow(span.item.toBoolean()) } else { @@ -245,7 +244,7 @@ private fun TranslationMessage( Spacer(modifier = Modifier.size(10.dp)) Text( - stringResource( + stringRes( R.string.translations_never_translate_from_lang, Locale(source).displayName, ), @@ -276,7 +275,7 @@ private fun TranslationMessage( Spacer(modifier = Modifier.size(10.dp)) Text( - stringResource( + stringRes( R.string.translations_show_in_lang_first, Locale(source).displayName, ), @@ -306,7 +305,7 @@ private fun TranslationMessage( Spacer(modifier = Modifier.size(10.dp)) Text( - stringResource( + stringRes( R.string.translations_show_in_lang_first, Locale(target).displayName, ), @@ -341,7 +340,7 @@ private fun TranslationMessage( Spacer(modifier = Modifier.size(10.dp)) Text( - stringResource( + stringRes( R.string.translations_always_translate_to_lang, lang.displayName, ), @@ -373,12 +372,12 @@ fun TranslateAndWatchLanguageChanges( // This takes some time. Launches as a Composition scope to make sure this gets cancel if this // item gets out of view. withContext(Dispatchers.IO) { - LanguageTranslatorService.autoTranslate( - content, - accountViewModel.account.dontTranslateFrom, - accountViewModel.account.translateTo, - ) - .addOnCompleteListener { task -> + LanguageTranslatorService + .autoTranslate( + content, + accountViewModel.account.dontTranslateFrom, + accountViewModel.account.translateTo, + ).addOnCompleteListener { task -> if (task.isSuccessful && !content.equals(task.result.result, true)) { if (task.result.sourceLang != null && task.result.targetLang != null) { val preference = diff --git a/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifierTest.kt b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifierTest.kt index a8ee63a463..6a1c6af29f 100644 --- a/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifierTest.kt +++ b/app/src/test/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifierTest.kt @@ -64,7 +64,9 @@ class Nip05NostrAddressVerifierTest { coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { - secondArg<(String) -> Unit>().invoke(nostrJson) + runBlocking { + secondArg Unit>().invoke(nostrJson) + } } val nip05 = "$userNameToTest@domain.com" @@ -95,7 +97,9 @@ class Nip05NostrAddressVerifierTest { "}" coEvery { nip05Verifier.fetchNip05Json(any(), any(), any()) } answers { - secondArg<(String) -> Unit>().invoke(nostrJson) + runBlocking { + secondArg Unit>().invoke(nostrJson) + } } val nip05 = "$ALL_LOWER_CASE_USER_NAME@domain.com" diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/labels/Following.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Following.kt similarity index 95% rename from commons/src/main/java/com/vitorpamplona/amethyst/commons/labels/Following.kt rename to commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Following.kt index 7ded87dd05..b036227999 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/labels/Following.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Following.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.commons.labels +package com.vitorpamplona.amethyst.commons.icons import androidx.compose.foundation.Image import androidx.compose.runtime.Composable @@ -41,12 +41,9 @@ private fun VectorPreview() { private var labelsFollowing: ImageVector? = null public val Following: ImageVector - get() { - if (labelsFollowing != null) { - return labelsFollowing!! - } - labelsFollowing = - ImageVector.Builder( + get() = + labelsFollowing ?: ImageVector + .Builder( name = "Following", defaultWidth = 30.dp, defaultHeight = 30.dp, @@ -128,6 +125,4 @@ public val Following: ImageVector close() } }.build() - - return labelsFollowing!! - } + .also { labelsFollowing = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Like.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Like.kt new file mode 100644 index 0000000000..3a2e3e693d --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Like.kt @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Like, null) +} + +private var labelsLike: ImageVector? = null + +public val Like: ImageVector + get() = + labelsLike ?: Builder( + name = "Like", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(12.0f, 21.638f) + horizontalLineToRelative(-0.014f) + curveTo(9.403f, 21.59f, 1.95f, 14.856f, 1.95f, 8.478f) + curveToRelative(0.0f, -3.064f, 2.525f, -5.754f, 5.403f, -5.754f) + curveToRelative(2.29f, 0.0f, 3.83f, 1.58f, 4.646f, 2.73f) + curveToRelative(0.814f, -1.148f, 2.354f, -2.73f, 4.645f, -2.73f) + curveToRelative(2.88f, 0.0f, 5.404f, 2.69f, 5.404f, 5.755f) + curveToRelative(0.0f, 6.376f, -7.454f, 13.11f, -10.037f, 13.157f) + horizontalLineTo(12.0f) + close() + moveTo(7.354f, 4.225f) + curveToRelative(-2.08f, 0.0f, -3.903f, 1.988f, -3.903f, 4.255f) + curveToRelative(0.0f, 5.74f, 7.034f, 11.596f, 8.55f, 11.658f) + curveToRelative(1.518f, -0.062f, 8.55f, -5.917f, 8.55f, -11.658f) + curveToRelative(0.0f, -2.267f, -1.823f, -4.255f, -3.903f, -4.255f) + curveToRelative(-2.528f, 0.0f, -3.94f, 2.936f, -3.952f, 2.965f) + curveToRelative(-0.23f, 0.562f, -1.156f, 0.562f, -1.387f, 0.0f) + curveToRelative(-0.014f, -0.03f, -1.425f, -2.965f, -3.954f, -2.965f) + close() + } + }.build() + .also { labelsLike = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Liked.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Liked.kt new file mode 100644 index 0000000000..2e58b4cf42 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Liked.kt @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Liked, null) +} + +private var labelsLiked: ImageVector? = null + +public val Liked: ImageVector + get() = + labelsLiked ?: Builder( + name = "Like", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFFCA395f)), + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(12.0f, 21.638f) + horizontalLineToRelative(-0.014f) + curveTo(9.403f, 21.59f, 1.95f, 14.856f, 1.95f, 8.478f) + curveToRelative(0.0f, -3.064f, 2.525f, -5.754f, 5.403f, -5.754f) + curveToRelative(2.29f, 0.0f, 3.83f, 1.58f, 4.646f, 2.73f) + curveToRelative(0.814f, -1.148f, 2.354f, -2.73f, 4.645f, -2.73f) + curveToRelative(2.88f, 0.0f, 5.404f, 2.69f, 5.404f, 5.755f) + curveToRelative(0.0f, 6.376f, -7.454f, 13.11f, -10.037f, 13.157f) + horizontalLineTo(12.0f) + close() + } + }.build() + .also { labelsLiked = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reply.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reply.kt new file mode 100644 index 0000000000..0c5e1adfc5 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reply.kt @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Reply, null) +} + +private var labelsReply: ImageVector? = null + +public val Reply: ImageVector + get() = + labelsReply ?: Builder( + name = "Reply", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(14.046f, 2.242f) + lineToRelative(-4.148f, -0.01f) + horizontalLineToRelative(-0.002f) + curveToRelative(-4.374f, 0.0f, -7.8f, 3.427f, -7.8f, 7.802f) + curveToRelative(0.0f, 4.098f, 3.186f, 7.206f, 7.465f, 7.37f) + verticalLineToRelative(3.828f) + curveToRelative(0.0f, 0.108f, 0.044f, 0.286f, 0.12f, 0.403f) + curveToRelative(0.142f, 0.225f, 0.384f, 0.347f, 0.632f, 0.347f) + curveToRelative(0.138f, 0.0f, 0.277f, -0.038f, 0.402f, -0.118f) + curveToRelative(0.264f, -0.168f, 6.473f, -4.14f, 8.088f, -5.506f) + curveToRelative(1.902f, -1.61f, 3.04f, -3.97f, 3.043f, -6.312f) + verticalLineToRelative(-0.017f) + curveToRelative(-0.006f, -4.367f, -3.43f, -7.787f, -7.8f, -7.788f) + close() + moveTo(17.833f, 15.214f) + curveToRelative(-1.134f, 0.96f, -4.862f, 3.405f, -6.772f, 4.643f) + lineTo(11.061f, 16.67f) + curveToRelative(0.0f, -0.414f, -0.335f, -0.75f, -0.75f, -0.75f) + horizontalLineToRelative(-0.396f) + curveToRelative(-3.66f, 0.0f, -6.318f, -2.476f, -6.318f, -5.886f) + curveToRelative(0.0f, -3.534f, 2.768f, -6.302f, 6.3f, -6.302f) + lineToRelative(4.147f, 0.01f) + horizontalLineToRelative(0.002f) + curveToRelative(3.532f, 0.0f, 6.3f, 2.766f, 6.302f, 6.296f) + curveToRelative(-0.003f, 1.91f, -0.942f, 3.844f, -2.514f, 5.176f) + close() + } + }.build() + .also { labelsReply = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Repost.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Repost.kt new file mode 100644 index 0000000000..d99d4a823d --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Repost.kt @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Repost, null) +} + +private var labelsRepost: ImageVector? = null + +public val Repost: ImageVector + get() = + labelsRepost ?: Builder( + name = "Repost", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(23.77f, 15.67f) + curveToRelative(-0.292f, -0.293f, -0.767f, -0.293f, -1.06f, 0.0f) + lineToRelative(-2.22f, 2.22f) + lineTo(20.49f, 7.65f) + curveToRelative(0.0f, -2.068f, -1.683f, -3.75f, -3.75f, -3.75f) + horizontalLineToRelative(-5.85f) + curveToRelative(-0.414f, 0.0f, -0.75f, 0.336f, -0.75f, 0.75f) + reflectiveCurveToRelative(0.336f, 0.75f, 0.75f, 0.75f) + horizontalLineToRelative(5.85f) + curveToRelative(1.24f, 0.0f, 2.25f, 1.01f, 2.25f, 2.25f) + verticalLineToRelative(10.24f) + lineToRelative(-2.22f, -2.22f) + curveToRelative(-0.293f, -0.293f, -0.768f, -0.293f, -1.06f, 0.0f) + reflectiveCurveToRelative(-0.294f, 0.768f, 0.0f, 1.06f) + lineToRelative(3.5f, 3.5f) + curveToRelative(0.145f, 0.147f, 0.337f, 0.22f, 0.53f, 0.22f) + reflectiveCurveToRelative(0.383f, -0.072f, 0.53f, -0.22f) + lineToRelative(3.5f, -3.5f) + curveToRelative(0.294f, -0.292f, 0.294f, -0.767f, 0.0f, -1.06f) + close() + moveTo(13.11f, 18.95f) + lineTo(7.26f, 18.95f) + curveToRelative(-1.24f, 0.0f, -2.25f, -1.01f, -2.25f, -2.25f) + lineTo(5.01f, 6.46f) + lineToRelative(2.22f, 2.22f) + curveToRelative(0.148f, 0.147f, 0.34f, 0.22f, 0.532f, 0.22f) + reflectiveCurveToRelative(0.384f, -0.073f, 0.53f, -0.22f) + curveToRelative(0.293f, -0.293f, 0.293f, -0.768f, 0.0f, -1.06f) + lineToRelative(-3.5f, -3.5f) + curveToRelative(-0.293f, -0.294f, -0.768f, -0.294f, -1.06f, 0.0f) + lineToRelative(-3.5f, 3.5f) + curveToRelative(-0.294f, 0.292f, -0.294f, 0.767f, 0.0f, 1.06f) + reflectiveCurveToRelative(0.767f, 0.293f, 1.06f, 0.0f) + lineToRelative(2.22f, -2.22f) + lineTo(3.512f, 16.7f) + curveToRelative(0.0f, 2.068f, 1.683f, 3.75f, 3.75f, 3.75f) + horizontalLineToRelative(5.85f) + curveToRelative(0.414f, 0.0f, 0.75f, -0.336f, 0.75f, -0.75f) + reflectiveCurveToRelative(-0.337f, -0.75f, -0.75f, -0.75f) + close() + } + }.build() + .also { labelsRepost = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reposted.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reposted.kt new file mode 100644 index 0000000000..28563c104e --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Reposted.kt @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Reposted, null) +} + +private var labelsReposted: ImageVector? = null + +public val Reposted: ImageVector + get() = + labelsReposted ?: Builder( + name = "Repost", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF59bc6d)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(23.77f, 15.67f) + curveToRelative(-0.292f, -0.293f, -0.767f, -0.293f, -1.06f, 0.0f) + lineToRelative(-2.22f, 2.22f) + lineTo(20.49f, 7.65f) + curveToRelative(0.0f, -2.068f, -1.683f, -3.75f, -3.75f, -3.75f) + horizontalLineToRelative(-5.85f) + curveToRelative(-0.414f, 0.0f, -0.75f, 0.336f, -0.75f, 0.75f) + reflectiveCurveToRelative(0.336f, 0.75f, 0.75f, 0.75f) + horizontalLineToRelative(5.85f) + curveToRelative(1.24f, 0.0f, 2.25f, 1.01f, 2.25f, 2.25f) + verticalLineToRelative(10.24f) + lineToRelative(-2.22f, -2.22f) + curveToRelative(-0.293f, -0.293f, -0.768f, -0.293f, -1.06f, 0.0f) + reflectiveCurveToRelative(-0.294f, 0.768f, 0.0f, 1.06f) + lineToRelative(3.5f, 3.5f) + curveToRelative(0.145f, 0.147f, 0.337f, 0.22f, 0.53f, 0.22f) + reflectiveCurveToRelative(0.383f, -0.072f, 0.53f, -0.22f) + lineToRelative(3.5f, -3.5f) + curveToRelative(0.294f, -0.292f, 0.294f, -0.767f, 0.0f, -1.06f) + close() + moveTo(13.11f, 18.95f) + lineTo(7.26f, 18.95f) + curveToRelative(-1.24f, 0.0f, -2.25f, -1.01f, -2.25f, -2.25f) + lineTo(5.01f, 6.46f) + lineToRelative(2.22f, 2.22f) + curveToRelative(0.148f, 0.147f, 0.34f, 0.22f, 0.532f, 0.22f) + reflectiveCurveToRelative(0.384f, -0.073f, 0.53f, -0.22f) + curveToRelative(0.293f, -0.293f, 0.293f, -0.768f, 0.0f, -1.06f) + lineToRelative(-3.5f, -3.5f) + curveToRelative(-0.293f, -0.294f, -0.768f, -0.294f, -1.06f, 0.0f) + lineToRelative(-3.5f, 3.5f) + curveToRelative(-0.294f, 0.292f, -0.294f, 0.767f, 0.0f, 1.06f) + reflectiveCurveToRelative(0.767f, 0.293f, 1.06f, 0.0f) + lineToRelative(2.22f, -2.22f) + lineTo(3.512f, 16.7f) + curveToRelative(0.0f, 2.068f, 1.683f, 3.75f, 3.75f, 3.75f) + horizontalLineToRelative(5.85f) + curveToRelative(0.414f, 0.0f, 0.75f, -0.336f, 0.75f, -0.75f) + reflectiveCurveToRelative(-0.337f, -0.75f, -0.75f, -0.75f) + close() + } + }.build() + .also { labelsReposted = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Search.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Search.kt new file mode 100644 index 0000000000..f3be659d2d --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Search.kt @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Search, null) +} + +private var labelsSearch: ImageVector? = null + +val Search: ImageVector + get() = + labelsSearch ?: Builder( + name = "Search", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(21.53f, 20.47f) + lineToRelative(-3.66f, -3.66f) + curveTo(19.195f, 15.24f, 20.0f, 13.214f, 20.0f, 11.0f) + curveToRelative(0.0f, -4.97f, -4.03f, -9.0f, -9.0f, -9.0f) + reflectiveCurveToRelative(-9.0f, 4.03f, -9.0f, 9.0f) + reflectiveCurveToRelative(4.03f, 9.0f, 9.0f, 9.0f) + curveToRelative(2.215f, 0.0f, 4.24f, -0.804f, 5.808f, -2.13f) + lineToRelative(3.66f, 3.66f) + curveToRelative(0.147f, 0.146f, 0.34f, 0.22f, 0.53f, 0.22f) + reflectiveCurveToRelative(0.385f, -0.073f, 0.53f, -0.22f) + curveToRelative(0.295f, -0.293f, 0.295f, -0.767f, 0.002f, -1.06f) + close() + moveTo(3.5f, 11.0f) + curveToRelative(0.0f, -4.135f, 3.365f, -7.5f, 7.5f, -7.5f) + reflectiveCurveToRelative(7.5f, 3.365f, 7.5f, 7.5f) + reflectiveCurveToRelative(-3.365f, 7.5f, -7.5f, 7.5f) + reflectiveCurveToRelative(-7.5f, -3.365f, -7.5f, -7.5f) + close() + } + }.build() + .also { labelsSearch = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Share.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Share.kt new file mode 100644 index 0000000000..a4c58913a8 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Share.kt @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(Share, null) +} + +private var labelsShare: ImageVector? = null + +val Share: ImageVector + get() = + labelsShare ?: Builder( + name = "Share", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(17.53f, 7.47f) + lineToRelative(-5.0f, -5.0f) + curveToRelative(-0.293f, -0.293f, -0.768f, -0.293f, -1.06f, 0.0f) + lineToRelative(-5.0f, 5.0f) + curveToRelative(-0.294f, 0.293f, -0.294f, 0.768f, 0.0f, 1.06f) + reflectiveCurveToRelative(0.767f, 0.294f, 1.06f, 0.0f) + lineToRelative(3.72f, -3.72f) + verticalLineTo(15.0f) + curveToRelative(0.0f, 0.414f, 0.336f, 0.75f, 0.75f, 0.75f) + reflectiveCurveToRelative(0.75f, -0.336f, 0.75f, -0.75f) + verticalLineTo(4.81f) + lineToRelative(3.72f, 3.72f) + curveToRelative(0.146f, 0.147f, 0.338f, 0.22f, 0.53f, 0.22f) + reflectiveCurveToRelative(0.384f, -0.072f, 0.53f, -0.22f) + curveToRelative(0.293f, -0.293f, 0.293f, -0.767f, 0.0f, -1.06f) + close() + } + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(19.708f, 21.944f) + horizontalLineTo(4.292f) + curveTo(3.028f, 21.944f, 2.0f, 20.916f, 2.0f, 19.652f) + verticalLineTo(14.0f) + curveToRelative(0.0f, -0.414f, 0.336f, -0.75f, 0.75f, -0.75f) + reflectiveCurveToRelative(0.75f, 0.336f, 0.75f, 0.75f) + verticalLineToRelative(5.652f) + curveToRelative(0.0f, 0.437f, 0.355f, 0.792f, 0.792f, 0.792f) + horizontalLineToRelative(15.416f) + curveToRelative(0.437f, 0.0f, 0.792f, -0.355f, 0.792f, -0.792f) + verticalLineTo(14.0f) + curveToRelative(0.0f, -0.414f, 0.336f, -0.75f, 0.75f, -0.75f) + reflectiveCurveToRelative(0.75f, 0.336f, 0.75f, 0.75f) + verticalLineToRelative(5.652f) + curveToRelative(0.0f, 1.264f, -1.028f, 2.292f, -2.292f, 2.292f) + close() + } + }.build() + .also { labelsShare = it } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Zap.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Zap.kt new file mode 100644 index 0000000000..bd45756ed1 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/Zap.kt @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.DefaultFillType +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.PathBuilder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview + +@Preview +@Composable +private fun VectorPreview() { + Image(Zap, null) +} + +private var zap: ImageVector? = null + +val Zap: ImageVector + get() = + zap ?: materialIcon(name = "Zap") { + materialOutlinedPath { + moveTo(11.0f, 21.0f) + horizontalLineToRelative(-1.0f) + lineToRelative(1.0f, -7.0f) + horizontalLineTo(7.5f) + curveToRelative(-0.58f, 0.0f, -0.57f, -0.32f, -0.38f, -0.66f) + curveToRelative(0.19f, -0.34f, 0.05f, -0.08f, 0.07f, -0.12f) + curveTo(8.48f, 10.94f, 10.42f, 7.54f, 13.0f, 3.0f) + horizontalLineToRelative(1.0f) + lineToRelative(-1.0f, 7.0f) + horizontalLineToRelative(3.5f) + curveToRelative(0.49f, 0.0f, 0.56f, 0.33f, 0.47f, 0.51f) + lineToRelative(-0.07f, 0.15f) + curveTo(12.96f, 17.55f, 11.0f, 21.0f, 11.0f, 21.0f) + close() + } + }.also { zap = it } + +inline fun ImageVector.Builder.materialOutlinedPath( + fillAlpha: Float = 1f, + strokeAlpha: Float = 1f, + pathFillType: PathFillType = DefaultFillType, + pathBuilder: PathBuilder.() -> Unit, +) = path( + fill = null, + fillAlpha = fillAlpha, + stroke = SolidColor(Color.Black), + strokeAlpha = strokeAlpha, + strokeLineWidth = 1.2f, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Bevel, + strokeLineMiter = 1f, + pathFillType = pathFillType, + pathBuilder = pathBuilder, +) diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/ZapSplit.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/ZapSplit.kt new file mode 100644 index 0000000000..94e3dd2322 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/icons/ZapSplit.kt @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.DefaultFillType +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.PathBuilder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +private fun VectorPreview() { + Image(ZapSplit, null) +} + +private var zapSplit: ImageVector? = null + +val ZapSplit: ImageVector + get() = + zapSplit ?: materialIcon(name = "ZapSplit") { + materialPath { + moveTo(7.0f, 21.0f) + horizontalLineToRelative(-1.0f) + lineToRelative(1.0f, -7.0f) + horizontalLineTo(3.5f) + curveToRelative(-0.88f, 0.0f, -0.33f, -0.75f, -0.31f, -0.78f) + curveTo(4.48f, 10.94f, 6.42f, 7.54f, 9.01f, 3.0f) + horizontalLineToRelative(1.0f) + lineToRelative(-1.0f, 7.0f) + horizontalLineToRelative(3.51f) + curveToRelative(0.4f, 0.0f, 0.62f, 0.19f, 0.4f, 0.66f) + curveTo(8.97f, 17.55f, 7.0f, 21.0f, 7.0f, 21.0f) + close() + moveTo(14.59f, 16.59f) + lineTo(19.17f, 12.0f) + lineTo(14.59f, 7.41f) + lineTo(16.0f, 6.0f) + lineToRelative(6.0f, 6.0f) + lineToRelative(-6.0f, 6.0f) + lineToRelative(-1.41f, -1.41f) + close() + } + }.also { zapSplit = it } + +inline fun ImageVector.Builder.materialPath( + fillAlpha: Float = 1f, + strokeAlpha: Float = 1f, + pathFillType: PathFillType = DefaultFillType, + pathBuilder: PathBuilder.() -> Unit, +) = path( + fill = SolidColor(Color.Black), + fillAlpha = fillAlpha, + stroke = null, + strokeAlpha = strokeAlpha, + strokeLineWidth = 1f, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Bevel, + strokeLineMiter = 1f, + pathFillType = pathFillType, + pathBuilder = pathBuilder, +) + +inline fun materialIcon( + name: String, + autoMirror: Boolean = false, + block: ImageVector.Builder.() -> ImageVector.Builder, +): ImageVector = + ImageVector + .Builder( + name = name, + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, + autoMirror = autoMirror, + ).block() + .build() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d9ae527d7..78eae24531 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,11 +11,11 @@ benchmarkJunit4 = "1.2.4" biometricKtx = "1.2.0-alpha05" blurhash = "1.0.0" coil = "2.6.0" -composeBom = "2024.05.00" +composeBom = "2024.06.00" coreKtx = "1.13.1" espressoCore = "3.5.1" firebaseBom = "33.1.0" -fragmentKtx = "1.7.1" +fragmentKtx = "1.8.0" gms = "4.4.1" jacksonModuleKotlin = "2.17.1" jna = "5.14.0" @@ -24,7 +24,7 @@ kotlin = "1.9.24" kotlinxCollectionsImmutable = "0.3.7" languageId = "17.0.5" lazysodiumAndroid = "5.1.0" -lifecycleRuntimeKtx = "2.7.0" +lifecycleRuntimeKtx = "2.8.2" lightcompressor = "1.3.2" markdown = "077a2cde64" media3 = "1.3.1" diff --git a/quartz/build.gradle b/quartz/build.gradle index fb153c6f0b..f775df581b 100644 --- a/quartz/build.gradle +++ b/quartz/build.gradle @@ -57,9 +57,7 @@ dependencies { implementation 'net.java.dev.jna:jna:5.14.0@aar' // Performant Parser of JSONs into Events - api(libs.jackson.module.kotlin) { - exclude(module: 'byte-buddy') // Workaround https://github.com/FasterXML/jackson-databind/issues/4428 until Jackson 2.17.1 - } + api(libs.jackson.module.kotlin) // immutable collections to avoid recomposition api libs.kotlinx.collections.immutable diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index 5051d93863..5dcd4710a3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -89,6 +89,8 @@ open class Event( override fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = forEachTagged("e", onEach) + override fun forEachHashTag(onEach: (eventId: HexKey) -> Unit) = forEachTagged("t", onEach) + private fun forEachTagged( tagName: String, onEach: (eventId: HexKey) -> Unit, @@ -141,7 +143,7 @@ open class Event( override fun isSensitive() = tags.any { - (it.size > 0 && it[0].equals("content-warning")) || + (it.size > 0 && it[0] == "content-warning") || (it.size > 1 && it[0] == "t" && (it[1].equals("nsfw", true) || it[1].equals("nude", true))) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt index 957ff7a997..5465a50f3d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -119,6 +119,8 @@ interface EventInterface { fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) + fun forEachHashTag(onEach: (eventId: HexKey) -> Unit) + fun mapTaggedEvent(map: (eventId: HexKey) -> R): List fun mapTaggedAddress(map: (address: String) -> R): List diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt index 9d4e8950ca..7e98dcd4b8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt @@ -56,9 +56,7 @@ class GiftWrapEvent( override fun isContentEncoded() = true - fun preCachedGift(signer: NostrSigner): Event? { - return cachedInnerEvent[signer.pubKey] - } + fun preCachedGift(signer: NostrSigner): Event? = cachedInnerEvent[signer.pubKey] fun addToCache( pubKey: HexKey, @@ -114,7 +112,7 @@ class GiftWrapEvent( fun create( event: Event, recipientPubKey: HexKey, - createdAt: Long = TimeUtils.randomWithinAWeek(), + createdAt: Long = TimeUtils.randomWithTwoDays(), onReady: (GiftWrapEvent) -> Unit, ) { val signer = NostrSignerInternal(KeyPair()) // GiftWrap is always a random key diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt index b6411f1625..1ca4af9c6d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt @@ -58,9 +58,7 @@ class SealedGossipEvent( override fun isContentEncoded() = true - fun preCachedGossip(signer: NostrSigner): Event? { - return cachedInnerEvent[signer.pubKey] - } + fun preCachedGossip(signer: NostrSigner): Event? = cachedInnerEvent[signer.pubKey] fun addToCache( pubKey: HexKey, @@ -133,7 +131,7 @@ class SealedGossipEvent( gossip: Gossip, encryptTo: HexKey, signer: NostrSigner, - createdAt: Long = TimeUtils.randomWithinAWeek(), + createdAt: Long = TimeUtils.randomWithTwoDays(), onReady: (SealedGossipEvent) -> Unit, ) { val msg = Gossip.toJson(gossip) @@ -172,8 +170,6 @@ class Gossip( fun toJson(event: Gossip): String = Event.mapper.writeValueAsString(event) - fun create(event: Event): Gossip { - return Gossip(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content) - } + fun create(event: Event): Gossip = Gossip(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt index 0682eccfb5..305d487be4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt @@ -22,9 +22,7 @@ package com.vitorpamplona.quartz.utils import kotlin.math.min -fun String.bytesUsedInMemory(): Int { - return (8 * ((((this.length) * 2) + 45) / 8)) -} +fun String.bytesUsedInMemory(): Int = (8 * ((((this.length) * 2) + 45) / 8)) fun String.containsIgnoreCase(term: String): Boolean { if (term.isEmpty()) return true // Empty string is contained @@ -72,4 +70,7 @@ fun String.containsAny(terms: List): Boolean { return terms.any { containsIgnoreCase(it.lowercase, it.uppercase) } } -class DualCase(val lowercase: String, val uppercase: String) +class DualCase( + val lowercase: String, + val uppercase: String, +) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt index 023fc9fb65..2b71a4e606 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt @@ -55,5 +55,7 @@ object TimeUtils { fun oneWeekAgo() = now() - ONE_WEEK - fun randomWithinAWeek() = System.currentTimeMillis() / 1000 - CryptoUtils.randomInt(ONE_WEEK) + fun oneMonthAgo() = now() - ONE_MONTH + + fun randomWithTwoDays() = System.currentTimeMillis() / 1000 - CryptoUtils.randomInt(twoDays()) }