Merge branch 'main' into redesign_emoji_selector

This commit is contained in:
Vitor Pamplona
2024-06-20 17:35:41 -04:00
committed by GitHub
251 changed files with 4160 additions and 2614 deletions
+3 -3
View File
@@ -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 {
@@ -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<String>, 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,
@@ -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<List<AccountInfo>>(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<AccountInfo> {
return savedAccounts()
}
fun allSavedAccounts(): List<AccountInfo> = 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,
@@ -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
}
}
}
@@ -28,13 +28,16 @@ import com.vitorpamplona.quartz.events.ImmutableListOfLists
object CachedRichTextParser {
val richTextCache = LruCache<String, RichTextViewerState>(50)
fun getCached(content: String): RichTextViewerState? = richTextCache[content]
fun parseText(
content: String,
tags: ImmutableListOfLists<String>,
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)
@@ -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<String, GenericLoadable<CashuToken>>(20)
fun cached(token: String): GenericLoadable<CashuToken> {
return cashuCache[token] ?: GenericLoadable.Loading()
}
fun cached(token: String): GenericLoadable<CashuToken> = cashuCache[token] ?: GenericLoadable.Loading()
fun parse(token: String): GenericLoadable<CashuToken> {
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),
)
}
}
@@ -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
@@ -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<User, Boolean>()
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,
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.StatusEvent
object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
var usersToWatch = setOf<User>()
private var usersToWatch = setOf<User>()
fun createUserMetadataFilter(): List<TypedFilter>? {
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 }
}
@@ -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 }
}
}
@@ -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<ZapSplitSetup, SignAllZapRequestsReturn>) -> 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<ZapSplitSetup, SignAllZapRequestsReturn>(
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,
),
@@ -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,
@@ -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
}
}
@@ -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)
@@ -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")
@@ -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
@@ -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
}
}
}
@@ -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<Int, String>(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) }
}
@@ -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 <T> CrossfadeIfEnabled(
targetState: T,
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = 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)
}
}
@@ -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,
)
@@ -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))
}
}
},
@@ -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)
}
@@ -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,
)
},
@@ -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))
},
)
}
@@ -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,
)
},
@@ -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,
)
},
@@ -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,
)
},
@@ -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,
)
},
@@ -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,
)
},
@@ -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,
)
},
@@ -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),
)
@@ -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,
@@ -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,
)
},
@@ -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))
}
}
},
@@ -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,
)
},
@@ -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))
}
}
@@ -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,
@@ -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),
)
}
}
@@ -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),
)
}
}
@@ -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))
}
}
@@ -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,
)
}
@@ -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,
)
},
@@ -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
@@ -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)
}
}
}
@@ -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
@@ -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
@@ -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
@@ -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,
@@ -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)
@@ -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()
}
},
),
@@ -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)
}
}
}
@@ -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
@@ -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,
)
@@ -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,
)
@@ -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,
)
@@ -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)
@@ -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,
)
@@ -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<CashuToken> -> CashuPreview(it.loaded, accountViewModel)
is GenericLoadable.Error<CashuToken> ->
@@ -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,
@@ -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()
}
}
@@ -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 {
@@ -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<String?>(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 },
)
@@ -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)
}
}
@@ -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)
}
}
@@ -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)
}
}
}
@@ -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,
)
@@ -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 -> {
@@ -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() },
@@ -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,
)
}
}
}
@@ -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(
@@ -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,
)
}
@@ -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,
)
@@ -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<MediaItemData, MediaItem>(20) {
override suspend fun compute(key: MediaItemData): MediaItem {
return MediaItem.Builder()
class MediaItemCache : GenericBaseCache<MediaItemData, MediaItem>(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<MediaItemData, MediaItem>(20) {
if (e is CancellationException) throw e
null
},
)
.build(),
)
.build()
}
).build(),
).build()
}
@Composable
@@ -409,7 +408,15 @@ fun GetMediaItem(
authorName: String?,
inner: @Composable (State<MediaItem>) -> 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<VisibilityData>()
@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<Boolean>, () -> 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<Boolean>, () -> 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<Boolean>, () -> 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),
)
}
}
@@ -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) {
@@ -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),
)
}
}
@@ -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<BaseMediaContent> = remember(content) { listOf(content).toImmutableList() },
images: ImmutableList<BaseMediaContent> = 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)
@@ -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,
)
@@ -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),
)
@@ -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(
@@ -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,
)
}
@@ -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<NavBackStackEntry?>,
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,
)
@@ -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<NavBackStackEntry?>,
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,
)
}
@@ -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() }
@@ -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<String, Note?> = 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<Note>,
account: Account,
): Set<Note> {
return newItems
}
): Set<Note> = newItems
open fun filterMore(
newItems: List<Note>,
account: Account,
): List<Note> {
return newItems
}
): List<Note> = newItems
}
object HomeLatestItem : LatestItem() {
@@ -382,16 +374,12 @@ object MessagesLatestItem : LatestItem() {
override fun filterMore(
newItems: Set<Note>,
account: Account,
): Set<Note> {
return newItems.filter { isNew(it, account) }.toSet()
}
): Set<Note> = newItems.filter { isNew(it, account) }.toSet()
override fun filterMore(
newItems: List<Note>,
account: Account,
): List<Note> {
return newItems.filter { isNew(it, account) }
}
): List<Note> = 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<NavBackStackEntry?>): String? {
return navState.value?.let { getRouteWithArguments(it.destination, it.arguments) }
}
fun getRouteWithArguments(navState: State<NavBackStackEntry?>): String? = navState.value?.let { getRouteWithArguments(it.destination, it.arguments) }
private fun getRouteWithArguments(
destination: NavDestination,
@@ -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 {
@@ -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)
}
}
}
@@ -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
@@ -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
@@ -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<Boolean>(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<Boolean>,
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,
@@ -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<Color>? = 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) {
@@ -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
@@ -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)
}
}
}
}
@@ -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)
}
}
// }
}
}
@@ -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<Boolean?>,
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<Boolean?>,
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) =
@@ -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,
)
}
}
@@ -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,
@@ -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,
)
@@ -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 <T, K, P, R> LiveData<T>.combineWith(
}
@Composable
private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableState<Boolean>) {
IconButton(
private fun RenderShowIndividualReactionsButton(
wantsToSeeReactions: MutableState<Boolean>,
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<Int>(
targetState = baseCount,
transitionSpec = AnimatedContentTransitionScope<Int>::transitionSpec,
label = "SlidingAnimationCount",
) { count ->
TextCount(count, textColor)
if (accountViewModel.settings.featureSet == FeatureSetType.PERFORMANCE) {
TextCount(baseCount, textColor)
} else {
AnimatedContent<Int>(
targetState = baseCount,
transitionSpec = AnimatedContentTransitionScope<Int>::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<String>::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<String>::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,
@@ -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"))
}
@@ -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,
)
}
@@ -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,
)
}

Some files were not shown because too many files have changed in this diff Show More