mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Use screens instead of dialogs
This commit is contained in:
+24
@@ -1,6 +1,30 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
*/
|
||||
package com.greenart7c3.nostrsigner.service
|
||||
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.checkNotInMainThread
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.ui.NotificationType
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.relays.NostrDataSource
|
||||
@@ -36,9 +36,14 @@ import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object NotificationDataSource : NostrDataSource("AccountData") {
|
||||
private val eventNotificationConsumer = EventNotificationConsumer(NostrSigner.getInstance())
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
private val clientListener =
|
||||
object : Client.Listener {
|
||||
@@ -136,8 +141,19 @@ object NotificationDataSource : NostrDataSource("AccountData") {
|
||||
}
|
||||
|
||||
init {
|
||||
Log.d(this.javaClass.simpleName, "${this.javaClass.simpleName} Subscribe")
|
||||
Client.subscribe(clientListener)
|
||||
scope.launch {
|
||||
LocalPreferences.loadSettingsFromEncryptedStorage()
|
||||
if (NostrSigner.getInstance().settings.notificationType == NotificationType.DIRECT) {
|
||||
Client.subscribe(clientListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if (!Client.isSubscribed(clientListener)) {
|
||||
Client.subscribe(clientListener)
|
||||
}
|
||||
super.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import java.io.IOException
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
|
||||
@Composable
|
||||
fun LanguageScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
account: Account,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val languageEntries = remember { context.getLangPreferenceDropdownEntries() }
|
||||
val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() }
|
||||
val languageIndex = getLanguageIndex(languageEntries, account.language)
|
||||
|
||||
Surface(
|
||||
modifier.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
Column {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SettingsRow(
|
||||
R.string.language,
|
||||
R.string.language_description,
|
||||
languageList,
|
||||
languageIndex,
|
||||
) {
|
||||
account.language = languageEntries[languageList[it].title]
|
||||
LocalPreferences.saveToEncryptedStorage(context, account)
|
||||
AppCompatDelegate.setApplicationLocales(
|
||||
LocaleListCompat.forLanguageTags(account.language),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getLocaleListFromXml(): LocaleListCompat {
|
||||
val tagsList = mutableListOf<CharSequence>()
|
||||
try {
|
||||
val xpp: XmlPullParser = resources.getXml(R.xml.locales_config)
|
||||
while (xpp.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (xpp.eventType == XmlPullParser.START_TAG) {
|
||||
if (xpp.name == "locale") {
|
||||
tagsList.add(xpp.getAttributeValue(0))
|
||||
}
|
||||
}
|
||||
xpp.next()
|
||||
}
|
||||
} catch (e: XmlPullParserException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return LocaleListCompat.forLanguageTags(tagsList.joinToString(","))
|
||||
}
|
||||
|
||||
fun Context.getLangPreferenceDropdownEntries(): ImmutableMap<String, String> {
|
||||
val localeList = getLocaleListFromXml()
|
||||
val map = mutableMapOf<String, String>()
|
||||
|
||||
for (a in 0 until localeList.size()) {
|
||||
localeList[a].let {
|
||||
map.put(
|
||||
it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() },
|
||||
it.toLanguageTag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
return map.toImmutableMap()
|
||||
}
|
||||
|
||||
fun getLanguageIndex(
|
||||
languageEntries: ImmutableMap<String, String>,
|
||||
selectedLanguage: String?,
|
||||
): Int {
|
||||
var languageIndex: Int
|
||||
languageIndex =
|
||||
if (selectedLanguage != null) {
|
||||
languageEntries.values.toTypedArray().indexOf(selectedLanguage)
|
||||
} else {
|
||||
languageEntries.values.toTypedArray().indexOf(Locale.current.toLanguageTag())
|
||||
}
|
||||
if (languageIndex == -1) {
|
||||
languageIndex = languageEntries.values.toTypedArray().indexOf(Locale.current.language)
|
||||
}
|
||||
if (languageIndex == -1) languageIndex = languageEntries.values.toTypedArray().indexOf("en")
|
||||
return languageIndex
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun LogsScreen(
|
||||
account: Account,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
Surface(modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
val logsFlow = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().getLogs()
|
||||
val logs = logsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
|
||||
LazyColumn(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
items(logs.value.size) { index ->
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(6.dp),
|
||||
) {
|
||||
Column(Modifier.padding(6.dp)) {
|
||||
val log = logs.value[index]
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Date: ")
|
||||
}
|
||||
append(TimeUtils.convertLongToDateTime(log.time))
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("URL: ")
|
||||
}
|
||||
append(log.url)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Type: ")
|
||||
}
|
||||
append(log.type)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Message: ")
|
||||
}
|
||||
append(log.message)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.clear_logs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,8 @@ import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
|
||||
import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen
|
||||
import com.greenart7c3.nostrsigner.ui.actions.DefaultRelaysScreen
|
||||
import com.greenart7c3.nostrsigner.ui.components.BackButtonScaffold
|
||||
import com.greenart7c3.nostrsigner.ui.components.MainScaffold
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
@@ -757,5 +759,89 @@ fun MainScreen(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.Logs.route,
|
||||
content = {
|
||||
BackButtonScaffold(
|
||||
navController = navController,
|
||||
) { padding ->
|
||||
LogsScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
account = account,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.ActiveRelays.route,
|
||||
content = {
|
||||
BackButtonScaffold(
|
||||
navController = navController,
|
||||
) { padding ->
|
||||
ActiveRelaysScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.Language.route,
|
||||
content = {
|
||||
BackButtonScaffold(
|
||||
navController = navController,
|
||||
) { padding ->
|
||||
LanguageScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
account = account,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.NotificationType.route,
|
||||
content = {
|
||||
BackButtonScaffold(
|
||||
navController = navController,
|
||||
) { padding ->
|
||||
NotificationTypeScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.DefaultRelays.route,
|
||||
content = {
|
||||
BackButtonScaffold(
|
||||
navController = navController,
|
||||
) { padding ->
|
||||
DefaultRelaysScreen(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.greenart7c3.nostrsigner.ui.components.PostButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun NotificationTypeScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavController,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var notificationItemsIndex by remember {
|
||||
mutableIntStateOf(NostrSigner.getInstance().settings.notificationType.screenCode)
|
||||
}
|
||||
val notificationItems =
|
||||
persistentListOf(
|
||||
TitleExplainer(stringResource(NotificationType.PUSH.resourceId)),
|
||||
TitleExplainer(stringResource(NotificationType.DIRECT.resourceId)),
|
||||
)
|
||||
Surface(
|
||||
modifier.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SettingsRow(
|
||||
R.string.notification_type,
|
||||
R.string.select_the_type_of_notification_you_want_to_receive,
|
||||
notificationItems,
|
||||
notificationItemsIndex,
|
||||
) {
|
||||
notificationItemsIndex = it
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR == "free") {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
PushNotificationSettingsRow()
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
PostButton(
|
||||
isActive = true,
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
notificationType = parseNotificationType(notificationItemsIndex),
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
PushNotificationUtils.hasInit = false
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
||||
if (notificationItemsIndex == 0) {
|
||||
NotificationDataSource.stopSync()
|
||||
RelayPool.disconnect()
|
||||
} else {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||
Intent(
|
||||
NostrSigner.getInstance().applicationContext,
|
||||
ConnectivityService::class.java,
|
||||
),
|
||||
)
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -16,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -31,8 +27,6 @@ import androidx.compose.material.icons.filled.Notifications
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.SurroundSound
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -51,34 +45,22 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.service.Biometrics
|
||||
import com.greenart7c3.nostrsigner.service.ConnectivityService
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.service.PushNotificationUtils
|
||||
import com.greenart7c3.nostrsigner.ui.actions.ConnectOrbotDialog
|
||||
import com.greenart7c3.nostrsigner.ui.actions.EditDefaultRelaysDialog
|
||||
import com.greenart7c3.nostrsigner.ui.actions.LogoutDialog
|
||||
import com.greenart7c3.nostrsigner.ui.components.CloseButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.HyperlinkText
|
||||
@@ -87,72 +69,11 @@ import com.greenart7c3.nostrsigner.ui.components.PostButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.TextSpinner
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import java.io.IOException
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
|
||||
fun Context.getLocaleListFromXml(): LocaleListCompat {
|
||||
val tagsList = mutableListOf<CharSequence>()
|
||||
try {
|
||||
val xpp: XmlPullParser = resources.getXml(R.xml.locales_config)
|
||||
while (xpp.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (xpp.eventType == XmlPullParser.START_TAG) {
|
||||
if (xpp.name == "locale") {
|
||||
tagsList.add(xpp.getAttributeValue(0))
|
||||
}
|
||||
}
|
||||
xpp.next()
|
||||
}
|
||||
} catch (e: XmlPullParserException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return LocaleListCompat.forLanguageTags(tagsList.joinToString(","))
|
||||
}
|
||||
|
||||
fun Context.getLangPreferenceDropdownEntries(): ImmutableMap<String, String> {
|
||||
val localeList = getLocaleListFromXml()
|
||||
val map = mutableMapOf<String, String>()
|
||||
|
||||
for (a in 0 until localeList.size()) {
|
||||
localeList[a].let {
|
||||
map.put(
|
||||
it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() },
|
||||
it.toLanguageTag(),
|
||||
)
|
||||
}
|
||||
}
|
||||
return map.toImmutableMap()
|
||||
}
|
||||
|
||||
fun getLanguageIndex(
|
||||
languageEntries: ImmutableMap<String, String>,
|
||||
selectedLanguage: String?,
|
||||
): Int {
|
||||
var languageIndex: Int
|
||||
languageIndex =
|
||||
if (selectedLanguage != null) {
|
||||
languageEntries.values.toTypedArray().indexOf(selectedLanguage)
|
||||
} else {
|
||||
languageEntries.values.toTypedArray().indexOf(Locale.current.toLanguageTag())
|
||||
}
|
||||
if (languageIndex == -1) {
|
||||
languageIndex = languageEntries.values.toTypedArray().indexOf(Locale.current.language)
|
||||
}
|
||||
if (languageIndex == -1) languageIndex = languageEntries.values.toTypedArray().indexOf("en")
|
||||
return languageIndex
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
@@ -167,21 +88,10 @@ fun SettingsScreen(
|
||||
var disconnectTorDialog by remember { mutableStateOf(false) }
|
||||
var conectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||
val proxyPort = remember { mutableStateOf(account.proxyPort.toString()) }
|
||||
val languageEntries = remember { context.getLangPreferenceDropdownEntries() }
|
||||
val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() }
|
||||
val languageIndex = getLanguageIndex(languageEntries, account.language)
|
||||
var languageDialog by remember { mutableStateOf(false) }
|
||||
var logDialog by remember { mutableStateOf(false) }
|
||||
var relayDialog by remember { mutableStateOf(false) }
|
||||
var allowNewConnections by remember { mutableStateOf(account.allowNewConnections) }
|
||||
var signatureDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val notificationItems =
|
||||
persistentListOf(
|
||||
TitleExplainer(stringResource(NotificationType.PUSH.resourceId)),
|
||||
TitleExplainer(stringResource(NotificationType.DIRECT.resourceId)),
|
||||
)
|
||||
|
||||
val biometricItems =
|
||||
persistentListOf(
|
||||
@@ -191,28 +101,8 @@ fun SettingsScreen(
|
||||
TitleExplainer(stringResource(BiometricsTimeType.TEN_MINUTES.resourceId)),
|
||||
)
|
||||
|
||||
var notificationTypeDialog by remember { mutableStateOf(false) }
|
||||
var securityDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (relayDialog) {
|
||||
EditDefaultRelaysDialog(
|
||||
onClose = {
|
||||
relayDialog = false
|
||||
},
|
||||
onPost = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (it.isNotEmpty()) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
defaultRelays = it,
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
}
|
||||
relayDialog = false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (signatureDialog) {
|
||||
val radioOptions = listOf(
|
||||
TitleExplainer(
|
||||
@@ -310,222 +200,6 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (logDialog) {
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
logDialog = false
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
CloseButton {
|
||||
logDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
val logsFlow = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().getLogs()
|
||||
val logs = logsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
|
||||
LazyColumn(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
items(logs.value.size) { index ->
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(6.dp),
|
||||
) {
|
||||
Column(Modifier.padding(6.dp)) {
|
||||
val log = logs.value[index]
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Date: ")
|
||||
}
|
||||
append(TimeUtils.convertLongToDateTime(log.time))
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("URL: ")
|
||||
}
|
||||
append(log.url)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Type: ")
|
||||
}
|
||||
append(log.type)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append("Message: ")
|
||||
}
|
||||
append(log.message)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub()).applicationDao().clearLogs()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.clear_logs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationTypeDialog) {
|
||||
var notificationItemsIndex by remember {
|
||||
mutableIntStateOf(NostrSigner.getInstance().settings.notificationType.screenCode)
|
||||
}
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
notificationTypeDialog = false
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
CloseButton {
|
||||
notificationTypeDialog = false
|
||||
}
|
||||
|
||||
PostButton(
|
||||
isActive = true,
|
||||
) {
|
||||
notificationTypeDialog = false
|
||||
scope.launch(Dispatchers.IO) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
notificationType = parseNotificationType(notificationItemsIndex),
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
PushNotificationUtils.hasInit = false
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts(context))
|
||||
if (notificationItemsIndex == 0) {
|
||||
NotificationDataSource.stopSync()
|
||||
RelayPool.disconnect()
|
||||
} else {
|
||||
NostrSigner.getInstance().checkForNewRelays()
|
||||
NotificationDataSource.start()
|
||||
}
|
||||
NostrSigner.getInstance().applicationContext.startForegroundService(
|
||||
Intent(
|
||||
NostrSigner.getInstance().applicationContext,
|
||||
ConnectivityService::class.java,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SettingsRow(
|
||||
R.string.notification_type,
|
||||
R.string.select_the_type_of_notification_you_want_to_receive,
|
||||
notificationItems,
|
||||
notificationItemsIndex,
|
||||
) {
|
||||
notificationItemsIndex = it
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR == "free") {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
PushNotificationSettingsRow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (languageDialog) {
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
languageDialog = false
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
CloseButton {
|
||||
languageDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SettingsRow(
|
||||
R.string.language,
|
||||
R.string.language_description,
|
||||
languageList,
|
||||
languageIndex,
|
||||
) {
|
||||
account.language = languageEntries[languageList[it].title]
|
||||
LocalPreferences.saveToEncryptedStorage(context, account)
|
||||
AppCompatDelegate.setApplicationLocales(
|
||||
LocaleListCompat.forLanguageTags(account.language),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (securityDialog) {
|
||||
var biometricsIndex by remember {
|
||||
mutableIntStateOf(NostrSigner.getInstance().settings.biometricsTimeType.screenCode)
|
||||
@@ -658,7 +332,7 @@ fun SettingsScreen(
|
||||
icon = Icons.Default.Language,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
languageDialog = true
|
||||
navController.navigate(Route.Language.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -674,7 +348,7 @@ fun SettingsScreen(
|
||||
icon = Icons.Default.Notifications,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
notificationTypeDialog = true
|
||||
navController.navigate(Route.NotificationType.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -714,7 +388,7 @@ fun SettingsScreen(
|
||||
icon = Icons.Default.Hub,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
relayDialog = true
|
||||
navController.navigate(Route.DefaultRelays.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -744,7 +418,7 @@ fun SettingsScreen(
|
||||
icon = Icons.Default.FilterList,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
logDialog = true
|
||||
navController.navigate(Route.Logs.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -49,6 +50,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
@@ -61,6 +63,7 @@ import com.vitorpamplona.ammolite.relays.RelayPool
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
@@ -239,10 +242,11 @@ fun EditRelaysDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditDefaultRelaysDialog(
|
||||
onClose: () -> Unit,
|
||||
onPost: (SnapshotStateList<RelaySetupInfo>) -> Unit,
|
||||
fun DefaultRelaysScreen(
|
||||
modifier: Modifier,
|
||||
navController: NavController,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var textFieldRelay by remember {
|
||||
mutableStateOf(TextFieldValue(""))
|
||||
}
|
||||
@@ -256,126 +260,38 @@ fun EditDefaultRelaysDialog(
|
||||
}
|
||||
localRelays
|
||||
}
|
||||
Dialog(
|
||||
onDismissRequest = onClose,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Surface(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize(),
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CloseButton {
|
||||
onClose()
|
||||
}
|
||||
PostButton(
|
||||
isActive = relays2.isNotEmpty(),
|
||||
onPost = {
|
||||
onPost(relays2)
|
||||
},
|
||||
)
|
||||
}
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
items(relays2.size) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
relays2[it].url,
|
||||
Modifier
|
||||
.weight(0.9f)
|
||||
.padding(8.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
relays2.removeAt(it)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
stringResource(R.string.delete),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.9f)
|
||||
.padding(horizontal = 16.dp),
|
||||
value = textFieldRelay.text,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
onValueChange = {
|
||||
textFieldRelay = TextFieldValue(it)
|
||||
},
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
val url = textFieldRelay.text
|
||||
if (url.isNotBlank() && url != "/") {
|
||||
var addedWSS =
|
||||
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||
if (url.endsWith(".onion") || url.endsWith(".onion/")) {
|
||||
"ws://$url"
|
||||
} else {
|
||||
"wss://$url"
|
||||
}
|
||||
} else {
|
||||
url
|
||||
}
|
||||
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||
if (relays2.any { it.url == addedWSS }) {
|
||||
textFieldRelay = TextFieldValue("")
|
||||
return@KeyboardActions
|
||||
}
|
||||
relays2.add(
|
||||
RelaySetupInfo(
|
||||
addedWSS,
|
||||
read = true,
|
||||
write = true,
|
||||
feedTypes = COMMON_FEED_TYPES,
|
||||
),
|
||||
)
|
||||
textFieldRelay = TextFieldValue("")
|
||||
}
|
||||
},
|
||||
),
|
||||
label = {
|
||||
Text("Relay")
|
||||
},
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
.fillMaxWidth(0.9f)
|
||||
.padding(horizontal = 16.dp),
|
||||
value = textFieldRelay.text,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
onValueChange = {
|
||||
textFieldRelay = TextFieldValue(it)
|
||||
},
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
val url = textFieldRelay.text
|
||||
if (url.isNotBlank() && url != "/") {
|
||||
var addedWSS =
|
||||
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||
// TODO: How to identify relays on the local network?
|
||||
val isLocalHost = url.contains("127.0.0.1") || url.contains("localhost")
|
||||
if (url.endsWith(".onion") || url.endsWith(".onion/") || isLocalHost) {
|
||||
if (url.endsWith(".onion") || url.endsWith(".onion/")) {
|
||||
"ws://$url"
|
||||
} else {
|
||||
"wss://$url"
|
||||
@@ -386,7 +302,7 @@ fun EditDefaultRelaysDialog(
|
||||
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||
if (relays2.any { it.url == addedWSS }) {
|
||||
textFieldRelay = TextFieldValue("")
|
||||
return@IconButton
|
||||
return@KeyboardActions
|
||||
}
|
||||
relays2.add(
|
||||
RelaySetupInfo(
|
||||
@@ -399,11 +315,104 @@ fun EditDefaultRelaysDialog(
|
||||
textFieldRelay = TextFieldValue("")
|
||||
}
|
||||
},
|
||||
),
|
||||
label = {
|
||||
Text("Relay")
|
||||
},
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
val url = textFieldRelay.text
|
||||
if (url.isNotBlank() && url != "/") {
|
||||
var addedWSS =
|
||||
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||
// TODO: How to identify relays on the local network?
|
||||
val isLocalHost = url.contains("127.0.0.1") || url.contains("localhost")
|
||||
if (url.endsWith(".onion") || url.endsWith(".onion/") || isLocalHost) {
|
||||
"ws://$url"
|
||||
} else {
|
||||
"wss://$url"
|
||||
}
|
||||
} else {
|
||||
url
|
||||
}
|
||||
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||
if (relays2.any { it.url == addedWSS }) {
|
||||
textFieldRelay = TextFieldValue("")
|
||||
return@IconButton
|
||||
}
|
||||
relays2.add(
|
||||
RelaySetupInfo(
|
||||
addedWSS,
|
||||
read = true,
|
||||
write = true,
|
||||
feedTypes = COMMON_FEED_TYPES,
|
||||
),
|
||||
)
|
||||
textFieldRelay = TextFieldValue("")
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PostButton(
|
||||
isActive = relays2.isNotEmpty(),
|
||||
onPost = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (relays2.isNotEmpty()) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
defaultRelays = relays2,
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
}
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigateUp()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
items(relays2.size) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
null,
|
||||
Text(
|
||||
relays2[it].url,
|
||||
Modifier
|
||||
.weight(0.9f)
|
||||
.padding(8.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
relays2.removeAt(it)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
stringResource(R.string.delete),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,8 +502,8 @@ fun RelayLogDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActiveRelaysDialog(
|
||||
onClose: () -> Unit,
|
||||
fun ActiveRelaysScreen(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val relays2 =
|
||||
remember {
|
||||
@@ -520,60 +529,44 @@ fun ActiveRelaysDialog(
|
||||
)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onClose,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Surface(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize(),
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxHeight(0.9f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CloseButton {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxHeight(0.9f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
items(relays2.size) {
|
||||
Row(
|
||||
items(relays2.size) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(6.dp)
|
||||
.clickable {
|
||||
relayLogUrl = relays2[it].url
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Circle,
|
||||
contentDescription = "Active Relay",
|
||||
tint = if (RelayPool.getRelay(relays2[it].url)?.isConnected() == true) Color.Green else Color.Red,
|
||||
)
|
||||
Text(
|
||||
relays2[it].url,
|
||||
Modifier
|
||||
.padding(6.dp)
|
||||
.clickable {
|
||||
relayLogUrl = relays2[it].url
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Circle,
|
||||
contentDescription = "Active Relay",
|
||||
tint = if (RelayPool.getRelay(relays2[it].url)?.isConnected() == true) Color.Green else Color.Red,
|
||||
)
|
||||
Text(
|
||||
relays2[it].url,
|
||||
Modifier
|
||||
.weight(0.9f)
|
||||
.padding(8.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
.weight(0.9f)
|
||||
.padding(8.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import com.greenart7c3.nostrsigner.service.toShortenHex
|
||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||
import com.greenart7c3.nostrsigner.ui.PermissionsFloatingActionButton
|
||||
import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet
|
||||
import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysDialog
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
@@ -135,16 +134,6 @@ fun MainScaffold(
|
||||
actions = {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.FLAVOR != "offline") {
|
||||
var showDialogRelay by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialogRelay) {
|
||||
ActiveRelaysDialog(
|
||||
onClose = {
|
||||
showDialogRelay = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
@@ -153,7 +142,7 @@ fun MainScaffold(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
) {
|
||||
showDialogRelay = true
|
||||
navController.navigate(Route.ActiveRelays.route)
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
||||
@@ -53,4 +53,39 @@ sealed class Route(
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object Logs : Route(
|
||||
title = "",
|
||||
route = "Logs",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object ActiveRelays : Route(
|
||||
title = "",
|
||||
route = "ActiveRelays",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object Language : Route(
|
||||
title = "",
|
||||
route = "Language",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object NotificationType : Route(
|
||||
title = "",
|
||||
route = "NotificationType",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object DefaultRelays : Route(
|
||||
title = "",
|
||||
route = "DefaultRelays",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user