use material 3, created permissions and settings screen, added more

build targets
This commit is contained in:
greenart7c3
2023-09-20 10:48:11 -03:00
parent b886b6d657
commit 77a3ea4d1a
38 changed files with 1399 additions and 850 deletions
+22
View File
@@ -93,6 +93,28 @@ jobs:
asset_name: amber-x86_64-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload APK arm64-v8a Asset
id: upload-release-asset-arm64-v8a-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/release/app-play-arm64-v8a-release-unsigned-signed.apk
asset_name: amber-arm64-v8a-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload APK armeabi-v7a Asset
id: upload-release-asset-armeabi-v7a-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/release/app-play-armeabi-v7a-release-unsigned-signed.apk
asset_name: amber-armeabi-v7a-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload AAB Asset
id: upload-release-asset-aab
uses: actions/upload-release-asset@v1
+4 -4
View File
@@ -38,7 +38,7 @@ android {
abi {
enable true
reset()
include "x86", "x86_64"
include "x86", "x86_64", "arm64-v8a", "armeabi-v7a"
universalApk true
}
}
@@ -74,7 +74,7 @@ dependencies {
implementation 'net.java.dev.jna:jna:5.13.0@aar'
implementation "androidx.core:core-splashscreen:1.0.1"
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
@@ -87,7 +87,7 @@ dependencies {
// Observe Live data as State
implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version"
implementation "androidx.compose.material:material:$compose_ui_version"
implementation "androidx.compose.material3:material3:1.1.1"
implementation "androidx.compose.material:material-icons-extended:$compose_ui_version"
implementation 'androidx.appcompat:appcompat:1.6.1'
@@ -119,7 +119,7 @@ dependencies {
// Markdown (With fix for full-image bleeds)
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui:a0954aba63')
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui-material:a0954aba63')
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui-material3:a0954aba63')
implementation('com.github.vitorpamplona.compose-richtext:richtext-commonmark:a0954aba63')
// Biometrics
+1
View File
@@ -14,6 +14,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.SignerSplash"
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true"
tools:targetApi="33">
<provider
android:name=".SignerProvider"
@@ -157,6 +157,7 @@ object LocalPreferences {
prefs.edit().apply {
remove(PrefKeys.REMEMBER_APPS)
}.apply()
account.savedApps = mutableMapOf()
}
fun saveToEncryptedStorage(account: Account) {
@@ -4,8 +4,8 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -24,7 +24,7 @@ class MainActivity : AppCompatActivity() {
NostrSignerTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
color = MaterialTheme.colorScheme.background
) {
val accountStateViewModel: AccountStateViewModel = viewModel {
AccountStateViewModel()
@@ -8,7 +8,7 @@ import com.greenart7c3.nostrsigner.service.KeyPair
@Stable
class Account(
val keyPair: KeyPair,
val savedApps: MutableMap<String, Boolean>
var savedApps: MutableMap<String, Boolean>
) {
val saveable: AccountLiveData = AccountLiveData(this)
}
@@ -20,7 +20,8 @@ fun AccountScreen(
Column {
Crossfade(
targetState = accountState,
animationSpec = tween(durationMillis = 100)
animationSpec = tween(durationMillis = 100),
label = "AccountScreen"
) { state ->
when (state) {
is AccountState.LoggedOff -> {
@@ -29,7 +30,7 @@ fun AccountScreen(
is AccountState.LoggedIn -> {
val intentData = IntentUtils.getIntentData(intent)
MainScreen(state.account, accountStateViewModel, intentData, packageName)
MainScreen(state.account, accountStateViewModel, intentData, packageName, state.route)
}
}
}
@@ -4,5 +4,5 @@ import com.greenart7c3.nostrsigner.models.Account
sealed class AccountState {
object LoggedOff : AccountState()
class LoggedIn(val account: Account) : AccountState()
class LoggedIn(val account: Account, val route: String?) : AccountState()
}
@@ -21,12 +21,12 @@ class AccountStateViewModel : ViewModel() {
val accountContent = _accountContent.asStateFlow()
init {
tryLoginExistingAccount()
tryLoginExistingAccount(null)
}
private fun tryLoginExistingAccount() {
private fun tryLoginExistingAccount(route: String?) {
LocalPreferences.loadFromEncryptedStorage()?.let {
startUI(it)
startUI(it, route)
}
}
@@ -47,10 +47,16 @@ class AccountStateViewModel : ViewModel() {
fun logOff(npub: String) {
prepareLogoutOrSwitch()
LocalPreferences.updatePrefsForLogout(npub)
tryLoginExistingAccount()
tryLoginExistingAccount(null)
}
fun startUI(key: String) {
fun switchUser(npub: String, route: String?) {
prepareLogoutOrSwitch()
LocalPreferences.switchToAccount(npub)
tryLoginExistingAccount(route)
}
fun startUI(key: String, route: String?) {
val account = if (key.startsWith("nsec")) {
Account(KeyPair(privKey = key.bechToBytes()), mutableMapOf())
} else {
@@ -58,18 +64,18 @@ class AccountStateViewModel : ViewModel() {
}
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
startUI(account, route)
}
fun newKey() {
val account = Account(KeyPair(), mutableMapOf())
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
startUI(account, null)
}
@OptIn(DelicateCoroutinesApi::class)
fun startUI(account: Account) {
_accountContent.update { AccountState.LoggedIn(account) }
fun startUI(account: Account, route: String?) {
_accountContent.update { AccountState.LoggedIn(account, route) }
GlobalScope.launch(Dispatchers.Main) {
account.saveable.observeForever(saveListener)
@@ -0,0 +1,243 @@
package com.greenart7c3.nostrsigner.ui
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.ui.components.EncryptDecryptData
import com.greenart7c3.nostrsigner.ui.components.EventData
import com.greenart7c3.nostrsigner.ui.components.LoginWithPubKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun HomeScreen(
modifier: Modifier,
json: IntentData?,
packageName: String?,
account: Account
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
Box(
modifier
) {
if (json == null) {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text("No event to sign")
}
} else {
json.let {
var key = "$packageName-${it.type}"
val appName = packageName ?: it.name
when (it.type) {
SignerType.GET_PUBLIC_KEY -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
LoginWithPubKey(
shouldRunOnAccept,
remember,
packageName,
appName,
{
val sig = account.keyPair.pubKey.toNpub()
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
"",
"",
sig
)
}
return@LoginWithPubKey
},
{
context.getAppCompatActivity()?.finish()
}
)
}
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EncryptDecryptData(
shouldRunOnAccept,
remember,
packageName,
appName,
it.type,
{
try {
coroutineScope.launch(Dispatchers.IO) {
val sig = try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
)
?: context.getString(R.string.could_not_decrypt_the_message)
} catch (e: Exception) {
context.getString(R.string.could_not_decrypt_the_message)
}
val result =
if (sig == context.getString(R.string.could_not_decrypt_the_message) && (it.type == SignerType.DECRYPT_ZAP_EVENT)) {
""
} else {
sig
}
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
"",
it.id,
result
)
}
return@EncryptDecryptData
} catch (e: Exception) {
val message = if (it.type.toString().contains("ENCRYPT", true)) {
"encrypt"
} else {
"decrypt"
}
coroutineScope.launch {
Toast.makeText(
context,
"Error to $message data",
Toast.LENGTH_SHORT
).show()
}
return@EncryptDecryptData
}
},
{
context.getAppCompatActivity()?.finish()
}
)
}
else -> {
val event =
IntentUtils.getIntent(it.data, account.keyPair)
key = "$packageName-${it.type}-${event.kind}"
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EventData(
shouldRunOnAccept,
remember,
packageName,
appName,
event,
event.toJson(),
{
if (event.pubKey != account.keyPair.pubKey.toHexKey()) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.event_pubkey_is_not_equal_to_current_logged_in_user),
Toast.LENGTH_SHORT
).show()
}
return@EventData
}
val localEvent =
Event.fromJson(
it.data
)
if (localEvent is LnZapRequestEvent && localEvent.tags.any { tag -> tag.any { t -> t == "anon" } }) {
val resultEvent =
AmberUtils.getZapRequestEvent(
localEvent,
account.keyPair.privKey
)
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
resultEvent.toJson(),
it.id,
resultEvent.toJson()
)
}
} else {
val signedEvent = AmberUtils.getSignedEvent(
event,
account.keyPair.privKey
)
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
signedEvent.toJson(),
it.id,
signedEvent.sig
)
}
}
},
{
context.getAppCompatActivity()?.finish()
}
)
}
}
}
}
}
}
@@ -0,0 +1,27 @@
package com.greenart7c3.nostrsigner.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
@Composable
fun rememberLifecycleEvent(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current): Lifecycle.Event {
var state by remember { mutableStateOf(Lifecycle.Event.ON_ANY) }
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
state = event
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
return state
}
@@ -17,16 +17,15 @@ import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -56,7 +55,6 @@ import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.ui.theme.Font14SP
import com.greenart7c3.nostrsigner.ui.theme.Size35dp
import com.greenart7c3.nostrsigner.ui.theme.placeholderText
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@@ -122,8 +120,7 @@ fun LoginPage(
),
placeholder = {
Text(
text = "nsec..",
color = MaterialTheme.colors.placeholderText
text = "nsec.."
)
},
trailingIcon = {
@@ -154,7 +151,7 @@ fun LoginPage(
painter = painterResource(R.drawable.ic_qrcode),
null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colors.primary
tint = MaterialTheme.colorScheme.primary
)
}
},
@@ -162,7 +159,7 @@ fun LoginPage(
keyboardActions = KeyboardActions(
onGo = {
try {
accountViewModel.startUI(key.value.text)
accountViewModel.startUI(key.value.text, null)
} catch (e: Exception) {
errorMessage = "Invalid key"
}
@@ -172,8 +169,8 @@ fun LoginPage(
if (errorMessage.isNotBlank()) {
Text(
text = errorMessage,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall
)
}
@@ -188,7 +185,7 @@ fun LoginPage(
if (key.value.text.isNotBlank()) {
try {
accountViewModel.startUI(key.value.text)
accountViewModel.startUI(key.value.text, null)
} catch (e: Exception) {
Log.e("Login", "Could not sign in", e)
errorMessage = "Invalid key"
@@ -198,11 +195,7 @@ fun LoginPage(
shape = RoundedCornerShape(Size35dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
colors = ButtonDefaults
.buttonColors(
backgroundColor = MaterialTheme.colors.primary
)
.height(50.dp)
) {
Text(text = "Login")
}
@@ -221,7 +214,7 @@ fun LoginPage(
style = TextStyle(
fontSize = Font14SP,
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colors.primary,
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center
)
)
@@ -4,76 +4,35 @@ import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.foundation.clickable
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.Spacer
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Divider
import androidx.compose.material.DrawerValue
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Scaffold
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccessTime
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.rememberDrawerState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.service.model.Event
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.components.Drawer
import com.greenart7c3.nostrsigner.ui.components.MainAppBar
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONObject
import com.greenart7c3.nostrsigner.ui.navigation.Route
import kotlinx.coroutines.delay
fun sendResult(
context: Context,
@@ -88,7 +47,9 @@ fun sendResult(
) {
val activity = context.getAppCompatActivity()
if (packageName != null) {
account.savedApps[key] = rememberChoice
if (rememberChoice) {
account.savedApps[key] = rememberChoice
}
LocalPreferences.saveToEncryptedStorage(account)
val intent = Intent()
intent.putExtra("signature", value)
@@ -106,568 +67,101 @@ fun sendResult(
activity?.finish()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, json: IntentData?, packageName: String?) {
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, json: IntentData?, packageName: String?, route: String?) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val items = listOf(Route.Home, Route.Permissions, Route.Settings)
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
Drawer(
accountStateViewModel = accountStateViewModel,
account = account,
scaffoldState = scaffoldState
topBar = {
CenterAlignedTopAppBar(
title = { Text(text = "Amber") }
)
},
topBar = {
MainAppBar(
scaffoldState,
accountStateViewModel,
account
)
}
) {
Box(
Modifier
.fillMaxSize()
.padding(it)
) {
if (json == null) {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text("No event to sign")
}
} else {
json.let {
var key = "$packageName-${it.type}"
val appName = packageName ?: it.name
when (it.type) {
SignerType.GET_PUBLIC_KEY -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
bottomBar = {
NavigationBar {
val currentRoute = navBackStackEntry?.destination?.route
items.forEach {
val selected = currentRoute == it.route
NavigationBarItem(
selected = selected,
onClick = {
navController.navigate(it.route) {
popUpTo(0)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
LoginWithPubKey(
shouldRunOnAccept,
remember,
packageName,
appName,
{
val sig = account.keyPair.pubKey.toNpub()
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
"",
"",
sig
)
}
return@LoginWithPubKey
},
{
context.getAppCompatActivity()?.finish()
}
},
icon = {
Icon(
if (selected) it.selectedIcon else it.icon,
it.route
)
},
label = {
Text(it.route)
}
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EncryptDecryptData(
shouldRunOnAccept,
remember,
packageName,
appName,
it.type,
{
try {
coroutineScope.launch(Dispatchers.IO) {
val sig = try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
) ?: context.getString(R.string.could_not_decrypt_the_message)
} catch (e: Exception) {
context.getString(R.string.could_not_decrypt_the_message)
}
val result = if (sig == context.getString(R.string.could_not_decrypt_the_message) && (it.type == SignerType.DECRYPT_ZAP_EVENT)) {
""
} else {
sig
}
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
"",
it.id,
result
)
}
return@EncryptDecryptData
} catch (e: Exception) {
val message = if (it.type.toString().contains("ENCRYPT", true)) "encrypt" else "decrypt"
coroutineScope.launch {
Toast.makeText(
context,
"Error to $message data",
Toast.LENGTH_SHORT
).show()
}
return@EncryptDecryptData
}
},
{
context.getAppCompatActivity()?.finish()
}
)
}
else -> {
val event = IntentUtils.getIntent(it.data, account.keyPair)
key = "$packageName-${it.type}-${event.kind}"
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EventData(
shouldRunOnAccept,
remember,
packageName,
appName,
event,
event.toJson(),
{
if (event.pubKey != account.keyPair.pubKey.toHexKey()) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.event_pubkey_is_not_equal_to_current_logged_in_user),
Toast.LENGTH_SHORT
).show()
}
return@EventData
}
val localEvent = com.vitorpamplona.quartz.events.Event.fromJson(it.data)
if (localEvent is LnZapRequestEvent && localEvent.tags.any { tag -> tag.any { t -> t == "anon" } }) {
val resultEvent = AmberUtils.getZapRequestEvent(localEvent, account.keyPair.privKey)
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
resultEvent.toJson(),
it.id,
resultEvent.toJson()
)
}
} else {
val signedEvent = AmberUtils.getSignedEvent(event, account.keyPair.privKey)
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
signedEvent.toJson(),
it.id,
signedEvent.sig
)
}
}
},
{
context.getAppCompatActivity()?.finish()
}
)
}
}
)
}
}
}
}
}
@Composable
fun AppTitle(appName: String) {
Text(
modifier = Modifier.fillMaxWidth(),
text = appName,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
@Composable
fun RememberMyChoice(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
onAccept: () -> Unit
) {
if (shouldRunOnAccept) {
LaunchedEffect(Unit) {
onAccept()
}
}
if (packageName != null) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
remember.value = !remember.value
}
) { padding ->
NavHost(
navController,
startDestination = Route.Home.route,
enterTransition = { fadeIn(animationSpec = tween(200)) },
exitTransition = { fadeOut(animationSpec = tween(200)) }
) {
Text(
modifier = Modifier.weight(1f),
text = "Remember my choice and don't ask again"
composable(
Route.Home.route,
content = {
HomeScreen(
Modifier
.fillMaxSize()
.padding(padding),
json,
packageName,
account
)
}
)
Switch(
checked = remember.value,
onCheckedChange = {
remember.value = !remember.value
composable(
Route.Permissions.route,
content = {
PermissionsScreen(
modifier = Modifier
.fillMaxSize()
.padding(padding),
account = account,
accountStateViewModel = accountStateViewModel
)
}
)
composable(
Route.Settings.route,
content = {
SettingsScreen(
Modifier
.fillMaxSize()
.padding(padding),
accountStateViewModel,
account
)
}
)
}
}
}
@Composable
fun AcceptRejectButtons(
onAccept: () -> Unit,
onReject: () -> Unit
) {
Row(
Modifier
.fillMaxWidth()
.padding(10.dp),
Arrangement.Center
) {
Button(
shape = ButtonBorder,
onClick = onReject
) {
Text("Reject")
}
Spacer(modifier = Modifier.width(8.dp))
Button(
shape = ButtonBorder,
onClick = onAccept
) {
Text("Accept")
}
}
}
@Composable
fun EncryptDecryptData(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
type: SignerType,
onAccept: () -> Unit,
onReject: () -> Unit
) {
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(Modifier.padding(6.dp)) {
val message = when (type) {
SignerType.NIP44_ENCRYPT -> "encrypt nip44"
SignerType.NIP04_ENCRYPT -> "encrypt nip04"
SignerType.NIP44_DECRYPT -> "decrypt nip44"
SignerType.NIP04_DECRYPT -> "decrypt nip04"
SignerType.DECRYPT_ZAP_EVENT -> "decrypt zap event"
else -> "encrypt/decrypt"
LaunchedEffect(Unit) {
if (route !== null) {
delay(200)
try {
navController.navigate(route) {
popUpTo(0)
}
Text(
"wants you to $message data",
fontWeight = FontWeight.Bold
)
}
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@Composable
fun LoginWithPubKey(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
onAccept: () -> Unit,
onReject: () -> Unit
) {
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(
Modifier
.padding(6.dp)
) {
Text(
"wants to read your public key",
fontWeight = FontWeight.Bold
)
}
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@Composable
fun EventData(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
event: Event,
rawJson: String,
onAccept: () -> Unit,
onReject: () -> Unit
) {
var showMore by remember {
mutableStateOf(false)
}
val eventDescription = event.description()
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(Modifier.padding(6.dp)) {
val message = if (event.kind == 22242) {
"client authentication"
} else {
eventDescription
}
Text(
"wants you to sign a $message",
fontWeight = FontWeight.Bold
)
EventRow(
Icons.Default.CalendarMonth,
"Kind ",
"${event.kind}"
)
EventRow(
Icons.Default.AccessTime,
"Created at ",
TimeUtils.format(event.createdAt)
)
EventRow(
Icons.Default.Person,
"Signed by ",
event.pubKey.toShortenHex()
)
EventRow(
Icons.Default.ContentPaste,
"Content ",
event.content
)
Divider(
modifier = Modifier.padding(top = 15.dp),
thickness = Dp.Hairline
)
}
}
RawJsonButton(
onCLick = {
showMore = !showMore
},
if (!showMore) "Show Details" else "Hide Details"
)
if (showMore) {
RawJson(rawJson, Modifier.weight(1f))
} else {
Spacer(modifier = Modifier.weight(1f))
}
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@Composable
fun RawJsonButton(
onCLick: () -> Unit,
text: String
) {
Box(
Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
contentAlignment = Alignment.Center
) {
Button(
shape = ButtonBorder,
onClick = onCLick
) {
Text(text)
} catch (_: Exception) { }
}
}
}
@Composable
fun RawJson(
rawJson: String,
modifier: Modifier
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
OutlinedTextField(
modifier = modifier,
value = TextFieldValue(JSONObject(rawJson).toString(2)),
onValueChange = { },
readOnly = true
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Button(
shape = ButtonBorder,
onClick = {
clipboardManager.setText(AnnotatedString(rawJson))
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.raw_json_copied_to_the_clipboard),
Toast.LENGTH_SHORT
).show()
}
}
) {
Text("Copy raw json")
}
}
}
@Composable
fun EventRow(
icon: ImageVector,
title: String,
content: String
) {
Divider(
modifier = Modifier.padding(vertical = 15.dp),
thickness = Dp.Hairline
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
Icon(
icon,
null,
tint = MaterialTheme.colors.onBackground
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = "$title ",
fontWeight = FontWeight.Bold
)
Text(
content,
color = Color.Gray,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth()
)
}
}
@@ -0,0 +1,246 @@
package com.greenart7c3.nostrsigner.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.ClearAll
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
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.lifecycle.Lifecycle
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.ui.components.CloseButton
import com.greenart7c3.nostrsigner.ui.components.IconRow
import com.greenart7c3.nostrsigner.ui.components.PostButton
import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.vitorpamplona.quartz.encoders.toNpub
import kotlinx.coroutines.launch
@Composable
fun PermissionsScreen(
modifier: Modifier,
account: Account,
accountStateViewModel: AccountStateViewModel
) {
val lifecycleEvent = rememberLifecycleEvent()
val localAccount = LocalPreferences.loadFromEncryptedStorage(account.keyPair.pubKey.toNpub())
val savedApps = localAccount!!.savedApps
val applications = savedApps.keys.toList().map {
it.split("-").first()
}.toSet()
var resetPermissions by remember { mutableStateOf(false) }
var selectedPackage by remember { mutableStateOf<String?>(null) }
var permissions by remember { mutableStateOf(listOf<String>()) }
val scope = rememberCoroutineScope()
LaunchedEffect(lifecycleEvent) {
if (lifecycleEvent == Lifecycle.Event.ON_RESUME) {
selectedPackage = null
}
}
if (resetPermissions) {
AlertDialog(
title = {
Text(text = stringResource(R.string.reset_permissions))
},
text = {
Text(text = stringResource(R.string.do_you_want_to_reset_all_permissions))
},
onDismissRequest = {
resetPermissions = false
},
confirmButton = {
TextButton(
onClick = {
resetPermissions = false
scope.launch {
LocalPreferences.deleteSavedApps(localAccount)
accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Permissions.route)
}
}
) {
Text(text = stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(
onClick = {
resetPermissions = false
}
) {
Text(text = stringResource(R.string.cancel))
}
}
)
}
if (selectedPackage !== null) {
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest = {
selectedPackage = null
}
) {
Surface(
Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
CloseButton(
onCancel = {
selectedPackage = null
}
)
PostButton(
onPost = {
scope.launch {
val localSaved = localAccount.savedApps.filter { !it.key.contains(selectedPackage!!) }.toMutableMap()
permissions.forEach {
localSaved["$selectedPackage-$it"] = true
}
localAccount.savedApps = localSaved
LocalPreferences.saveToEncryptedStorage(localAccount)
selectedPackage = null
accountStateViewModel.switchUser(localAccount.keyPair.pubKey.toNpub(), Route.Permissions.route)
}
},
isActive = true
)
}
permissions.forEachIndexed { index, permission ->
Row(
modifier = Modifier
.padding(vertical = 15.dp, horizontal = 25.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = permission,
fontSize = 18.sp
)
}
Icon(
Icons.Default.Delete,
null,
modifier = Modifier
.size(22.dp)
.clickable {
permissions =
permissions.filterIndexed { i, _ -> i != index }
},
tint = Color.Red
)
}
}
}
}
}
}
Column(
modifier
) {
Box(
Modifier
.padding(16.dp)
) {
IconRow(
title = "Reset permissions",
icon = Icons.Default.ClearAll,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
resetPermissions = true
}
)
}
Divider()
if (applications.isEmpty()) {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text("No permissions granted")
}
} else {
LazyColumn(
Modifier
.fillMaxSize()
) {
items(applications.size) {
Box(
Modifier
.padding(16.dp)
) {
IconRow(
title = applications.elementAt(it),
icon = Icons.Default.ArrowForward,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
permissions = localAccount.savedApps.keys.filter { item -> item.startsWith(applications.elementAt(it)) }.map { item ->
item.replace("${applications.elementAt(it)}-", "")
}
selectedPackage = applications.elementAt(it)
}
)
}
}
}
}
}
}
@@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -34,13 +34,13 @@ fun QrCodeDrawer(contents: String, modifier: Modifier = Modifier) {
createQrCode(contents = contents)
}
val foregroundColor = MaterialTheme.colors.onSurface
val foregroundColor = MaterialTheme.colorScheme.onSurface
Box(
modifier = modifier
.defaultMinSize(48.dp, 48.dp)
.aspectRatio(1f)
.background(MaterialTheme.colors.background)
.background(MaterialTheme.colorScheme.background)
) {
Canvas(modifier = Modifier.fillMaxSize()) {
// Calculate the height and width of each column/row
@@ -0,0 +1,101 @@
package com.greenart7c3.nostrsigner.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material.icons.filled.Key
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupDialog
import com.greenart7c3.nostrsigner.ui.components.IconRow
import com.vitorpamplona.quartz.encoders.toNpub
@Composable
fun SettingsScreen(
modifier: Modifier,
accountStateViewModel: AccountStateViewModel,
account: Account
) {
var backupDialogOpen by remember { mutableStateOf(false) }
var logoutDialog by remember { mutableStateOf(false) }
if (logoutDialog) {
AlertDialog(
title = {
Text(text = stringResource(R.string.logout))
},
text = {
Text(text = stringResource(R.string.logging_out_deletes_all_your_local_information_make_sure_to_have_your_private_keys_backed_up_to_avoid_losing_your_account_do_you_want_to_continue))
},
onDismissRequest = {
logoutDialog = false
},
confirmButton = {
TextButton(
onClick = {
logoutDialog = false
accountStateViewModel.logOff(account.keyPair.pubKey.toNpub())
}
) {
Text(text = stringResource(R.string.logout))
}
},
dismissButton = {
TextButton(
onClick = {
logoutDialog = false
}
) {
Text(text = stringResource(R.string.cancel))
}
}
)
}
Column(
modifier
) {
Box(
Modifier
.padding(16.dp)
) {
IconRow(
title = "Backup Keys",
icon = Icons.Default.Key,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
backupDialogOpen = true
}
)
}
Box(
Modifier
.padding(16.dp)
) {
IconRow(
title = "Logout",
icon = Icons.Default.ExitToApp,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
logoutDialog = true
}
)
}
}
if (backupDialogOpen) {
AccountBackupDialog(account, onClose = { backupDialogOpen = false })
}
}
@@ -7,8 +7,8 @@ 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.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -29,7 +29,7 @@ fun ShowQRDialog(account: Account, onClose: () -> Unit) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.background(MaterialTheme.colors.background)
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Row(
@@ -16,14 +16,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Key
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
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
@@ -43,7 +42,7 @@ import com.greenart7c3.nostrsigner.ui.components.CloseButton
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.MaterialRichText
import com.halilibo.richtext.ui.material3.Material3RichText
import com.halilibo.richtext.ui.resolveDefaults
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.encoders.toNsec
@@ -59,7 +58,7 @@ fun AccountBackupDialog(account: Account, onClose: () -> Unit) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.background(MaterialTheme.colors.background)
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Row(
@@ -79,7 +78,7 @@ fun AccountBackupDialog(account: Account, onClose: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
MaterialRichText(
Material3RichText(
style = RichTextStyle().resolveDefaults()
) {
Markdown(
@@ -125,18 +124,15 @@ private fun NSecCopyButton(
}
},
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) {
Icon(
tint = MaterialTheme.colors.onPrimary,
tint = MaterialTheme.colorScheme.onPrimary,
imageVector = Icons.Default.Key,
contentDescription = stringResource(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup),
modifier = Modifier.padding(end = 5.dp)
)
Text(stringResource(id = R.string.copy_my_secret_key), color = MaterialTheme.colors.onPrimary)
Text(stringResource(id = R.string.copy_my_secret_key), color = MaterialTheme.colorScheme.onPrimary)
}
}
@@ -161,20 +157,17 @@ private fun NPubCopyButton(
}
},
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
) {
Icon(
tint = MaterialTheme.colors.onPrimary,
tint = MaterialTheme.colorScheme.onPrimary,
imageVector = Icons.Default.Key,
contentDescription = stringResource(R.string.copies_the_npub_id_your_user_to_the_clipboard_for_backup),
modifier = Modifier.padding(end = 5.dp)
)
Text(
stringResource(R.string.copy_my_public_key),
color = MaterialTheme.colors.onPrimary
color = MaterialTheme.colorScheme.onPrimary
)
}
}
@@ -0,0 +1,41 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
@Composable
fun AcceptRejectButtons(
onAccept: () -> Unit,
onReject: () -> Unit
) {
Row(
Modifier
.fillMaxWidth()
.padding(10.dp),
Arrangement.Center
) {
Button(
shape = ButtonBorder,
onClick = onReject
) {
Text("Reject")
}
Spacer(modifier = Modifier.width(8.dp))
Button(
shape = ButtonBorder,
onClick = onAccept
) {
Text("Accept")
}
}
}
@@ -0,0 +1,18 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@Composable
fun AppTitle(appName: String) {
Text(
modifier = Modifier.fillMaxWidth(),
text = appName,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
@@ -1,9 +1,7 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material3.Button
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
@Composable
@@ -12,11 +10,7 @@ fun CloseButton(onCancel: () -> Unit) {
onClick = {
onCancel()
},
shape = ButtonBorder,
colors = ButtonDefaults
.buttonColors(
backgroundColor = Color.Gray
)
shape = ButtonBorder
) {
CloseIcon()
}
@@ -1,18 +1,16 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.greenart7c3.nostrsigner.ui.theme.Size20Modifier
@Composable
fun CloseIcon() {
Icon(
Icons.Default.Cancel,
Icons.Outlined.Close,
contentDescription = "Cancel",
modifier = Size20Modifier,
tint = Color.White
modifier = Size20Modifier
)
}
@@ -7,19 +7,19 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.AlertDialog
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ClearAll
import androidx.compose.material.icons.filled.Key
import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider
import androidx.compose.material3.DrawerState
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -46,7 +46,7 @@ import kotlinx.coroutines.launch
fun Drawer(
accountStateViewModel: AccountStateViewModel,
account: Account,
scaffoldState: ScaffoldState
drawerState: DrawerState
) {
var logoutDialog by remember { mutableStateOf(false) }
var resetPermissions by remember { mutableStateOf(false) }
@@ -102,7 +102,7 @@ fun Drawer(
onClick = {
resetPermissions = false
scope.launch {
scaffoldState.drawerState.close()
drawerState.close()
LocalPreferences.deleteSavedApps(account)
}
}
@@ -124,14 +124,14 @@ fun Drawer(
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.background
color = MaterialTheme.colorScheme.background
) {
Column {
Spacer(modifier = Modifier.weight(1f))
IconRow(
title = "Reset permissions",
icon = Icons.Default.ClearAll,
tint = MaterialTheme.colors.onBackground,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
resetPermissions = true
}
@@ -139,27 +139,27 @@ fun Drawer(
IconRow(
title = "Backup Keys",
icon = Icons.Default.Key,
tint = MaterialTheme.colors.onBackground,
tint = MaterialTheme.colorScheme.onBackground,
onClick = {
scope.launch {
scaffoldState.drawerState.close()
drawerState.close()
}
backupDialogOpen = true
}
)
BottomContent(account, scaffoldState)
BottomContent(account, drawerState)
}
}
if (backupDialogOpen) {
AccountBackupDialog(account, onClose = { backupDialogOpen = false })
}
BackHandler(enabled = scaffoldState.drawerState.isOpen) {
scope.launch { scaffoldState.drawerState.close() }
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
}
@Composable
fun BottomContent(account: Account, scaffoldState: ScaffoldState) {
fun BottomContent(account: Account, drawerState: DrawerState) {
val coroutineScope = rememberCoroutineScope()
// store the dialog open or close state
@@ -189,14 +189,14 @@ fun BottomContent(account: Account, scaffoldState: ScaffoldState) {
IconButton(onClick = {
dialogOpen = true
coroutineScope.launch {
scaffoldState.drawerState.close()
drawerState.close()
}
}) {
Icon(
Icons.Default.QrCode,
null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colors.primary
tint = MaterialTheme.colorScheme.primary
)
}
}
@@ -0,0 +1,69 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.models.SignerType
@Composable
fun EncryptDecryptData(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
type: SignerType,
onAccept: () -> Unit,
onReject: () -> Unit
) {
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(Modifier.padding(6.dp)) {
val message = when (type) {
SignerType.NIP44_ENCRYPT -> "encrypt nip44"
SignerType.NIP04_ENCRYPT -> "encrypt nip04"
SignerType.NIP44_DECRYPT -> "decrypt nip44"
SignerType.NIP04_DECRYPT -> "decrypt nip04"
SignerType.DECRYPT_ZAP_EVENT -> "decrypt zap event"
else -> "encrypt/decrypt"
}
Text(
"wants you to $message data",
fontWeight = FontWeight.Bold
)
}
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@@ -0,0 +1,122 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccessTime
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.service.model.Event
import com.greenart7c3.nostrsigner.service.toShortenHex
@Composable
fun EventData(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
event: Event,
rawJson: String,
onAccept: () -> Unit,
onReject: () -> Unit
) {
var showMore by androidx.compose.runtime.remember {
mutableStateOf(false)
}
val eventDescription = event.description()
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(Modifier.padding(6.dp)) {
val message = if (event.kind == 22242) {
"client authentication"
} else {
eventDescription
}
Text(
"wants you to sign a $message",
fontWeight = FontWeight.Bold
)
EventRow(
Icons.Default.CalendarMonth,
"Kind ",
"${event.kind}"
)
EventRow(
Icons.Default.AccessTime,
"Created at ",
TimeUtils.format(event.createdAt)
)
EventRow(
Icons.Default.Person,
"Signed by ",
event.pubKey.toShortenHex()
)
EventRow(
Icons.Default.ContentPaste,
"Content ",
event.content
)
Divider(
modifier = Modifier.padding(top = 15.dp),
thickness = Dp.Hairline
)
}
}
RawJsonButton(
onCLick = {
showMore = !showMore
},
if (!showMore) "Show Details" else "Hide Details"
)
if (showMore) {
RawJson(rawJson, Modifier.weight(1f))
} else {
Spacer(modifier = Modifier.weight(1f))
}
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@@ -0,0 +1,53 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun EventRow(
icon: ImageVector,
title: String,
content: String
) {
Divider(
modifier = Modifier.padding(vertical = 15.dp),
thickness = Dp.Hairline
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
Icon(
icon,
null,
tint = MaterialTheme.colorScheme.onBackground
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = "$title ",
fontWeight = FontWeight.Bold
)
Text(
content,
color = Color.Gray,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth()
)
}
}
@@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -0,0 +1,62 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun LoginWithPubKey(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
appName: String,
onAccept: () -> Unit,
onReject: () -> Unit
) {
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
AppTitle(appName)
Spacer(Modifier.size(4.dp))
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(
Modifier
.padding(6.dp)
) {
Text(
"wants to read your public key",
fontWeight = FontWeight.Bold
)
}
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
remember,
packageName,
onAccept
)
AcceptRejectButtons(
onAccept,
onReject
)
}
}
@@ -1,10 +1,8 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -21,10 +19,6 @@ fun PostButton(modifier: Modifier = Modifier, onPost: () -> Unit = {}, isActive:
}
},
shape = ButtonBorder,
colors = ButtonDefaults
.buttonColors(
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
),
contentPadding = PaddingValues(0.dp)
) {
Text(text = "Post", color = Color.White)
@@ -0,0 +1,58 @@
package com.greenart7c3.nostrsigner.ui.components
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
import kotlinx.coroutines.launch
import org.json.JSONObject
@Composable
fun RawJson(
rawJson: String,
modifier: Modifier
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
OutlinedTextField(
modifier = modifier,
value = TextFieldValue(JSONObject(rawJson).toString(2)),
onValueChange = { },
readOnly = true
)
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Button(
shape = ButtonBorder,
onClick = {
clipboardManager.setText(AnnotatedString(rawJson))
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.raw_json_copied_to_the_clipboard),
Toast.LENGTH_SHORT
).show()
}
}
) {
Text("Copy raw json")
}
}
}
@@ -0,0 +1,32 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
@Composable
fun RawJsonButton(
onCLick: () -> Unit,
text: String
) {
Box(
Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
contentAlignment = Alignment.Center
) {
Button(
shape = ButtonBorder,
onClick = onCLick
) {
Text(text)
}
}
}
@@ -0,0 +1,47 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun RememberMyChoice(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
packageName: String?,
onAccept: () -> Unit
) {
if (shouldRunOnAccept) {
LaunchedEffect(Unit) {
onAccept()
}
}
if (packageName != null) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
remember.value = !remember.value
}
) {
Text(
modifier = Modifier.weight(1f),
text = "Remember my choice and don't ask again"
)
Switch(
checked = remember.value,
onCheckedChange = {
remember.value = !remember.value
}
)
}
}
}
@@ -1,104 +0,0 @@
package com.greenart7c3.nostrsigner.ui.components
import androidx.compose.material.AlertDialog
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Logout
import androidx.compose.material.icons.filled.Menu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
import com.vitorpamplona.quartz.encoders.toNpub
import kotlinx.coroutines.launch
@Composable
fun MainAppBar(scaffoldState: ScaffoldState, accountStateViewModel: AccountStateViewModel, account: Account) {
val scope = rememberCoroutineScope()
TopAppBar(
title = { Text(text = "Amber") },
navigationIcon = {
IconButton(
onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null,
tint = MaterialTheme.colors.onSurface
)
}
},
actions = {
LogoutButton(
accountStateViewModel = accountStateViewModel,
account = account
)
},
backgroundColor = Color.Transparent,
elevation = 0.dp
)
}
@Composable
fun LogoutButton(accountStateViewModel: AccountStateViewModel, account: Account) {
var logoutDialog by remember { mutableStateOf(false) }
if (logoutDialog) {
AlertDialog(
title = {
Text(text = "Logout")
},
text = {
Text(text = "Logging out deletes all your local information. Make sure to have your private keys backed up to avoid losing your account. Do you want to continue?")
},
onDismissRequest = {
logoutDialog = false
},
confirmButton = {
TextButton(
onClick = {
logoutDialog = false
accountStateViewModel.logOff(account.keyPair.pubKey.toNpub())
}
) {
Text(text = "Logout")
}
},
dismissButton = {
TextButton(
onClick = {
logoutDialog = false
}
) {
Text(text = "Cancel")
}
}
)
}
IconButton(
onClick = {
logoutDialog = true
}
) {
Icon(
imageVector = Icons.Default.Logout,
contentDescription = null,
tint = MaterialTheme.colors.onSurface
)
}
}
@@ -0,0 +1,36 @@
package com.greenart7c3.nostrsigner.ui.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.ViewList
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.ViewList
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector
@Immutable
sealed class Route(
val route: String,
val icon: ImageVector,
val selectedIcon: ImageVector
) {
data object Home : Route(
route = "Home",
icon = Icons.Outlined.Home,
selectedIcon = Icons.Default.Home
)
data object Permissions : Route(
route = "Permissions",
icon = Icons.Outlined.ViewList,
selectedIcon = Icons.Default.ViewList
)
data object Settings : Route(
route = "Settings",
icon = Icons.Outlined.Settings,
selectedIcon = Icons.Default.Settings
)
}
@@ -1,14 +1,17 @@
package com.greenart7c3.nostrsigner.ui.previews
import android.annotation.SuppressLint
import androidx.compose.material.Scaffold
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.service.model.Event
import com.greenart7c3.nostrsigner.ui.EventData
import com.greenart7c3.nostrsigner.ui.components.EventData
import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@@ -21,16 +24,20 @@ fun EventDataPreview() {
NostrSignerTheme(darkTheme = true) {
Scaffold {
EventData(
false,
remember,
null,
"App",
event,
data,
{ },
{ }
)
Box(
Modifier.padding(it)
) {
EventData(
false,
remember,
null,
"App",
event,
data,
{ },
{ }
)
}
}
}
}
@@ -4,11 +4,10 @@ import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
@@ -37,26 +36,21 @@ val Teal200 = Color(0xFF03DAC5)
val ButtonBorder = RoundedCornerShape(20.dp)
val Size20Modifier = Modifier.size(20.dp)
private val DarkColorPalette = darkColors(
private val DarkColorPalette = darkColorScheme(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200,
secondaryVariant = Purple200
secondary = Purple700,
tertiary = Teal200
)
private val LightColorPalette = lightColors(
private val LightColorPalette = lightColorScheme(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200,
secondaryVariant = Purple500
secondary = Purple700,
tertiary = Teal200
)
private val DarkPlaceholderText = DarkColorPalette.onSurface.copy(alpha = 0.32f)
private val LightPlaceholderText = LightColorPalette.onSurface.copy(alpha = 0.32f)
val Colors.placeholderText: Color
get() = if (isLight) LightPlaceholderText else DarkPlaceholderText
@Composable
fun NostrSignerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
@@ -65,7 +59,7 @@ fun NostrSignerTheme(
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
MaterialTheme(
colors = colors,
colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content
@@ -1,6 +1,6 @@
package com.greenart7c3.nostrsigner.ui.theme
import androidx.compose.material.Typography
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
@@ -8,7 +8,7 @@ import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
body1 = TextStyle(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
+2 -2
View File
@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
ext {
lifecycle_version = '2.6.1'
compose_ui_version = '1.5.0'
nav_version = "2.7.0"
compose_ui_version = '1.5.1'
nav_version = "2.7.2"
}
}