- Moves app startup to an IO thread.
- Upgrades Shared Preferences serialization to a Single JSON object - Simplifies Shared Preferences state changes - LazyInitializes Video cache
This commit is contained in:
@@ -4,16 +4,26 @@ import android.app.Application
|
|||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
import android.os.StrictMode.VmPolicy
|
import android.os.StrictMode.VmPolicy
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import coil.ImageLoader
|
||||||
import com.vitorpamplona.amethyst.service.playback.VideoCache
|
import com.vitorpamplona.amethyst.service.playback.VideoCache
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
|
@UnstableApi class Amethyst : Application() {
|
||||||
|
val videoCache: VideoCache by lazy {
|
||||||
|
val newCache = VideoCache()
|
||||||
|
newCache.initFileCache(instance)
|
||||||
|
newCache
|
||||||
|
}
|
||||||
|
|
||||||
class Amethyst : Application() {
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
VideoCache.initFileCache(instance)
|
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
StrictMode.setThreadPolicy(
|
StrictMode.setThreadPolicy(
|
||||||
ThreadPolicy.Builder()
|
ThreadPolicy.Builder()
|
||||||
@@ -34,6 +44,20 @@ class Amethyst : Application() {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val (value, elapsed) = measureTimedValue {
|
||||||
|
// initializes the video cache in a thread
|
||||||
|
videoCache
|
||||||
|
}
|
||||||
|
Log.d("Rendering Metrics", "VideoCache initialized in $elapsed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun imageLoaderBuilder(): ImageLoader.Builder {
|
||||||
|
return ImageLoader.Builder(applicationContext).diskCache {
|
||||||
|
SingletonDiskCache.get(applicationContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.security.crypto.MasterKey
|
|||||||
object EncryptedStorage {
|
object EncryptedStorage {
|
||||||
private const val PREFERENCES_NAME = "secret_keeper"
|
private const val PREFERENCES_NAME = "secret_keeper"
|
||||||
|
|
||||||
|
// returns the preferences for each account or a global file if null.
|
||||||
fun prefsFileName(npub: String? = null): String {
|
fun prefsFileName(npub: String? = null): String {
|
||||||
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,20 @@ import com.vitorpamplona.amethyst.model.Nip47URI
|
|||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
import com.vitorpamplona.amethyst.model.ServersAvailable
|
import com.vitorpamplona.amethyst.model.ServersAvailable
|
||||||
import com.vitorpamplona.amethyst.model.Settings
|
import com.vitorpamplona.amethyst.model.Settings
|
||||||
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
import com.vitorpamplona.amethyst.model.parseBooleanType
|
import com.vitorpamplona.amethyst.model.parseBooleanType
|
||||||
import com.vitorpamplona.amethyst.model.parseConnectivityType
|
import com.vitorpamplona.amethyst.model.parseConnectivityType
|
||||||
|
import com.vitorpamplona.amethyst.model.parseThemeType
|
||||||
import com.vitorpamplona.amethyst.service.HttpClient
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -81,22 +86,25 @@ private object PrefKeys {
|
|||||||
const val AUTOMATICALLY_HIDE_NAV_BARS = "automatically_hide_nav_bars"
|
const val AUTOMATICALLY_HIDE_NAV_BARS = "automatically_hide_nav_bars"
|
||||||
const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer"
|
const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer"
|
||||||
const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture"
|
const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture"
|
||||||
|
|
||||||
|
const val ALL_ACCOUNT_INFO = "all_saved_accounts_info"
|
||||||
|
const val SHARED_SETTINGS = "shared_settings"
|
||||||
}
|
}
|
||||||
|
|
||||||
object LocalPreferences {
|
object LocalPreferences {
|
||||||
private const val comma = ","
|
private const val comma = ","
|
||||||
|
|
||||||
private var _currentAccount: String? = null
|
private var _currentAccount: String? = null
|
||||||
private var _savedAccounts: List<String>? = null
|
private var _savedAccounts: List<AccountInfo>? = null
|
||||||
|
|
||||||
fun currentAccount(): String? {
|
suspend fun currentAccount(): String? {
|
||||||
if (_currentAccount == null) {
|
if (_currentAccount == null) {
|
||||||
_currentAccount = encryptedPreferences().getString(PrefKeys.CURRENT_ACCOUNT, null)
|
_currentAccount = encryptedPreferences().getString(PrefKeys.CURRENT_ACCOUNT, null)
|
||||||
}
|
}
|
||||||
return _currentAccount
|
return _currentAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCurrentAccount(npub: String) {
|
private suspend fun updateCurrentAccount(npub: String) {
|
||||||
if (_currentAccount != npub) {
|
if (_currentAccount != npub) {
|
||||||
_currentAccount = npub
|
_currentAccount = npub
|
||||||
|
|
||||||
@@ -106,20 +114,41 @@ object LocalPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun savedAccounts(): List<String> {
|
private fun savedAccounts(): List<AccountInfo> {
|
||||||
if (_savedAccounts == null) {
|
if (_savedAccounts == null) {
|
||||||
_savedAccounts = encryptedPreferences()
|
with(encryptedPreferences()) {
|
||||||
.getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(comma) ?: listOf()
|
val newSystemOfAccounts = getString(PrefKeys.ALL_ACCOUNT_INFO, "[]")?.let {
|
||||||
|
Event.mapper.readValue<List<AccountInfo>>(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSystemOfAccounts != null && newSystemOfAccounts.isNotEmpty()) {
|
||||||
|
_savedAccounts = newSystemOfAccounts
|
||||||
|
} else {
|
||||||
|
val oldAccounts = getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(comma) ?: listOf()
|
||||||
|
|
||||||
|
val migrated = oldAccounts.map { npub ->
|
||||||
|
AccountInfo(
|
||||||
|
npub,
|
||||||
|
encryptedPreferences(npub).getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false),
|
||||||
|
(encryptedPreferences(npub).getString(PrefKeys.NOSTR_PRIVKEY, "") ?: "").isNotBlank()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("AAA migrated: $migrated")
|
||||||
|
|
||||||
|
_savedAccounts = migrated
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _savedAccounts!!
|
return _savedAccounts!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSavedAccounts(accounts: List<String>) {
|
private suspend fun updateSavedAccounts(accounts: List<AccountInfo>) = withContext(Dispatchers.IO) {
|
||||||
if (_savedAccounts != accounts) {
|
if (_savedAccounts != accounts) {
|
||||||
_savedAccounts = accounts
|
_savedAccounts = accounts
|
||||||
|
|
||||||
encryptedPreferences().edit().apply {
|
encryptedPreferences().edit().apply {
|
||||||
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
|
putString(PrefKeys.ALL_ACCOUNT_INFO, Event.mapper.writeValueAsString(accounts))
|
||||||
}.apply()
|
}.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +156,7 @@ object LocalPreferences {
|
|||||||
private val prefsDirPath: String
|
private val prefsDirPath: String
|
||||||
get() = "${Amethyst.instance.filesDir.parent}/shared_prefs/"
|
get() = "${Amethyst.instance.filesDir.parent}/shared_prefs/"
|
||||||
|
|
||||||
private fun addAccount(npub: String) {
|
private suspend fun addAccount(npub: AccountInfo) {
|
||||||
val accounts = savedAccounts().toMutableList()
|
val accounts = savedAccounts().toMutableList()
|
||||||
if (npub !in accounts) {
|
if (npub !in accounts) {
|
||||||
accounts.add(npub)
|
accounts.add(npub)
|
||||||
@@ -135,22 +164,27 @@ object LocalPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCurrentAccount(account: Account) {
|
private suspend fun setCurrentAccount(account: Account) = withContext(Dispatchers.IO) {
|
||||||
val npub = account.userProfile().pubkeyNpub()
|
val npub = account.userProfile().pubkeyNpub()
|
||||||
|
val accInfo = AccountInfo(
|
||||||
|
npub,
|
||||||
|
account.isWriteable(),
|
||||||
|
account.loginWithExternalSigner
|
||||||
|
)
|
||||||
updateCurrentAccount(npub)
|
updateCurrentAccount(npub)
|
||||||
addAccount(npub)
|
addAccount(accInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToAccount(npub: String) {
|
suspend fun switchToAccount(accountInfo: AccountInfo) = withContext(Dispatchers.IO) {
|
||||||
updateCurrentAccount(npub)
|
updateCurrentAccount(accountInfo.npub)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the account from the app level shared preferences
|
* Removes the account from the app level shared preferences
|
||||||
*/
|
*/
|
||||||
private fun removeAccount(npub: String) {
|
private suspend fun removeAccount(accountInfo: AccountInfo) {
|
||||||
val accounts = savedAccounts().toMutableList()
|
val accounts = savedAccounts().toMutableList()
|
||||||
if (accounts.remove(npub)) {
|
if (accounts.remove(accountInfo)) {
|
||||||
updateSavedAccounts(accounts)
|
updateSavedAccounts(accounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +193,8 @@ object LocalPreferences {
|
|||||||
* Deletes the npub-specific shared preference file
|
* Deletes the npub-specific shared preference file
|
||||||
*/
|
*/
|
||||||
private fun deleteUserPreferenceFile(npub: String) {
|
private fun deleteUserPreferenceFile(npub: String) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
val prefsDir = File(prefsDirPath)
|
val prefsDir = File(prefsDirPath)
|
||||||
prefsDir.list()?.forEach {
|
prefsDir.list()?.forEach {
|
||||||
if (it.contains(npub)) {
|
if (it.contains(npub)) {
|
||||||
@@ -168,6 +204,8 @@ object LocalPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun encryptedPreferences(npub: String? = null): SharedPreferences {
|
private fun encryptedPreferences(npub: String? = null): SharedPreferences {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) {
|
return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) {
|
||||||
val preferenceFile = if (npub == null) DEBUG_PREFERENCES_NAME else "${DEBUG_PREFERENCES_NAME}_$npub"
|
val preferenceFile = if (npub == null) DEBUG_PREFERENCES_NAME else "${DEBUG_PREFERENCES_NAME}_$npub"
|
||||||
Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE)
|
Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE)
|
||||||
@@ -185,40 +223,31 @@ object LocalPreferences {
|
|||||||
* condition and the file will probably not be deleted
|
* condition and the file will probably not be deleted
|
||||||
*/
|
*/
|
||||||
@SuppressLint("ApplySharedPref")
|
@SuppressLint("ApplySharedPref")
|
||||||
fun updatePrefsForLogout(npub: String) {
|
suspend fun updatePrefsForLogout(accountInfo: AccountInfo) = withContext(Dispatchers.IO) {
|
||||||
val userPrefs = encryptedPreferences(npub)
|
val userPrefs = encryptedPreferences(accountInfo.npub)
|
||||||
userPrefs.edit().clear().commit()
|
userPrefs.edit().clear().commit()
|
||||||
removeAccount(npub)
|
removeAccount(accountInfo)
|
||||||
deleteUserPreferenceFile(npub)
|
deleteUserPreferenceFile(accountInfo.npub)
|
||||||
|
|
||||||
if (savedAccounts().isEmpty()) {
|
if (savedAccounts().isEmpty()) {
|
||||||
val appPrefs = encryptedPreferences()
|
encryptedPreferences().edit().clear().apply()
|
||||||
appPrefs.edit().clear().apply()
|
} else if (currentAccount() == accountInfo.npub) {
|
||||||
} else if (currentAccount() == npub) {
|
updateCurrentAccount(savedAccounts().elementAt(0).npub)
|
||||||
updateCurrentAccount(savedAccounts().elementAt(0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePrefsForLogin(account: Account) {
|
suspend fun updatePrefsForLogin(account: Account) {
|
||||||
setCurrentAccount(account)
|
setCurrentAccount(account)
|
||||||
saveToEncryptedStorage(account)
|
saveToEncryptedStorage(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allSavedAccounts(): List<AccountInfo> {
|
fun allSavedAccounts(): List<AccountInfo> {
|
||||||
return savedAccounts().map { npub ->
|
return savedAccounts()
|
||||||
AccountInfo(
|
|
||||||
npub,
|
|
||||||
hasPrivKey(npub),
|
|
||||||
getLoggedInWithExternalSigner(npub)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allLocalAccountNPubs(): Set<String> {
|
suspend fun saveToEncryptedStorage(account: Account) = withContext(Dispatchers.IO) {
|
||||||
return savedAccounts().toSet()
|
checkNotInMainThread()
|
||||||
}
|
|
||||||
|
|
||||||
fun saveToEncryptedStorage(account: Account) {
|
|
||||||
val prefs = encryptedPreferences(account.userProfile().pubkeyNpub())
|
val prefs = encryptedPreferences(account.userProfile().pubkeyNpub())
|
||||||
prefs.edit().apply {
|
prefs.edit().apply {
|
||||||
account.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
|
account.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
|
||||||
@@ -256,71 +285,102 @@ object LocalPreferences {
|
|||||||
}
|
}
|
||||||
putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, account.loginWithExternalSigner)
|
putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, account.loginWithExternalSigner)
|
||||||
}.apply()
|
}.apply()
|
||||||
|
|
||||||
val globalPrefs = encryptedPreferences()
|
|
||||||
globalPrefs.edit().apply {
|
|
||||||
if (account.settings.automaticallyShowImages.prefCode == null) {
|
|
||||||
remove(PrefKeys.AUTOMATICALLY_SHOW_IMAGES)
|
|
||||||
} else {
|
|
||||||
putBoolean(PrefKeys.AUTOMATICALLY_SHOW_IMAGES, account.settings.automaticallyShowImages.prefCode!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.settings.automaticallyStartPlayback.prefCode == null) {
|
suspend fun loadCurrentAccountFromEncryptedStorage(): Account? {
|
||||||
remove(PrefKeys.AUTOMATICALLY_START_PLAYBACK)
|
val acc = loadCurrentAccountFromEncryptedStorage(currentAccount())
|
||||||
} else {
|
|
||||||
putBoolean(PrefKeys.AUTOMATICALLY_START_PLAYBACK, account.settings.automaticallyStartPlayback.prefCode!!)
|
|
||||||
}
|
|
||||||
if (account.settings.automaticallyShowUrlPreview.prefCode == null) {
|
|
||||||
remove(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW)
|
|
||||||
} else {
|
|
||||||
putBoolean(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW, account.settings.automaticallyShowUrlPreview.prefCode!!)
|
|
||||||
}
|
|
||||||
if (account.settings.automaticallyHideNavigationBars.prefCode == null) {
|
|
||||||
remove(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS)
|
|
||||||
} else {
|
|
||||||
putBoolean(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS, account.settings.automaticallyHideNavigationBars.prefCode!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.settings.automaticallyShowProfilePictures.prefCode == null) {
|
|
||||||
remove(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE)
|
|
||||||
} else {
|
|
||||||
putBoolean(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE, account.settings.automaticallyShowProfilePictures.prefCode!!)
|
|
||||||
}
|
|
||||||
putString(PrefKeys.PREFERRED_LANGUAGE, account.settings.preferredLanguage ?: "")
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTheme(theme: Int) {
|
|
||||||
encryptedPreferences().edit().apply {
|
|
||||||
putInt(PrefKeys.THEME, theme)
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTheme(): Int {
|
|
||||||
return encryptedPreferences().getInt(PrefKeys.THEME, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPreferredLanguage(): String {
|
|
||||||
return encryptedPreferences().getString(PrefKeys.PREFERRED_LANGUAGE, "") ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLoggedInWithExternalSigner(npub: String): Boolean {
|
|
||||||
return encryptedPreferences(npub).getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasPrivKey(npub: String): Boolean {
|
|
||||||
return (encryptedPreferences(npub).getString(PrefKeys.NOSTR_PRIVKEY, "") ?: "").isNotBlank()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadFromEncryptedStorage(): Account? {
|
|
||||||
val acc = loadFromEncryptedStorage(currentAccount())
|
|
||||||
acc?.registerObservers()
|
acc?.registerObservers()
|
||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadFromEncryptedStorage(npub: String?): Account? {
|
suspend fun migrateOldSharedSettings(): Settings? {
|
||||||
encryptedPreferences(npub).apply {
|
val prefs = encryptedPreferences()
|
||||||
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null
|
loadOldSharedSettings(prefs)?.let {
|
||||||
|
saveSharedSettings(it, prefs)
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveSharedSettings(sharedSettings: Settings, prefs: SharedPreferences = encryptedPreferences()) {
|
||||||
|
with(prefs.edit()) {
|
||||||
|
putString(PrefKeys.SHARED_SETTINGS, Event.mapper.writeValueAsString(sharedSettings))
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadSharedSettings(prefs: SharedPreferences = encryptedPreferences()): Settings? {
|
||||||
|
with(prefs) {
|
||||||
|
return try {
|
||||||
|
getString(PrefKeys.SHARED_SETTINGS, "{}")?.let {
|
||||||
|
Event.mapper.readValue<Settings>(it)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.w("LocalPreferences", "Unable to decode shared preferences: ${getString(PrefKeys.SHARED_SETTINGS, null)}", e)
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Turned into a single JSON object")
|
||||||
|
suspend fun loadOldSharedSettings(prefs: SharedPreferences = encryptedPreferences()): Settings? {
|
||||||
|
with(prefs) {
|
||||||
|
if (!contains(PrefKeys.AUTOMATICALLY_START_PLAYBACK)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val automaticallyShowImages = if (contains(PrefKeys.AUTOMATICALLY_SHOW_IMAGES)) {
|
||||||
|
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_SHOW_IMAGES, false))
|
||||||
|
} else {
|
||||||
|
ConnectivityType.ALWAYS
|
||||||
|
}
|
||||||
|
|
||||||
|
val automaticallyStartPlayback = if (contains(PrefKeys.AUTOMATICALLY_START_PLAYBACK)) {
|
||||||
|
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_START_PLAYBACK, false))
|
||||||
|
} else {
|
||||||
|
ConnectivityType.ALWAYS
|
||||||
|
}
|
||||||
|
val automaticallyShowUrlPreview = if (contains(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW)) {
|
||||||
|
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW, false))
|
||||||
|
} else {
|
||||||
|
ConnectivityType.ALWAYS
|
||||||
|
}
|
||||||
|
val automaticallyHideNavigationBars = if (contains(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS)) {
|
||||||
|
parseBooleanType(getBoolean(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS, false))
|
||||||
|
} else {
|
||||||
|
BooleanType.ALWAYS
|
||||||
|
}
|
||||||
|
|
||||||
|
val automaticallyShowProfilePictures = if (contains(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE)) {
|
||||||
|
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE, false))
|
||||||
|
} else {
|
||||||
|
ConnectivityType.ALWAYS
|
||||||
|
}
|
||||||
|
|
||||||
|
val themeType = if (contains(PrefKeys.THEME)) {
|
||||||
|
parseThemeType(getInt(PrefKeys.THEME, ThemeType.SYSTEM.screenCode))
|
||||||
|
} else {
|
||||||
|
ThemeType.SYSTEM
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings(
|
||||||
|
themeType,
|
||||||
|
getString(PrefKeys.PREFERRED_LANGUAGE, null)?.ifBlank { null },
|
||||||
|
automaticallyShowImages,
|
||||||
|
automaticallyStartPlayback,
|
||||||
|
automaticallyShowUrlPreview,
|
||||||
|
automaticallyHideNavigationBars,
|
||||||
|
automaticallyShowProfilePictures
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadCurrentAccountFromEncryptedStorage(npub: String?): Account? = withContext(Dispatchers.IO) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
return@withContext with(encryptedPreferences(npub)) {
|
||||||
|
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return@with null
|
||||||
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
||||||
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
||||||
val followingCommunities = getStringSet(PrefKeys.FOLLOWING_COMMUNITIES, null) ?: setOf()
|
val followingCommunities = getStringSet(PrefKeys.FOLLOWING_COMMUNITIES, null) ?: setOf()
|
||||||
@@ -413,40 +473,7 @@ object LocalPreferences {
|
|||||||
mapOf()
|
mapOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
val settings = Settings()
|
return@with Account(
|
||||||
encryptedPreferences().apply {
|
|
||||||
settings.automaticallyShowImages = if (contains(PrefKeys.AUTOMATICALLY_SHOW_IMAGES)) {
|
|
||||||
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_SHOW_IMAGES, false))
|
|
||||||
} else {
|
|
||||||
ConnectivityType.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.automaticallyStartPlayback = if (contains(PrefKeys.AUTOMATICALLY_START_PLAYBACK)) {
|
|
||||||
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_START_PLAYBACK, false))
|
|
||||||
} else {
|
|
||||||
ConnectivityType.ALWAYS
|
|
||||||
}
|
|
||||||
settings.automaticallyShowUrlPreview = if (contains(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW)) {
|
|
||||||
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_LOAD_URL_PREVIEW, false))
|
|
||||||
} else {
|
|
||||||
ConnectivityType.ALWAYS
|
|
||||||
}
|
|
||||||
settings.automaticallyHideNavigationBars = if (contains(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS)) {
|
|
||||||
parseBooleanType(getBoolean(PrefKeys.AUTOMATICALLY_HIDE_NAV_BARS, false))
|
|
||||||
} else {
|
|
||||||
BooleanType.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.automaticallyShowProfilePictures = if (contains(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE)) {
|
|
||||||
parseConnectivityType(getBoolean(PrefKeys.AUTOMATICALLY_SHOW_PROFILE_PICTURE, false))
|
|
||||||
} else {
|
|
||||||
ConnectivityType.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.preferredLanguage = getString(PrefKeys.PREFERRED_LANGUAGE, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
val a = Account(
|
|
||||||
keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||||
followingChannels = followingChannels,
|
followingChannels = followingChannels,
|
||||||
followingCommunities = followingCommunities,
|
followingCommunities = followingCommunities,
|
||||||
@@ -474,11 +501,8 @@ object LocalPreferences {
|
|||||||
warnAboutPostsWithReports = warnAboutReports,
|
warnAboutPostsWithReports = warnAboutReports,
|
||||||
filterSpamFromStrangers = filterSpam,
|
filterSpamFromStrangers = filterSpam,
|
||||||
lastReadPerRoute = lastReadPerRoute,
|
lastReadPerRoute = lastReadPerRoute,
|
||||||
settings = settings,
|
|
||||||
loginWithExternalSigner = loginWithExternalSigner
|
loginWithExternalSigner = loginWithExternalSigner
|
||||||
)
|
)
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.decode.GifDecoder
|
import coil.decode.GifDecoder
|
||||||
import coil.decode.ImageDecoderDecoder
|
import coil.decode.ImageDecoderDecoder
|
||||||
import coil.decode.SvgDecoder
|
import coil.decode.SvgDecoder
|
||||||
@@ -44,14 +43,14 @@ object ServiceManager {
|
|||||||
private var isStarted: Boolean = false // to not open amber in a loop trying to use auth relays and registering for notifications
|
private var isStarted: Boolean = false // to not open amber in a loop trying to use auth relays and registering for notifications
|
||||||
private var account: Account? = null
|
private var account: Account? = null
|
||||||
|
|
||||||
fun start(account: Account, context: Context) {
|
fun start(account: Account) {
|
||||||
this.account = account
|
this.account = account
|
||||||
ExternalSignerUtils.account = account
|
ExternalSignerUtils.account = account
|
||||||
start(context)
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun start(context: Context) {
|
fun start() {
|
||||||
if (isStarted && account != null) {
|
if (isStarted && account != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,7 +62,7 @@ object ServiceManager {
|
|||||||
HttpClient.start(account)
|
HttpClient.start(account)
|
||||||
OptOutFromFilters.start(account?.warnAboutPostsWithReports ?: true, account?.filterSpamFromStrangers ?: true)
|
OptOutFromFilters.start(account?.warnAboutPostsWithReports ?: true, account?.filterSpamFromStrangers ?: true)
|
||||||
Coil.setImageLoader {
|
Coil.setImageLoader {
|
||||||
ImageLoader.Builder(context).components {
|
Amethyst.instance.imageLoaderBuilder().components {
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
add(ImageDecoderDecoder.Factory())
|
add(ImageDecoderDecoder.Factory())
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +70,6 @@ object ServiceManager {
|
|||||||
}
|
}
|
||||||
add(SvgDecoder.Factory())
|
add(SvgDecoder.Factory())
|
||||||
}.logger(DebugLogger())
|
}.logger(DebugLogger())
|
||||||
.diskCache { SingletonDiskCache.get(context.applicationContext) }
|
|
||||||
.okHttpClient { HttpClient.getHttpClient() }
|
.okHttpClient { HttpClient.getHttpClient() }
|
||||||
.respectCacheHeaders(false)
|
.respectCacheHeaders(false)
|
||||||
.build()
|
.build()
|
||||||
@@ -138,7 +136,9 @@ object ServiceManager {
|
|||||||
fun trimMemory() {
|
fun trimMemory() {
|
||||||
LocalCache.cleanObservers()
|
LocalCache.cleanObservers()
|
||||||
|
|
||||||
val accounts = LocalPreferences.allLocalAccountNPubs().mapNotNull { decodePublicKeyAsHexOrNull(it) }.toSet()
|
val accounts = LocalPreferences.allSavedAccounts().mapNotNull {
|
||||||
|
decodePublicKeyAsHexOrNull(it.npub)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
account?.let {
|
account?.let {
|
||||||
LocalCache.pruneOldAndHiddenMessages(it)
|
LocalCache.pruneOldAndHiddenMessages(it)
|
||||||
@@ -152,10 +152,10 @@ object ServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restartIfDifferentAccount(account: Account, context: Context) {
|
fun restartIfDifferentAccount(account: Account) {
|
||||||
if (this.account != account) {
|
if (this.account != account) {
|
||||||
pause()
|
pause()
|
||||||
start(account, context)
|
start(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -130,7 +131,6 @@ class Account(
|
|||||||
var warnAboutPostsWithReports: Boolean = true,
|
var warnAboutPostsWithReports: Boolean = true,
|
||||||
var filterSpamFromStrangers: Boolean = true,
|
var filterSpamFromStrangers: Boolean = true,
|
||||||
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>(),
|
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>(),
|
||||||
var settings: Settings = Settings(),
|
|
||||||
var loginWithExternalSigner: Boolean = false
|
var loginWithExternalSigner: Boolean = false
|
||||||
) {
|
) {
|
||||||
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
||||||
@@ -208,46 +208,6 @@ class Account(
|
|||||||
|
|
||||||
var userProfileCache: User? = null
|
var userProfileCache: User? = null
|
||||||
|
|
||||||
fun updateAutomaticallyStartPlayback(
|
|
||||||
automaticallyStartPlayback: ConnectivityType
|
|
||||||
) {
|
|
||||||
settings.automaticallyStartPlayback = automaticallyStartPlayback
|
|
||||||
live.invalidateData()
|
|
||||||
saveable.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowUrlPreview(
|
|
||||||
automaticallyShowUrlPreview: ConnectivityType
|
|
||||||
) {
|
|
||||||
settings.automaticallyShowUrlPreview = automaticallyShowUrlPreview
|
|
||||||
live.invalidateData()
|
|
||||||
saveable.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowProfilePicture(
|
|
||||||
automaticallyShowProfilePicture: ConnectivityType
|
|
||||||
) {
|
|
||||||
settings.automaticallyShowProfilePictures = automaticallyShowProfilePicture
|
|
||||||
live.invalidateData()
|
|
||||||
saveable.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyHideHavBars(
|
|
||||||
automaticallyHideHavBars: BooleanType
|
|
||||||
) {
|
|
||||||
settings.automaticallyHideNavigationBars = automaticallyHideHavBars
|
|
||||||
live.invalidateData()
|
|
||||||
saveable.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowImages(
|
|
||||||
automaticallyShowImages: ConnectivityType
|
|
||||||
) {
|
|
||||||
settings.automaticallyShowImages = automaticallyShowImages
|
|
||||||
live.invalidateData()
|
|
||||||
saveable.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateOptOutOptions(warnReports: Boolean, filterSpam: Boolean) {
|
fun updateOptOutOptions(warnReports: Boolean, filterSpam: Boolean) {
|
||||||
warnAboutPostsWithReports = warnReports
|
warnAboutPostsWithReports = warnReports
|
||||||
filterSpamFromStrangers = filterSpam
|
filterSpamFromStrangers = filterSpam
|
||||||
@@ -3178,7 +3138,7 @@ class Account(
|
|||||||
return lastReadPerRoute[route] ?: 0
|
return lastReadPerRoute[route] ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerObservers() {
|
suspend fun registerObservers() = withContext(Dispatchers.Main) {
|
||||||
// Observes relays to restart connections
|
// Observes relays to restart connections
|
||||||
userProfile().live().relays.observeForever {
|
userProfile().live().relays.observeForever {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
|
||||||
import com.vitorpamplona.amethyst.service.ExternalSignerUtils
|
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledInsert
|
import com.vitorpamplona.amethyst.ui.components.BundledInsert
|
||||||
@@ -13,7 +11,6 @@ import com.vitorpamplona.quartz.encoders.Hex
|
|||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.encoders.HexValidator
|
import com.vitorpamplona.quartz.encoders.HexValidator
|
||||||
import com.vitorpamplona.quartz.encoders.Nip19
|
import com.vitorpamplona.quartz.encoders.Nip19
|
||||||
import com.vitorpamplona.quartz.encoders.bechToBytes
|
|
||||||
import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull
|
import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull
|
||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||||
@@ -286,14 +283,6 @@ object LocalCache {
|
|||||||
val user = getOrCreateUser(event.pubKey)
|
val user = getOrCreateUser(event.pubKey)
|
||||||
if (user.latestBookmarkList == null || event.createdAt > user.latestBookmarkList!!.createdAt) {
|
if (user.latestBookmarkList == null || event.createdAt > user.latestBookmarkList!!.createdAt) {
|
||||||
if (event.dTag() == "bookmark") {
|
if (event.dTag() == "bookmark") {
|
||||||
val loggedInUser = LocalPreferences.currentAccount()
|
|
||||||
val hexKey = loggedInUser?.bechToBytes()
|
|
||||||
if (hexKey != null) {
|
|
||||||
val pubKey = Hex.encode(hexKey)
|
|
||||||
if (pubKey == event.pubKey) {
|
|
||||||
ExternalSignerUtils.content.remove(event.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user.updateBookmark(event)
|
user.updateBookmark(event)
|
||||||
}
|
}
|
||||||
// Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}")
|
// Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}")
|
||||||
|
|||||||
@@ -4,16 +4,34 @@ import androidx.compose.runtime.Stable
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class Settings(
|
data class Settings(
|
||||||
var preferredLanguage: String? = null,
|
val theme: ThemeType = ThemeType.SYSTEM,
|
||||||
var automaticallyShowImages: ConnectivityType = ConnectivityType.ALWAYS,
|
val preferredLanguage: String? = null,
|
||||||
var automaticallyStartPlayback: ConnectivityType = ConnectivityType.ALWAYS,
|
val automaticallyShowImages: ConnectivityType = ConnectivityType.ALWAYS,
|
||||||
var automaticallyShowUrlPreview: ConnectivityType = ConnectivityType.ALWAYS,
|
val automaticallyStartPlayback: ConnectivityType = ConnectivityType.ALWAYS,
|
||||||
var automaticallyHideNavigationBars: BooleanType = BooleanType.ALWAYS,
|
val automaticallyShowUrlPreview: ConnectivityType = ConnectivityType.ALWAYS,
|
||||||
var automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS
|
val automaticallyHideNavigationBars: BooleanType = BooleanType.ALWAYS,
|
||||||
|
val automaticallyShowProfilePictures: ConnectivityType = ConnectivityType.ALWAYS
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ConnectivityType(val prefCode: Boolean?, val screenCode: Int, val reourceId: 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) {
|
||||||
|
ThemeType.SYSTEM.screenCode -> ThemeType.SYSTEM
|
||||||
|
ThemeType.LIGHT.screenCode -> ThemeType.LIGHT
|
||||||
|
ThemeType.DARK.screenCode -> ThemeType.DARK
|
||||||
|
else -> {
|
||||||
|
ThemeType.SYSTEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ConnectivityType(val prefCode: Boolean?, val screenCode: Int, val resourceId: Int) {
|
||||||
ALWAYS(null, 0, R.string.connectivity_type_always),
|
ALWAYS(null, 0, R.string.connectivity_type_always),
|
||||||
WIFI_ONLY(true, 1, R.string.connectivity_type_wifi_only),
|
WIFI_ONLY(true, 1, R.string.connectivity_type_wifi_only),
|
||||||
NEVER(false, 2, R.string.connectivity_type_never)
|
NEVER(false, 2, R.string.connectivity_type_never)
|
||||||
|
|||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
package com.vitorpamplona.amethyst.service.connectivitystatus
|
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
|
|
||||||
object ConnectivityStatus {
|
|
||||||
private val onMobileData = mutableStateOf(false)
|
|
||||||
val isOnMobileData: MutableState<Boolean> = onMobileData
|
|
||||||
|
|
||||||
fun updateConnectivityStatus(isOnMobileData: Boolean, isOnWifi: Boolean) {
|
|
||||||
if (onMobileData.value != isOnMobileData) {
|
|
||||||
onMobileData.value = isOnMobileData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-5
@@ -33,12 +33,13 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
|||||||
// PushNotification Wraps don't include a receiver.
|
// PushNotification Wraps don't include a receiver.
|
||||||
// Test with all logged in accounts
|
// Test with all logged in accounts
|
||||||
LocalPreferences.allSavedAccounts().forEach {
|
LocalPreferences.allSavedAccounts().forEach {
|
||||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
if (it.hasPrivKey || it.loggedInWithExternalSigner) {
|
||||||
if (acc != null && (acc.keyPair.privKey != null || acc.loginWithExternalSigner)) {
|
LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)?.let { acc ->
|
||||||
consumeIfMatchesAccount(event, acc)
|
consumeIfMatchesAccount(event, acc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun consumeIfMatchesAccount(pushWrappedEvent: GiftWrapEvent, account: Account) {
|
private suspend fun consumeIfMatchesAccount(pushWrappedEvent: GiftWrapEvent, account: Account) {
|
||||||
val key = account.keyPair.privKey
|
val key = account.keyPair.privKey
|
||||||
@@ -72,9 +73,6 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
|||||||
}
|
}
|
||||||
} else if (key != null) {
|
} else if (key != null) {
|
||||||
pushWrappedEvent.unwrap(key)?.let { notificationEvent ->
|
pushWrappedEvent.unwrap(key)?.let { notificationEvent ->
|
||||||
if (!LocalCache.justVerify(notificationEvent)) return // invalid event
|
|
||||||
if (LocalCache.notes[notificationEvent.id] != null) return // already processed
|
|
||||||
|
|
||||||
LocalCache.justConsume(notificationEvent, null)
|
LocalCache.justConsume(notificationEvent, null)
|
||||||
|
|
||||||
unwrapAndConsume(notificationEvent, account)?.let { innerEvent ->
|
unwrapAndConsume(notificationEvent, account)?.let { innerEvent ->
|
||||||
|
|||||||
+2
-2
@@ -18,12 +18,12 @@ class RegisterAccounts(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
// creates proof that it controls all accounts
|
// creates proof that it controls all accounts
|
||||||
private fun signEventsToProveControlOfAccounts(
|
private suspend fun signEventsToProveControlOfAccounts(
|
||||||
accounts: List<AccountInfo>,
|
accounts: List<AccountInfo>,
|
||||||
notificationToken: String
|
notificationToken: String
|
||||||
): List<RelayAuthEvent> {
|
): List<RelayAuthEvent> {
|
||||||
return accounts.mapNotNull {
|
return accounts.mapNotNull {
|
||||||
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
val acc = LocalPreferences.loadCurrentAccountFromEncryptedStorage(it.npub)
|
||||||
if (acc != null && (acc.isWriteable() || acc.loginWithExternalSigner)) {
|
if (acc != null && (acc.isWriteable() || acc.loginWithExternalSigner)) {
|
||||||
if (acc.loginWithExternalSigner) {
|
if (acc.loginWithExternalSigner) {
|
||||||
ExternalSignerUtils.account = acc
|
ExternalSignerUtils.account = acc
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ class PlaybackService : MediaSessionService() {
|
|||||||
|
|
||||||
fun newProgressiveDataSource(): MediaSource.Factory {
|
fun newProgressiveDataSource(): MediaSource.Factory {
|
||||||
return ProgressiveMediaSource.Factory(
|
return ProgressiveMediaSource.Factory(
|
||||||
VideoCache.get(
|
(applicationContext as Amethyst).videoCache.get(HttpClient.getHttpClient())
|
||||||
Amethyst.instance,
|
|
||||||
HttpClient.getHttpClient()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@UnstableApi object VideoCache {
|
@UnstableApi class VideoCache {
|
||||||
|
|
||||||
var exoPlayerCacheSize: Long = 150 * 1024 * 1024 // 90MB
|
var exoPlayerCacheSize: Long = 150 * 1024 * 1024 // 90MB
|
||||||
|
|
||||||
@@ -42,11 +42,7 @@ import java.io.File
|
|||||||
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(context: Context, client: OkHttpClient): CacheDataSource.Factory {
|
fun get(client: OkHttpClient): CacheDataSource.Factory {
|
||||||
if (!this::simpleCache.isInitialized) {
|
|
||||||
initFileCache(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renews the factory because OkHttpMight have changed.
|
// Renews the factory because OkHttpMight have changed.
|
||||||
renewCacheFactory(client)
|
renewCacheFactory(client)
|
||||||
|
|
||||||
|
|||||||
@@ -14,17 +14,16 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.service.ExternalSignerUtils
|
import com.vitorpamplona.amethyst.service.ExternalSignerUtils
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
|
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
|
||||||
import com.vitorpamplona.amethyst.ui.components.DefaultMutedSetting
|
import com.vitorpamplona.amethyst.ui.components.DefaultMutedSetting
|
||||||
import com.vitorpamplona.amethyst.ui.components.keepPlayingMutex
|
import com.vitorpamplona.amethyst.ui.components.keepPlayingMutex
|
||||||
@@ -33,7 +32,7 @@ import com.vitorpamplona.amethyst.ui.navigation.debugState
|
|||||||
import com.vitorpamplona.amethyst.ui.note.Nip47
|
import com.vitorpamplona.amethyst.ui.note.Nip47
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||||
import com.vitorpamplona.quartz.encoders.Nip19
|
import com.vitorpamplona.quartz.encoders.Nip19
|
||||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||||
@@ -50,46 +49,38 @@ import java.net.URLEncoder
|
|||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
private val isOnMobileDataState = mutableStateOf(false)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ExternalSignerUtils.start(this)
|
ExternalSignerUtils.start(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val language = LocalPreferences.getPreferredLanguage()
|
setContent {
|
||||||
if (language.isNotBlank()) {
|
val sharedPreferencesViewModel: SharedPreferencesViewModel = viewModel()
|
||||||
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(language)
|
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
LaunchedEffect(key1 = sharedPreferencesViewModel, isOnMobileDataState) {
|
||||||
|
sharedPreferencesViewModel.init()
|
||||||
|
sharedPreferencesViewModel.updateConnectivityStatusState(isOnMobileDataState)
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
AmethystTheme(sharedPreferencesViewModel) {
|
||||||
val themeViewModel: ThemeViewModel = viewModel()
|
|
||||||
|
|
||||||
themeViewModel.onChange(LocalPreferences.getTheme())
|
|
||||||
AmethystTheme(themeViewModel) {
|
|
||||||
// A surface container using the 'background' color from the theme
|
// A surface container using the 'background' color from the theme
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
val accountStateViewModel: AccountStateViewModel = viewModel {
|
val accountStateViewModel: AccountStateViewModel = viewModel()
|
||||||
AccountStateViewModel(this@MainActivity)
|
|
||||||
|
LaunchedEffect(key1 = Unit) {
|
||||||
|
accountStateViewModel.tryLoginExistingAccountAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountScreen(accountStateViewModel, themeViewModel)
|
AccountScreen(accountStateViewModel, sharedPreferencesViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val networkRequest = NetworkRequest.Builder()
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val connectivityManager =
|
|
||||||
getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
@@ -102,13 +93,22 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// Only starts after login
|
// Only starts after login
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
if (ServiceManager.shouldPauseService) {
|
if (ServiceManager.shouldPauseService) {
|
||||||
ServiceManager.start(this@MainActivity)
|
ServiceManager.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts())
|
PushNotificationUtils.init(LocalPreferences.allSavedAccounts())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val networkRequest = NetworkRequest.Builder()
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
(getSystemService(ConnectivityManager::class.java) as ConnectivityManager)
|
||||||
|
.registerNetworkCallback(networkRequest, networkCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@@ -120,6 +120,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (ServiceManager.shouldPauseService) {
|
if (ServiceManager.shouldPauseService) {
|
||||||
ServiceManager.pause()
|
ServiceManager.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(getSystemService(ConnectivityManager::class.java) as ConnectivityManager)
|
||||||
|
.unregisterNetworkCallback(networkCallback)
|
||||||
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +157,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
if (ServiceManager.shouldPauseService) {
|
if (ServiceManager.shouldPauseService) {
|
||||||
ServiceManager.pause()
|
ServiceManager.pause()
|
||||||
ServiceManager.start(this@MainActivity)
|
ServiceManager.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,15 +170,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onCapabilitiesChanged(network, networkCapabilities)
|
super.onCapabilitiesChanged(network, networkCapabilities)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val hasMobileData =
|
val isOnMobileData = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|
Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasMobileData $isOnMobileData")
|
||||||
val hasWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
|
||||||
Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasMobileData $hasMobileData")
|
isOnMobileDataState.value = isOnMobileData
|
||||||
Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasWifi $hasWifi")
|
|
||||||
ConnectivityStatus.updateConnectivityStatus(
|
|
||||||
hasMobileData,
|
|
||||||
hasWifi
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,12 +54,10 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||||
@@ -69,8 +67,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -304,13 +300,9 @@ private fun RenderSearchResults(
|
|||||||
if (searchBarViewModel.isSearching) {
|
if (searchBarViewModel.isSearching) {
|
||||||
val users by searchBarViewModel.searchResultsUsers.collectAsState()
|
val users by searchBarViewModel.searchResultsUsers.collectAsState()
|
||||||
val channels by searchBarViewModel.searchResultsChannels.collectAsState()
|
val channels by searchBarViewModel.searchResultsChannels.collectAsState()
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -332,10 +324,8 @@ private fun RenderSearchResults(
|
|||||||
key = { _, item -> "u" + item.pubkeyHex }
|
key = { _, item -> "u" + item.pubkeyHex }
|
||||||
) { _, item ->
|
) { _, item ->
|
||||||
UserComposeForChat(item, accountViewModel) {
|
UserComposeForChat(item, accountViewModel) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.createChatRoomFor(item) {
|
||||||
val withKey = ChatroomKey(persistentSetOf(item.pubkeyHex))
|
nav("Room/$it")
|
||||||
accountViewModel.userProfile().createChatroom(withKey)
|
|
||||||
nav("Room/${withKey.hashCode()}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBarViewModel.clear()
|
searchBarViewModel.clear()
|
||||||
|
|||||||
@@ -58,11 +58,9 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays
|
import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
@@ -304,7 +302,6 @@ fun ServerConfig(
|
|||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
relayInfo?.let {
|
relayInfo?.let {
|
||||||
@@ -318,16 +315,12 @@ fun ServerConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerConfigClickableLine(
|
ServerConfigClickableLine(
|
||||||
loadProfilePicture = automaticallyShowProfilePicture,
|
|
||||||
item = item,
|
item = item,
|
||||||
|
loadProfilePicture = automaticallyShowProfilePicture,
|
||||||
onToggleDownload = onToggleDownload,
|
onToggleDownload = onToggleDownload,
|
||||||
onToggleUpload = onToggleUpload,
|
onToggleUpload = onToggleUpload,
|
||||||
onToggleFollows = onToggleFollows,
|
onToggleFollows = onToggleFollows,
|
||||||
@@ -362,8 +355,8 @@ fun ServerConfig(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ServerConfigClickableLine(
|
fun ServerConfigClickableLine(
|
||||||
loadProfilePicture: Boolean,
|
|
||||||
item: RelaySetupInfo,
|
item: RelaySetupInfo,
|
||||||
|
loadProfilePicture: Boolean,
|
||||||
onToggleDownload: (RelaySetupInfo) -> Unit,
|
onToggleDownload: (RelaySetupInfo) -> Unit,
|
||||||
onToggleUpload: (RelaySetupInfo) -> Unit,
|
onToggleUpload: (RelaySetupInfo) -> Unit,
|
||||||
onToggleFollows: (RelaySetupInfo) -> Unit,
|
onToggleFollows: (RelaySetupInfo) -> Unit,
|
||||||
|
|||||||
@@ -26,10 +26,8 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
import com.vitorpamplona.amethyst.ui.components.ClickableEmail
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
||||||
@@ -51,11 +49,7 @@ fun RelayInformationDialog(
|
|||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
|
|||||||
@@ -8,19 +8,13 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
|
fun UrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
|
||||||
val automaticallyShowUrlPreview = remember {
|
val automaticallyShowUrlPreview = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowUrlPreview) {
|
accountViewModel.settings.showUrlPreview.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!automaticallyShowUrlPreview) {
|
if (!automaticallyShowUrlPreview) {
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
@@ -33,11 +31,7 @@ fun UrlPreviewCard(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val automaticallyShowUrlPreview = remember {
|
val automaticallyShowUrlPreview = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowUrlPreview) {
|
accountViewModel.settings.showUrlPreview.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!automaticallyShowUrlPreview) {
|
if (!automaticallyShowUrlPreview) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -63,8 +64,6 @@ import androidx.media3.session.MediaController
|
|||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import com.linc.audiowaveform.infiniteLinearGradient
|
import com.linc.audiowaveform.infiniteLinearGradient
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
|
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
|
||||||
import com.vitorpamplona.amethyst.ui.note.LyricsIcon
|
import com.vitorpamplona.amethyst.ui.note.LyricsIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.LyricsOffIcon
|
import com.vitorpamplona.amethyst.ui.note.LyricsOffIcon
|
||||||
@@ -202,14 +201,8 @@ fun VideoViewInner(
|
|||||||
onDialog: ((Boolean) -> Unit)? = null
|
onDialog: ((Boolean) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val automaticallyStartPlayback = remember {
|
val automaticallyStartPlayback = remember {
|
||||||
mutableStateOf(
|
mutableStateOf<Boolean>(
|
||||||
if (alwaysShowVideo) { true } else {
|
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback.value
|
||||||
when (accountViewModel.account.settings.automaticallyStartPlayback) {
|
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +518,7 @@ private fun RenderVideoPlayer(
|
|||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
keepPlaying: MutableState<Boolean>,
|
keepPlaying: MutableState<Boolean>,
|
||||||
automaticallyStartPlayback: MutableState<Boolean>,
|
automaticallyStartPlayback: State<Boolean>,
|
||||||
activeOnScreen: MutableState<Boolean>,
|
activeOnScreen: MutableState<Boolean>,
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
onDialog: ((Boolean) -> Unit)?
|
onDialog: ((Boolean) -> Unit)?
|
||||||
@@ -735,7 +728,7 @@ fun DrawWaveform(waveform: ImmutableList<Int>, waveformProgress: MutableState<Fl
|
|||||||
fun ControlWhenPlayerIsActive(
|
fun ControlWhenPlayerIsActive(
|
||||||
controller: Player,
|
controller: Player,
|
||||||
keepPlaying: MutableState<Boolean>,
|
keepPlaying: MutableState<Boolean>,
|
||||||
automaticallyStartPlayback: MutableState<Boolean>,
|
automaticallyStartPlayback: State<Boolean>,
|
||||||
activeOnScreen: MutableState<Boolean>
|
activeOnScreen: MutableState<Boolean>
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(key1 = activeOnScreen.value) {
|
LaunchedEffect(key1 = activeOnScreen.value) {
|
||||||
|
|||||||
@@ -78,9 +78,7 @@ import coil.compose.AsyncImage
|
|||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.service.BlurHashRequester
|
import com.vitorpamplona.amethyst.service.BlurHashRequester
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||||
@@ -276,21 +274,14 @@ private fun LocalImageView(
|
|||||||
content: ZoomableLocalImage,
|
content: ZoomableLocalImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
accountViewModel: AccountViewModel?,
|
accountViewModel: AccountViewModel,
|
||||||
alwayShowImage: Boolean = false
|
alwayShowImage: Boolean = false
|
||||||
) {
|
) {
|
||||||
if (content.localFile != null && content.localFile.exists()) {
|
if (content.localFile != null && content.localFile.exists()) {
|
||||||
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
||||||
val showImage = remember {
|
val showImage = remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
if (alwayShowImage) { true } else {
|
if (alwayShowImage) true else accountViewModel.settings.showImages.value
|
||||||
when (accountViewModel?.account?.settings?.automaticallyShowImages) {
|
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,20 +341,13 @@ private fun UrlImageView(
|
|||||||
content: ZoomableUrlImage,
|
content: ZoomableUrlImage,
|
||||||
mainImageModifier: Modifier,
|
mainImageModifier: Modifier,
|
||||||
topPaddingForControllers: Dp = Dp.Unspecified,
|
topPaddingForControllers: Dp = Dp.Unspecified,
|
||||||
accountViewModel: AccountViewModel?,
|
accountViewModel: AccountViewModel,
|
||||||
alwayShowImage: Boolean = false
|
alwayShowImage: Boolean = false
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
||||||
val showImage = remember {
|
val showImage = remember {
|
||||||
mutableStateOf(
|
mutableStateOf<Boolean>(
|
||||||
if (alwayShowImage) { true } else {
|
if (alwayShowImage) true else accountViewModel.settings.showImages.value
|
||||||
when (accountViewModel?.account?.settings?.automaticallyShowImages) {
|
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-12
@@ -45,10 +45,8 @@ import androidx.lifecycle.map
|
|||||||
import com.vitorpamplona.amethyst.AccountInfo
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||||
@@ -141,14 +139,6 @@ fun DisplayAccount(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUser == null) {
|
if (baseUser == null) {
|
||||||
LaunchedEffect(key1 = acc.npub) {
|
LaunchedEffect(key1 = acc.npub) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
@@ -168,7 +158,7 @@ fun DisplayAccount(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
accountStateViewModel.switchUser(acc.npub)
|
accountStateViewModel.switchUser(acc)
|
||||||
}
|
}
|
||||||
.padding(16.dp, 16.dp),
|
.padding(16.dp, 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@@ -186,6 +176,10 @@ fun DisplayAccount(
|
|||||||
.width(55.dp)
|
.width(55.dp)
|
||||||
.padding(0.dp)
|
.padding(0.dp)
|
||||||
) {
|
) {
|
||||||
|
val automaticallyShowProfilePicture = remember {
|
||||||
|
accountViewModel.settings.showProfilePictures.value
|
||||||
|
}
|
||||||
|
|
||||||
AccountPicture(it, automaticallyShowProfilePicture)
|
AccountPicture(it, automaticallyShowProfilePicture)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
@@ -281,7 +275,7 @@ private fun LogoutButton(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
logoutDialog = false
|
logoutDialog = false
|
||||||
accountStateViewModel.logOff(acc.npub)
|
accountStateViewModel.logOff(acc)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.log_out))
|
Text(text = stringResource(R.string.log_out))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen
|
||||||
@@ -69,7 +69,7 @@ fun AppNavigation(
|
|||||||
|
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
themeViewModel: ThemeViewModel
|
sharedPreferencesViewModel: SharedPreferencesViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val nav = remember {
|
val nav = remember {
|
||||||
@@ -263,8 +263,7 @@ fun AppNavigation(
|
|||||||
Route.Settings.let { route ->
|
Route.Settings.let { route ->
|
||||||
composable(route.route, route.arguments, content = {
|
composable(route.route, route.arguments, content = {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
accountViewModel = accountViewModel,
|
sharedPreferencesViewModel
|
||||||
themeViewModel
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import com.fonfon.kgeohash.toGeoHash
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
|
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@@ -80,7 +79,6 @@ import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
@@ -508,11 +506,7 @@ private fun LoggedInUserPictureDrawer(
|
|||||||
val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex }
|
val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex }
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.navigation
|
package com.vitorpamplona.amethyst.ui.navigation
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -28,7 +27,6 @@ import androidx.compose.material.icons.filled.Send
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.DrawerState
|
import androidx.compose.material3.DrawerState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -69,10 +67,8 @@ import com.vitorpamplona.amethyst.LocalPreferences
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.HttpClient
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
|
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||||
@@ -89,11 +85,9 @@ import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size26Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size26Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerContent(
|
fun DrawerContent(
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
@@ -102,11 +96,7 @@ fun DrawerContent(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalDrawerSheet(
|
ModalDrawerSheet(
|
||||||
@@ -138,7 +128,12 @@ fun DrawerContent(
|
|||||||
accountViewModel
|
accountViewModel
|
||||||
)
|
)
|
||||||
|
|
||||||
BottomContent(accountViewModel.account.userProfile(), drawerState, automaticallyShowProfilePicture, nav)
|
BottomContent(
|
||||||
|
accountViewModel.account.userProfile(),
|
||||||
|
drawerState,
|
||||||
|
automaticallyShowProfilePicture,
|
||||||
|
nav
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,11 +160,7 @@ fun ProfileContent(
|
|||||||
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
@@ -574,7 +565,9 @@ fun ListContent(
|
|||||||
conectOrbotDialogOpen = false
|
conectOrbotDialogOpen = false
|
||||||
disconnectTorDialog = false
|
disconnectTorDialog = false
|
||||||
checked = true
|
checked = true
|
||||||
enableTor(accountViewModel.account, true, proxyPort, context, coroutineScope)
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
enableTor(accountViewModel.account, true, proxyPort)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
accountViewModel.toast(
|
accountViewModel.toast(
|
||||||
@@ -602,7 +595,13 @@ fun ListContent(
|
|||||||
onClick = {
|
onClick = {
|
||||||
disconnectTorDialog = false
|
disconnectTorDialog = false
|
||||||
checked = false
|
checked = false
|
||||||
enableTor(accountViewModel.account, false, proxyPort, context, coroutineScope)
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
enableTor(
|
||||||
|
accountViewModel.account,
|
||||||
|
false,
|
||||||
|
proxyPort
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.yes))
|
Text(text = stringResource(R.string.yes))
|
||||||
@@ -621,20 +620,16 @@ fun ListContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableTor(
|
private suspend fun enableTor(
|
||||||
account: Account,
|
account: Account,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
portNumber: MutableState<String>,
|
portNumber: MutableState<String>
|
||||||
context: Context,
|
|
||||||
scope: CoroutineScope
|
|
||||||
) {
|
) {
|
||||||
account.proxyPort = portNumber.value.toInt()
|
account.proxyPort = portNumber.value.toInt()
|
||||||
account.proxy = HttpClient.initProxy(checked, "127.0.0.1", account.proxyPort)
|
account.proxy = HttpClient.initProxy(checked, "127.0.0.1", account.proxyPort)
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
LocalPreferences.saveToEncryptedStorage(account)
|
LocalPreferences.saveToEncryptedStorage(account)
|
||||||
ServiceManager.pause()
|
ServiceManager.pause()
|
||||||
ServiceManager.start(context)
|
ServiceManager.start()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -47,10 +47,8 @@ import androidx.lifecycle.map
|
|||||||
import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended
|
import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -188,11 +186,7 @@ private fun ChannelRoomCompose(
|
|||||||
val hasNewMessages = remember { mutableStateOf<Boolean>(false) }
|
val hasNewMessages = remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
|
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
|
||||||
|
|||||||
@@ -43,10 +43,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
@@ -396,11 +394,7 @@ private fun MessageBubbleLines(
|
|||||||
availableBubbleSize: MutableState<Int>
|
availableBubbleSize: MutableState<Int>
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawAuthorInfo) {
|
if (drawAuthorInfo) {
|
||||||
|
|||||||
@@ -43,10 +43,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ImageUrlType
|
import com.vitorpamplona.amethyst.ui.components.ImageUrlType
|
||||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
@@ -524,11 +522,7 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchUserMetadata(author) { baseUserPicture ->
|
WatchUserMetadata(author) { baseUserPicture ->
|
||||||
|
|||||||
@@ -84,12 +84,10 @@ import com.fonfon.kgeohash.toGeoHash
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||||
@@ -730,11 +728,7 @@ fun ShortCommunityHeader(baseNote: AddressableNote, fontWeight: FontWeight = Fon
|
|||||||
val noteEvent = remember(noteState) { noteState?.note?.event as? CommunityDefinitionEvent } ?: return
|
val noteEvent = remember(noteState) { noteState?.note?.event as? CommunityDefinitionEvent } ?: return
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -2791,11 +2785,7 @@ private fun RenderAuthorImages(
|
|||||||
val isChannel = baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null
|
val isChannel = baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
@@ -3793,11 +3783,7 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountView
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
val automaticallyShowUrlPreview = remember {
|
val automaticallyShowUrlPreview = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowUrlPreview) {
|
accountViewModel.settings.showUrlPreview.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (automaticallyShowUrlPreview) {
|
if (automaticallyShowUrlPreview) {
|
||||||
|
|||||||
@@ -32,12 +32,10 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -128,11 +126,7 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
|
|||||||
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val clickableModifier = remember(relay) {
|
val clickableModifier = remember(relay) {
|
||||||
|
|||||||
@@ -44,11 +44,9 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ExternalSignerUtils
|
import com.vitorpamplona.amethyst.service.ExternalSignerUtils
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -81,13 +79,6 @@ fun NoteAuthorPicture(
|
|||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val author by baseNote.live().authorChanges.observeAsState(baseNote.author)
|
val author by baseNote.live().authorChanges.observeAsState(baseNote.author)
|
||||||
val automaticallyShowProfilePicture = remember {
|
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Crossfade(targetState = author) {
|
Crossfade(targetState = author) {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
@@ -336,11 +327,7 @@ fun PictureAndFollowingMark(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
||||||
@@ -14,33 +22,53 @@ import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
|||||||
@Composable
|
@Composable
|
||||||
fun AccountScreen(
|
fun AccountScreen(
|
||||||
accountStateViewModel: AccountStateViewModel,
|
accountStateViewModel: AccountStateViewModel,
|
||||||
themeViewModel: ThemeViewModel
|
sharedPreferencesViewModel: SharedPreferencesViewModel
|
||||||
) {
|
) {
|
||||||
val accountState by accountStateViewModel.accountContent.collectAsState()
|
val accountState by accountStateViewModel.accountContent.collectAsState()
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(
|
||||||
|
targetState = accountState,
|
||||||
|
animationSpec = tween(durationMillis = 100),
|
||||||
|
label = "AccountState"
|
||||||
|
) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
|
is AccountState.Loading -> {
|
||||||
|
LoadingAccounts()
|
||||||
|
}
|
||||||
is AccountState.LoggedOff -> {
|
is AccountState.LoggedOff -> {
|
||||||
LoginPage(accountStateViewModel, isFirstLogin = true)
|
LoginPage(accountStateViewModel, isFirstLogin = true)
|
||||||
}
|
}
|
||||||
is AccountState.LoggedIn -> {
|
is AccountState.LoggedIn -> {
|
||||||
val accountViewModel: AccountViewModel = viewModel(
|
val accountViewModel: AccountViewModel = viewModel(
|
||||||
key = state.account.userProfile().pubkeyHex,
|
key = state.account.userProfile().pubkeyHex,
|
||||||
factory = AccountViewModel.Factory(state.account)
|
factory = AccountViewModel.Factory(state.account, sharedPreferencesViewModel.sharedPrefs)
|
||||||
)
|
)
|
||||||
|
|
||||||
MainScreen(accountViewModel, accountStateViewModel, themeViewModel)
|
MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel)
|
||||||
}
|
}
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
val accountViewModel: AccountViewModel = viewModel(
|
val accountViewModel: AccountViewModel = viewModel(
|
||||||
key = state.account.userProfile().pubkeyHex,
|
key = state.account.userProfile().pubkeyHex,
|
||||||
factory = AccountViewModel.Factory(state.account)
|
factory = AccountViewModel.Factory(state.account, sharedPreferencesViewModel.sharedPrefs)
|
||||||
)
|
)
|
||||||
|
|
||||||
MainScreen(accountViewModel, accountStateViewModel, themeViewModel)
|
MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingAccounts() {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.loading_account))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
|
||||||
sealed class AccountState {
|
sealed class AccountState {
|
||||||
|
object Loading : AccountState()
|
||||||
object LoggedOff : AccountState()
|
object LoggedOff : AccountState()
|
||||||
class LoggedInViewOnly(val account: Account) : AccountState()
|
class LoggedInViewOnly(val account: Account) : AccountState()
|
||||||
class LoggedIn(val account: Account) : AccountState()
|
class LoggedIn(val account: Account) : AccountState()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
@@ -20,32 +21,36 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val EMAIL_PATTERN = Pattern.compile(".+@.+\\.[a-z]+")
|
val EMAIL_PATTERN = Pattern.compile(".+@.+\\.[a-z]+")
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountStateViewModel(val context: Context) : ViewModel() {
|
class AccountStateViewModel() : ViewModel() {
|
||||||
private val _accountContent = MutableStateFlow<AccountState>(AccountState.LoggedOff)
|
private val _accountContent = MutableStateFlow<AccountState>(AccountState.Loading)
|
||||||
val accountContent = _accountContent.asStateFlow()
|
val accountContent = _accountContent.asStateFlow()
|
||||||
|
|
||||||
init {
|
fun tryLoginExistingAccountAsync() {
|
||||||
Log.d("Init", "AccountStateViewModel")
|
|
||||||
// pulls account from storage.
|
// pulls account from storage.
|
||||||
|
viewModelScope.launch {
|
||||||
// Keeps it in the the UI thread to void blinking the login page.
|
|
||||||
// viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
tryLoginExistingAccount()
|
tryLoginExistingAccount()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryLoginExistingAccount() {
|
private suspend fun tryLoginExistingAccount() = withContext(Dispatchers.IO) {
|
||||||
LocalPreferences.loadFromEncryptedStorage()?.let {
|
LocalPreferences.loadCurrentAccountFromEncryptedStorage()?.let {
|
||||||
startUI(it)
|
startUI(it)
|
||||||
|
} ?: run {
|
||||||
|
requestLoginUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startUI(key: String, useProxy: Boolean, proxyPort: Int, loginWithExternalSigner: Boolean = false) {
|
private suspend fun requestLoginUI() {
|
||||||
|
_accountContent.update { AccountState.LoggedOff }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loginAndStartUI(key: String, useProxy: Boolean, proxyPort: Int, loginWithExternalSigner: Boolean = false) = withContext(Dispatchers.IO) {
|
||||||
val parsed = Nip19.uriToRoute(key)
|
val parsed = Nip19.uriToRoute(key)
|
||||||
val pubKeyParsed = parsed?.hex?.hexToByteArray()
|
val pubKeyParsed = parsed?.hex?.hexToByteArray()
|
||||||
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
||||||
@@ -63,37 +68,23 @@ class AccountStateViewModel(val context: Context) : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalPreferences.updatePrefsForLogin(account)
|
LocalPreferences.updatePrefsForLogin(account)
|
||||||
startUI(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchUser(npub: String) {
|
|
||||||
prepareLogoutOrSwitch()
|
|
||||||
LocalPreferences.switchToAccount(npub)
|
|
||||||
tryLoginExistingAccount()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newKey(useProxy: Boolean, proxyPort: Int) {
|
|
||||||
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
|
||||||
val account = Account(KeyPair(), proxy = proxy, proxyPort = proxyPort)
|
|
||||||
// saves to local preferences
|
|
||||||
LocalPreferences.updatePrefsForLogin(account)
|
|
||||||
startUI(account)
|
startUI(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun startUI(account: Account) {
|
suspend fun startUI(account: Account) = withContext(Dispatchers.Main) {
|
||||||
if (account.keyPair.privKey != null) {
|
if (account.keyPair.privKey != null) {
|
||||||
_accountContent.update { AccountState.LoggedIn(account) }
|
_accountContent.update { AccountState.LoggedIn(account) }
|
||||||
} else {
|
} else {
|
||||||
_accountContent.update { AccountState.LoggedInViewOnly(account) }
|
_accountContent.update { AccountState.LoggedInViewOnly(account) }
|
||||||
}
|
}
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
ServiceManager.restartIfDifferentAccount(account, context)
|
ServiceManager.restartIfDifferentAccount(account)
|
||||||
}
|
}
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
account.saveable.observeForever(saveListener)
|
account.saveable.observeForever(saveListener)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private val saveListener: (com.vitorpamplona.amethyst.model.AccountState) -> Unit = {
|
private val saveListener: (com.vitorpamplona.amethyst.model.AccountState) -> Unit = {
|
||||||
@@ -102,28 +93,58 @@ class AccountStateViewModel(val context: Context) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
private suspend fun prepareLogoutOrSwitch() = withContext(Dispatchers.Main) {
|
||||||
private fun prepareLogoutOrSwitch() {
|
when (val state = _accountContent.value) {
|
||||||
when (val state = accountContent.value) {
|
|
||||||
is AccountState.LoggedIn -> {
|
is AccountState.LoggedIn -> {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
state.account.saveable.removeObserver(saveListener)
|
state.account.saveable.removeObserver(saveListener)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
state.account.saveable.removeObserver(saveListener)
|
state.account.saveable.removeObserver(saveListener)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
_accountContent.update { AccountState.LoggedOff }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logOff(npub: String) {
|
fun login(
|
||||||
|
key: String,
|
||||||
|
useProxy: Boolean,
|
||||||
|
proxyPort: Int,
|
||||||
|
loginWithExternalSigner: Boolean = false,
|
||||||
|
onError: () -> Unit
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Login", "Could not sign in", e)
|
||||||
|
onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newKey(useProxy: Boolean, proxyPort: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
|
||||||
|
val account = Account(KeyPair(), proxy = proxy, proxyPort = proxyPort)
|
||||||
|
// saves to local preferences
|
||||||
|
LocalPreferences.updatePrefsForLogin(account)
|
||||||
|
startUI(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun switchUser(accountInfo: AccountInfo) {
|
||||||
|
viewModelScope.launch {
|
||||||
prepareLogoutOrSwitch()
|
prepareLogoutOrSwitch()
|
||||||
LocalPreferences.updatePrefsForLogout(npub)
|
LocalPreferences.switchToAccount(accountInfo)
|
||||||
tryLoginExistingAccount()
|
tryLoginExistingAccount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun logOff(accountInfo: AccountInfo) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
prepareLogoutOrSwitch()
|
||||||
|
LocalPreferences.updatePrefsForLogout(accountInfo)
|
||||||
|
tryLoginExistingAccount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.model.BooleanType
|
||||||
|
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||||
|
import com.vitorpamplona.amethyst.model.Settings
|
||||||
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class SettingsState() {
|
||||||
|
var theme by mutableStateOf(ThemeType.SYSTEM)
|
||||||
|
var language by mutableStateOf<String?>(null)
|
||||||
|
|
||||||
|
var automaticallyShowImages by mutableStateOf(ConnectivityType.ALWAYS)
|
||||||
|
var automaticallyStartPlayback by mutableStateOf(ConnectivityType.ALWAYS)
|
||||||
|
var automaticallyShowUrlPreview by mutableStateOf(ConnectivityType.ALWAYS)
|
||||||
|
var automaticallyHideNavigationBars by mutableStateOf(BooleanType.ALWAYS)
|
||||||
|
var automaticallyShowProfilePictures by mutableStateOf(ConnectivityType.ALWAYS)
|
||||||
|
|
||||||
|
var isOnMobileData: State<Boolean> = mutableStateOf(false)
|
||||||
|
|
||||||
|
val showProfilePictures = derivedStateOf {
|
||||||
|
when (automaticallyShowProfilePictures) {
|
||||||
|
ConnectivityType.WIFI_ONLY -> !isOnMobileData.value
|
||||||
|
ConnectivityType.NEVER -> false
|
||||||
|
ConnectivityType.ALWAYS -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showUrlPreview = derivedStateOf {
|
||||||
|
when (automaticallyShowUrlPreview) {
|
||||||
|
ConnectivityType.WIFI_ONLY -> !isOnMobileData.value
|
||||||
|
ConnectivityType.NEVER -> false
|
||||||
|
ConnectivityType.ALWAYS -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val startVideoPlayback = derivedStateOf {
|
||||||
|
when (automaticallyStartPlayback) {
|
||||||
|
ConnectivityType.WIFI_ONLY -> !isOnMobileData.value
|
||||||
|
ConnectivityType.NEVER -> false
|
||||||
|
ConnectivityType.ALWAYS -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showImages = derivedStateOf {
|
||||||
|
when (automaticallyShowImages) {
|
||||||
|
ConnectivityType.WIFI_ONLY -> !isOnMobileData.value
|
||||||
|
ConnectivityType.NEVER -> false
|
||||||
|
ConnectivityType.ALWAYS -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class SharedPreferencesViewModel : ViewModel() {
|
||||||
|
val sharedPrefs: SettingsState = SettingsState()
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val savedSettings = LocalPreferences.loadSharedSettings()
|
||||||
|
?: LocalPreferences.migrateOldSharedSettings()
|
||||||
|
?: Settings()
|
||||||
|
|
||||||
|
sharedPrefs.theme = savedSettings.theme
|
||||||
|
sharedPrefs.language = savedSettings.preferredLanguage
|
||||||
|
sharedPrefs.automaticallyShowImages = savedSettings.automaticallyShowImages
|
||||||
|
sharedPrefs.automaticallyStartPlayback = savedSettings.automaticallyStartPlayback
|
||||||
|
sharedPrefs.automaticallyShowUrlPreview = savedSettings.automaticallyShowUrlPreview
|
||||||
|
sharedPrefs.automaticallyHideNavigationBars = savedSettings.automaticallyHideNavigationBars
|
||||||
|
sharedPrefs.automaticallyShowProfilePictures = savedSettings.automaticallyShowProfilePictures
|
||||||
|
|
||||||
|
updateLanguageInTheUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTheme(newTheme: ThemeType) {
|
||||||
|
if (sharedPrefs.theme != newTheme) {
|
||||||
|
sharedPrefs.theme = newTheme
|
||||||
|
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLanguage(newLanguage: String?) {
|
||||||
|
if (sharedPrefs.language != newLanguage) {
|
||||||
|
sharedPrefs.language = newLanguage
|
||||||
|
updateLanguageInTheUI()
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLanguageInTheUI() {
|
||||||
|
if (sharedPrefs.language != null) {
|
||||||
|
AppCompatDelegate.setApplicationLocales(
|
||||||
|
LocaleListCompat.forLanguageTags(sharedPrefs.language)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutomaticallyStartPlayback(newAutomaticallyStartPlayback: ConnectivityType) {
|
||||||
|
if (sharedPrefs.automaticallyStartPlayback != newAutomaticallyStartPlayback) {
|
||||||
|
sharedPrefs.automaticallyStartPlayback = newAutomaticallyStartPlayback
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutomaticallyShowUrlPreview(newAutomaticallyShowUrlPreview: ConnectivityType) {
|
||||||
|
if (sharedPrefs.automaticallyShowUrlPreview != newAutomaticallyShowUrlPreview) {
|
||||||
|
sharedPrefs.automaticallyShowUrlPreview = newAutomaticallyShowUrlPreview
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutomaticallyShowProfilePicture(newAutomaticallyShowProfilePictures: ConnectivityType) {
|
||||||
|
if (sharedPrefs.automaticallyShowProfilePictures != newAutomaticallyShowProfilePictures) {
|
||||||
|
sharedPrefs.automaticallyShowProfilePictures = newAutomaticallyShowProfilePictures
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutomaticallyHideNavBars(newAutomaticallyHideHavBars: BooleanType) {
|
||||||
|
if (sharedPrefs.automaticallyHideNavigationBars != newAutomaticallyHideHavBars) {
|
||||||
|
sharedPrefs.automaticallyHideNavigationBars = newAutomaticallyHideHavBars
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutomaticallyShowImages(newAutomaticallyShowImages: ConnectivityType) {
|
||||||
|
if (sharedPrefs.automaticallyShowImages != newAutomaticallyShowImages) {
|
||||||
|
sharedPrefs.automaticallyShowImages = newAutomaticallyShowImages
|
||||||
|
saveSharedSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateConnectivityStatusState(isOnMobileDataState: State<Boolean>) {
|
||||||
|
if (sharedPrefs.isOnMobileData != isOnMobileDataState) {
|
||||||
|
sharedPrefs.isOnMobileData = isOnMobileDataState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSharedSettings() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
LocalPreferences.saveSharedSettings(
|
||||||
|
Settings(
|
||||||
|
sharedPrefs.theme,
|
||||||
|
sharedPrefs.language,
|
||||||
|
sharedPrefs.automaticallyShowImages,
|
||||||
|
sharedPrefs.automaticallyStartPlayback,
|
||||||
|
sharedPrefs.automaticallyShowUrlPreview,
|
||||||
|
sharedPrefs.automaticallyHideNavigationBars,
|
||||||
|
sharedPrefs.automaticallyShowProfilePictures
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
class ThemeViewModel : ViewModel() {
|
|
||||||
private val _theme = MutableLiveData(0)
|
|
||||||
val theme: LiveData<Int> = _theme
|
|
||||||
|
|
||||||
fun onChange(newValue: Int) {
|
|
||||||
_theme.value = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-35
@@ -17,9 +17,7 @@ import coil.request.ImageRequest
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AccountState
|
import com.vitorpamplona.amethyst.model.AccountState
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.BooleanType
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
@@ -41,6 +39,7 @@ import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
|||||||
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.SettingsState
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.encoders.Nip19
|
import com.vitorpamplona.quartz.encoders.Nip19
|
||||||
@@ -81,7 +80,7 @@ class StringToastMsg(val title: String, val msg: String) : ToastMsg()
|
|||||||
class ResourceToastMsg(val titleResId: Int, val resourceId: Int) : ToastMsg()
|
class ResourceToastMsg(val titleResId: Int, val resourceId: Int) : ToastMsg()
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
class AccountViewModel(val account: Account, val settings: SettingsState) : ViewModel(), Dao {
|
||||||
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
||||||
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
||||||
val accountMarkAsReadUpdates = mutableStateOf(0)
|
val accountMarkAsReadUpdates = mutableStateOf(0)
|
||||||
@@ -129,36 +128,6 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAutomaticallyStartPlayback(
|
|
||||||
automaticallyStartPlayback: ConnectivityType
|
|
||||||
) {
|
|
||||||
account.updateAutomaticallyStartPlayback(automaticallyStartPlayback)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowUrlPreview(
|
|
||||||
automaticallyShowUrlPreview: ConnectivityType
|
|
||||||
) {
|
|
||||||
account.updateAutomaticallyShowUrlPreview(automaticallyShowUrlPreview)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowProfilePicture(
|
|
||||||
automaticallyShowProfilePicture: ConnectivityType
|
|
||||||
) {
|
|
||||||
account.updateAutomaticallyShowProfilePicture(automaticallyShowProfilePicture)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyHideNavBars(
|
|
||||||
automaticallyHideHavBars: BooleanType
|
|
||||||
) {
|
|
||||||
account.updateAutomaticallyHideHavBars(automaticallyHideHavBars)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAutomaticallyShowImages(
|
|
||||||
automaticallyShowImages: ConnectivityType
|
|
||||||
) {
|
|
||||||
account.updateAutomaticallyShowImages(automaticallyShowImages)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWriteable(): Boolean {
|
fun isWriteable(): Boolean {
|
||||||
return account.isWriteable()
|
return account.isWriteable()
|
||||||
}
|
}
|
||||||
@@ -884,9 +853,9 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account, val settings: SettingsState) : ViewModelProvider.Factory {
|
||||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||||
return AccountViewModel(account) as AccountViewModel
|
return AccountViewModel(account, settings) as AccountViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -90,7 +89,6 @@ import com.vitorpamplona.amethyst.model.PublicChatChannel
|
|||||||
import com.vitorpamplona.amethyst.model.ServersAvailable
|
import com.vitorpamplona.amethyst.model.ServersAvailable
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
@@ -696,11 +694,7 @@ fun ShortChannelHeader(
|
|||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import androidx.lifecycle.LifecycleEventObserver
|
|||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.note.AddButton
|
import com.vitorpamplona.amethyst.ui.note.AddButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
||||||
@@ -122,7 +121,6 @@ fun HiddenUsersScreen(
|
|||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
warnAboutReports = it
|
warnAboutReports = it
|
||||||
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,7 +133,6 @@ fun HiddenUsersScreen(
|
|||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
filterSpam = it
|
filterSpam = it
|
||||||
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ import kotlin.math.abs
|
|||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
accountStateViewModel: AccountStateViewModel,
|
accountStateViewModel: AccountStateViewModel,
|
||||||
themeViewModel: ThemeViewModel
|
sharedPreferencesViewModel: SharedPreferencesViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
|
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -243,7 +243,7 @@ fun MainScreen(
|
|||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
val newOffset = bottomBarOffsetHeightPx.value + available.y
|
val newOffset = bottomBarOffsetHeightPx.value + available.y
|
||||||
|
|
||||||
if (accountViewModel.account.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) {
|
if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) {
|
||||||
val newBottomBarOffset = if (navState.value?.destination?.route !in InvertedLayouts) {
|
val newBottomBarOffset = if (navState.value?.destination?.route !in InvertedLayouts) {
|
||||||
newOffset.coerceIn(-bottomBarHeightPx, 0f)
|
newOffset.coerceIn(-bottomBarHeightPx, 0f)
|
||||||
} else {
|
} else {
|
||||||
@@ -352,7 +352,7 @@ fun MainScreen(
|
|||||||
userReactionsStatsModel = userReactionsStatsModel,
|
userReactionsStatsModel = userReactionsStatsModel,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
themeViewModel = themeViewModel
|
sharedPreferencesViewModel = sharedPreferencesViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,6 +432,10 @@ fun FloatingButtons(
|
|||||||
val accountState by accountStateViewModel.accountContent.collectAsState()
|
val accountState by accountStateViewModel.accountContent.collectAsState()
|
||||||
|
|
||||||
when (accountState) {
|
when (accountState) {
|
||||||
|
is AccountState.Loading -> {
|
||||||
|
// Does nothing.
|
||||||
|
}
|
||||||
|
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
WritePermissionButtons(navEntryState, accountViewModel, nav, navScrollToTop)
|
WritePermissionButtons(navEntryState, accountViewModel, nav, navScrollToTop)
|
||||||
|
|||||||
@@ -95,12 +95,10 @@ import coil.compose.AsyncImage
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
@@ -884,11 +882,7 @@ private fun DrawAdditionalInfo(
|
|||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user.toBestDisplayName().let {
|
user.toBestDisplayName().let {
|
||||||
@@ -1208,11 +1202,7 @@ private fun DisplayBadges(
|
|||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadAddressableNote(
|
LoadAddressableNote(
|
||||||
|
|||||||
@@ -50,13 +50,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
import com.vitorpamplona.amethyst.ui.note.AboutDisplay
|
import com.vitorpamplona.amethyst.ui.note.AboutDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
||||||
@@ -348,11 +346,7 @@ private fun DisplaySearchResults(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val automaticallyShowProfilePicture = remember {
|
val automaticallyShowProfilePicture = remember {
|
||||||
when (accountViewModel.account.settings.automaticallyShowProfilePictures) {
|
accountViewModel.settings.showProfilePictures.value
|
||||||
ConnectivityType.WIFI_ONLY -> !ConnectivityStatus.isOnMobileData.value
|
|
||||||
ConnectivityType.NEVER -> false
|
|
||||||
ConnectivityType.ALWAYS -> true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -24,7 +23,6 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -37,23 +35,22 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||||
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
import com.vitorpamplona.amethyst.model.parseBooleanType
|
import com.vitorpamplona.amethyst.model.parseBooleanType
|
||||||
import com.vitorpamplona.amethyst.model.parseConnectivityType
|
import com.vitorpamplona.amethyst.model.parseConnectivityType
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel
|
import com.vitorpamplona.amethyst.model.parseThemeType
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -79,7 +76,7 @@ fun Context.getLocaleListFromXml(): LocaleListCompat {
|
|||||||
return LocaleListCompat.forLanguageTags(tagsList.joinToString(","))
|
return LocaleListCompat.forLanguageTags(tagsList.joinToString(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getLangPreferenceDropdownEntries(): Map<String, String> {
|
fun Context.getLangPreferenceDropdownEntries(): ImmutableMap<String, String> {
|
||||||
val localeList = getLocaleListFromXml()
|
val localeList = getLocaleListFromXml()
|
||||||
val map = mutableMapOf<String, String>()
|
val map = mutableMapOf<String, String>()
|
||||||
|
|
||||||
@@ -88,13 +85,13 @@ fun Context.getLangPreferenceDropdownEntries(): Map<String, String> {
|
|||||||
map.put(it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() }, it.toLanguageTag())
|
map.put(it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() }, it.toLanguageTag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map
|
return map.toImmutableMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLanguageIndex(languageEntries: Map<String, String>): Int {
|
fun getLanguageIndex(languageEntries: ImmutableMap<String, String>, sharedPreferencesViewModel: SharedPreferencesViewModel): Int {
|
||||||
val language = LocalPreferences.getPreferredLanguage()
|
val language = sharedPreferencesViewModel.sharedPrefs.language
|
||||||
var languageIndex = -1
|
var languageIndex = -1
|
||||||
if (language.isNotBlank()) {
|
if (language != null) {
|
||||||
languageIndex = languageEntries.values.toTypedArray().indexOf(language)
|
languageIndex = languageEntries.values.toTypedArray().indexOf(language)
|
||||||
} else {
|
} else {
|
||||||
languageIndex = languageEntries.values.toTypedArray().indexOf(Locale.current.toLanguageTag())
|
languageIndex = languageEntries.values.toTypedArray().indexOf(Locale.current.toLanguageTag())
|
||||||
@@ -104,44 +101,43 @@ fun getLanguageIndex(languageEntries: Map<String, String>): Int {
|
|||||||
return languageIndex
|
return languageIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
accountViewModel: AccountViewModel,
|
sharedPreferencesViewModel: SharedPreferencesViewModel
|
||||||
themeViewModel: ThemeViewModel
|
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val selectedItens = persistentListOf(
|
val selectedItens = persistentListOf(
|
||||||
TitleExplainer(stringResource(ConnectivityType.ALWAYS.reourceId)),
|
TitleExplainer(stringResource(ConnectivityType.ALWAYS.resourceId)),
|
||||||
TitleExplainer(stringResource(ConnectivityType.WIFI_ONLY.reourceId)),
|
TitleExplainer(stringResource(ConnectivityType.WIFI_ONLY.resourceId)),
|
||||||
TitleExplainer(stringResource(ConnectivityType.NEVER.reourceId))
|
TitleExplainer(stringResource(ConnectivityType.NEVER.resourceId))
|
||||||
)
|
)
|
||||||
|
|
||||||
val themeItens = persistentListOf(
|
val themeItens = persistentListOf(
|
||||||
TitleExplainer(stringResource(R.string.system)),
|
TitleExplainer(stringResource(ThemeType.SYSTEM.resourceId)),
|
||||||
TitleExplainer(stringResource(R.string.light)),
|
TitleExplainer(stringResource(ThemeType.LIGHT.resourceId)),
|
||||||
TitleExplainer(stringResource(R.string.dark))
|
TitleExplainer(stringResource(ThemeType.DARK.resourceId))
|
||||||
)
|
)
|
||||||
|
|
||||||
val booleanItems = persistentListOf(
|
val booleanItems = persistentListOf(
|
||||||
TitleExplainer(stringResource(ConnectivityType.ALWAYS.reourceId)),
|
TitleExplainer(stringResource(ConnectivityType.ALWAYS.resourceId)),
|
||||||
TitleExplainer(stringResource(ConnectivityType.NEVER.reourceId))
|
TitleExplainer(stringResource(ConnectivityType.NEVER.resourceId))
|
||||||
)
|
)
|
||||||
|
|
||||||
val settings = accountViewModel.account.settings
|
val showImagesIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyShowImages.screenCode
|
||||||
val showImagesIndex = settings.automaticallyShowImages.screenCode
|
val videoIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyStartPlayback.screenCode
|
||||||
val videoIndex = settings.automaticallyStartPlayback.screenCode
|
val linkIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyShowUrlPreview.screenCode
|
||||||
val linkIndex = settings.automaticallyShowUrlPreview.screenCode
|
val hideNavBarsIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyHideNavigationBars.screenCode
|
||||||
val hideNavBarsIndex = settings.automaticallyHideNavigationBars.screenCode
|
val profilePictureIndex = sharedPreferencesViewModel.sharedPrefs.automaticallyShowProfilePictures.screenCode
|
||||||
val profilePictureIndex = settings.automaticallyShowProfilePictures.screenCode
|
val themeIndex = sharedPreferencesViewModel.sharedPrefs.theme.screenCode
|
||||||
|
|
||||||
val themeIndex = themeViewModel.theme.value ?: 0
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val languageEntries = context.getLangPreferenceDropdownEntries()
|
val languageEntries = remember {
|
||||||
val languageList = languageEntries.keys.map { TitleExplainer(it) }.toImmutableList()
|
context.getLangPreferenceDropdownEntries()
|
||||||
val languageIndex = getLanguageIndex(languageEntries)
|
}
|
||||||
|
val languageList = remember {
|
||||||
|
languageEntries.keys.map { TitleExplainer(it) }.toImmutableList()
|
||||||
|
}
|
||||||
|
val languageIndex = getLanguageIndex(languageEntries, sharedPreferencesViewModel)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -155,16 +151,7 @@ fun SettingsScreen(
|
|||||||
languageList,
|
languageList,
|
||||||
languageIndex
|
languageIndex
|
||||||
) {
|
) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
sharedPreferencesViewModel.updateLanguage(languageEntries[languageList[it].title])
|
||||||
val job = scope.launch(Dispatchers.IO) {
|
|
||||||
val locale = languageEntries[languageList[it].title]
|
|
||||||
accountViewModel.account.settings.preferredLanguage = locale
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
job.join()
|
|
||||||
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(languageEntries[languageList[it].title])
|
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
@@ -175,10 +162,7 @@ fun SettingsScreen(
|
|||||||
themeItens,
|
themeItens,
|
||||||
themeIndex
|
themeIndex
|
||||||
) {
|
) {
|
||||||
themeViewModel.onChange(it)
|
sharedPreferencesViewModel.updateTheme(parseThemeType(it))
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
LocalPreferences.updateTheme(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
@@ -189,12 +173,7 @@ fun SettingsScreen(
|
|||||||
selectedItens,
|
selectedItens,
|
||||||
showImagesIndex
|
showImagesIndex
|
||||||
) {
|
) {
|
||||||
val automaticallyShowImages = parseConnectivityType(it)
|
sharedPreferencesViewModel.updateAutomaticallyShowImages(parseConnectivityType(it))
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
accountViewModel.updateAutomaticallyShowImages(automaticallyShowImages)
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
@@ -205,12 +184,7 @@ fun SettingsScreen(
|
|||||||
selectedItens,
|
selectedItens,
|
||||||
videoIndex
|
videoIndex
|
||||||
) {
|
) {
|
||||||
val automaticallyStartPlayback = parseConnectivityType(it)
|
sharedPreferencesViewModel.updateAutomaticallyStartPlayback(parseConnectivityType(it))
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
accountViewModel.updateAutomaticallyStartPlayback(automaticallyStartPlayback)
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
@@ -221,12 +195,7 @@ fun SettingsScreen(
|
|||||||
selectedItens,
|
selectedItens,
|
||||||
linkIndex
|
linkIndex
|
||||||
) {
|
) {
|
||||||
val automaticallyShowUrlPreview = parseConnectivityType(it)
|
sharedPreferencesViewModel.updateAutomaticallyShowUrlPreview(parseConnectivityType(it))
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
accountViewModel.updateAutomaticallyShowUrlPreview(automaticallyShowUrlPreview)
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsRow(
|
SettingsRow(
|
||||||
@@ -235,12 +204,7 @@ fun SettingsScreen(
|
|||||||
selectedItens,
|
selectedItens,
|
||||||
profilePictureIndex
|
profilePictureIndex
|
||||||
) {
|
) {
|
||||||
val automaticallyShowProfilePicture = parseConnectivityType(it)
|
sharedPreferencesViewModel.updateAutomaticallyShowProfilePicture(parseConnectivityType(it))
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
accountViewModel.updateAutomaticallyShowProfilePicture(automaticallyShowProfilePicture)
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = HalfVertSpacer)
|
Spacer(modifier = HalfVertSpacer)
|
||||||
@@ -251,12 +215,7 @@ fun SettingsScreen(
|
|||||||
booleanItems,
|
booleanItems,
|
||||||
hideNavBarsIndex
|
hideNavBarsIndex
|
||||||
) {
|
) {
|
||||||
val automaticallyHideNavBars = parseBooleanType(it)
|
sharedPreferencesViewModel.updateAutomaticallyHideNavBars(parseBooleanType(it))
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
accountViewModel.updateAutomaticallyHideNavBars(automaticallyHideNavBars)
|
|
||||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedOff
|
package com.vitorpamplona.amethyst.ui.screen.loggedOff
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -92,6 +91,7 @@ fun LoginPage(
|
|||||||
var errorMessage by remember { mutableStateOf("") }
|
var errorMessage by remember { mutableStateOf("") }
|
||||||
val acceptedTerms = remember { mutableStateOf(!isFirstLogin) }
|
val acceptedTerms = remember { mutableStateOf(!isFirstLogin) }
|
||||||
var termsAcceptanceIsRequired by remember { mutableStateOf("") }
|
var termsAcceptanceIsRequired by remember { mutableStateOf("") }
|
||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var dialogOpen by remember {
|
var dialogOpen by remember {
|
||||||
@@ -102,6 +102,7 @@ fun LoginPage(
|
|||||||
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
|
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var loginWithExternalSigner by remember { mutableStateOf(false) }
|
var loginWithExternalSigner by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val activity = rememberLauncherForActivityResult(
|
val activity = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult(),
|
contract = ActivityResultContracts.StartActivityForResult(),
|
||||||
onResult = {
|
onResult = {
|
||||||
@@ -130,10 +131,7 @@ fun LoginPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||||
try {
|
accountViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt(), true) {
|
||||||
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt(), true)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Login", "Could not sign in", e)
|
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
errorMessage = context.getString(R.string.invalid_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,12 +252,20 @@ fun LoginPage(
|
|||||||
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onGo = {
|
onGo = {
|
||||||
try {
|
if (!acceptedTerms.value) {
|
||||||
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
|
termsAcceptanceIsRequired = context.getString(R.string.acceptance_of_terms_is_required)
|
||||||
} catch (e: Exception) {
|
}
|
||||||
|
|
||||||
|
if (key.value.text.isBlank()) {
|
||||||
|
errorMessage = context.getString(R.string.key_is_required)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||||
|
accountViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
errorMessage = context.getString(R.string.invalid_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (errorMessage.isNotBlank()) {
|
if (errorMessage.isNotBlank()) {
|
||||||
@@ -368,10 +374,7 @@ fun LoginPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||||
try {
|
accountViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt()) {
|
||||||
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Login", "Could not sign in", e)
|
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
errorMessage = context.getString(R.string.invalid_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,15 +412,12 @@ fun LoginPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
if (acceptedTerms.value && key.value.text.isNotBlank()) {
|
||||||
try {
|
accountViewModel.login(
|
||||||
accountViewModel.startUI(
|
|
||||||
key.value.text,
|
key.value.text,
|
||||||
useProxy.value,
|
useProxy.value,
|
||||||
proxyPort.value.toInt(),
|
proxyPort.value.toInt(),
|
||||||
true
|
true
|
||||||
)
|
) {
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Login", "Could not sign in", e)
|
|
||||||
errorMessage = context.getString(R.string.invalid_key)
|
errorMessage = context.getString(R.string.invalid_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import androidx.compose.material3.darkColorScheme
|
|||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -31,7 +30,8 @@ import com.halilibo.richtext.ui.RichTextStyle
|
|||||||
import com.halilibo.richtext.ui.resolveDefaults
|
import com.halilibo.richtext.ui.resolveDefaults
|
||||||
import com.patrykandpatrick.vico.compose.style.ChartStyle
|
import com.patrykandpatrick.vico.compose.style.ChartStyle
|
||||||
import com.patrykandpatrick.vico.core.DefaultColors
|
import com.patrykandpatrick.vico.core.DefaultColors
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
|
|
||||||
private val DarkColorPalette = darkColorScheme(
|
private val DarkColorPalette = darkColorScheme(
|
||||||
primary = Purple200,
|
primary = Purple200,
|
||||||
@@ -328,11 +328,10 @@ val ColorScheme.chartStyle: ChartStyle
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AmethystTheme(themeViewModel: ThemeViewModel, content: @Composable () -> Unit) {
|
fun AmethystTheme(sharedPrefsViewModel: SharedPreferencesViewModel, content: @Composable () -> Unit) {
|
||||||
val theme = themeViewModel.theme.observeAsState()
|
val darkTheme = when (sharedPrefsViewModel.sharedPrefs.theme) {
|
||||||
val darkTheme = when (theme.value) {
|
ThemeType.DARK -> true
|
||||||
2 -> true
|
ThemeType.LIGHT -> false
|
||||||
1 -> false
|
|
||||||
else -> isSystemInDarkTheme()
|
else -> isSystemInDarkTheme()
|
||||||
}
|
}
|
||||||
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
|
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
|
||||||
|
|||||||
@@ -149,6 +149,7 @@
|
|||||||
<string name="login">Login</string>
|
<string name="login">Login</string>
|
||||||
<string name="generate_a_new_key">Generate a new key</string>
|
<string name="generate_a_new_key">Generate a new key</string>
|
||||||
<string name="loading_feed">Loading feed</string>
|
<string name="loading_feed">Loading feed</string>
|
||||||
|
<string name="loading_account">Loading account</string>
|
||||||
<string name="error_loading_replies">"Error loading replies: "</string>
|
<string name="error_loading_replies">"Error loading replies: "</string>
|
||||||
<string name="try_again">Try again</string>
|
<string name="try_again">Try again</string>
|
||||||
<string name="feed_is_empty">Feed is empty.</string>
|
<string name="feed_is_empty">Feed is empty.</string>
|
||||||
|
|||||||
+5
@@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.service.notifications
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
@@ -15,6 +16,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
class PushNotificationReceiverService : FirebaseMessagingService() {
|
class PushNotificationReceiverService : FirebaseMessagingService() {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
@@ -23,10 +25,13 @@ class PushNotificationReceiverService : FirebaseMessagingService() {
|
|||||||
// this is called when a message is received
|
// this is called when a message is received
|
||||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val (value, elapsed) = measureTimedValue {
|
||||||
parseMessage(remoteMessage.data)?.let {
|
parseMessage(remoteMessage.data)?.let {
|
||||||
receiveIfNew(it)
|
receiveIfNew(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.d("Time", "Notification processed in $elapsed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
|
private suspend fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
|
||||||
|
|||||||
Reference in New Issue
Block a user