Use screens instead of dialogs

This commit is contained in:
greenart7c3
2024-09-09 09:33:34 -03:00
parent f914d55203
commit 12a347f822
10 changed files with 678 additions and 512 deletions
+24
View File
@@ -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,
)
}