From c723ef67b21cf0700d6ca7fccee3e2ba10d2bb6d Mon Sep 17 00:00:00 2001 From: greenart7c3 <115044884+greenart7c3@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:11:31 -0300 Subject: [PATCH] removed online features --- app/build.gradle | 3 - app/src/main/AndroidManifest.xml | 3 +- .../greenart7c3/nostrsigner/MainActivity.kt | 3 +- .../nostrsigner/PubKeyFormatter.kt | 6 - .../nostrsigner/models/HexUtils.kt | 38 --- .../nostrsigner/models/IntentData.kt | 3 +- .../nostrsigner/models/TimeUtils.kt | 8 - .../nostrsigner/service/Bech32Util.kt | 3 - .../nostrsigner/service/model/ATag.kt | 22 -- .../service/model/EmojiPackEvent.kt | 56 ---- .../nostrsigner/service/model/EmojiUrl.kt | 6 + .../nostrsigner/service/model/Event.kt | 19 +- .../service/model/RelayAuthEvent.kt | 36 --- .../nostrsigner/service/nip19/Nip19.kt | 172 ----------- .../nostrsigner/service/relays/EOSETime.kt | 7 - .../nostrsigner/service/relays/JsonFilter.kt | 65 ---- .../nostrsigner/service/relays/Relay.kt | 283 ------------------ .../nostrsigner/service/relays/TypedFilter.kt | 63 ---- .../nostrsigner/ui/AccountScreen.kt | 2 - .../greenart7c3/nostrsigner/ui/MainScreen.kt | 84 +----- .../nostrsigner/ui/ShowQRDialog.kt | 2 +- .../ui/actions/AccountBackupDialog.kt | 2 + .../ui/actions/RelaySelectionDialog.kt | 234 --------------- .../ui/components/BundledUpdate.kt | 49 --- .../nostrsigner/ui/components/CloseButton.kt | 23 ++ .../nostrsigner/ui/components/CloseIcon.kt | 18 ++ .../nostrsigner/ui/components/PostButton.kt | 32 ++ .../greenart7c3/nostrsigner/ui/theme/Theme.kt | 5 + 28 files changed, 95 insertions(+), 1152 deletions(-) delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/PubKeyFormatter.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiPackEvent.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiUrl.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/model/RelayAuthEvent.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/nip19/Nip19.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/relays/EOSETime.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/relays/JsonFilter.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/relays/Relay.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/relays/TypedFilter.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RelaySelectionDialog.kt delete mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BundledUpdate.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseButton.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseIcon.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/PostButton.kt diff --git a/app/build.gradle b/app/build.gradle index 7c78bfb2..157c8b3c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,9 +105,6 @@ dependencies { // Json Serialization implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - // Websockets API - implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11' - // 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') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4404c2bc..452df5d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,11 +2,10 @@ - - + val data: String ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt index 38914338..b53c9079 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt @@ -1,13 +1,5 @@ package com.greenart7c3.nostrsigner.models object TimeUtils { - const val fiveMinutes = 60 * 5 - const val oneHour = 60 * 60 - const val oneDay = 24 * 60 * 60 - fun now() = System.currentTimeMillis() / 1000 - fun fiveMinutesAgo() = now() - fiveMinutes - fun oneHourAgo() = now() - oneHour - fun oneDayAgo() = now() - oneDay - fun eightHoursAgo() = now() - (oneHour * 8) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/Bech32Util.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/Bech32Util.kt index 9e35b3a7..f481c816 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/Bech32Util.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/Bech32Util.kt @@ -201,10 +201,7 @@ object Bech32 { fun ByteArray.toNsec() = Bech32.encodeBytes(hrp = "nsec", this, Bech32.Encoding.Bech32) fun ByteArray.toNpub() = Bech32.encodeBytes(hrp = "npub", this, Bech32.Encoding.Bech32) -fun ByteArray.toNote() = Bech32.encodeBytes(hrp = "note", this, Bech32.Encoding.Bech32) fun ByteArray.toNEvent() = Bech32.encodeBytes(hrp = "nevent", this, Bech32.Encoding.Bech32) -fun ByteArray.toNAddress() = Bech32.encodeBytes(hrp = "naddr", this, Bech32.Encoding.Bech32) -fun ByteArray.toLnUrl() = Bech32.encodeBytes(hrp = "lnurl", this, Bech32.Encoding.Bech32) fun String.bechToBytes(hrp: String? = null): ByteArray { val decodedForm = Bech32.decodeBytes(this) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt index 64b943e6..d139da51 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt @@ -2,37 +2,15 @@ package com.greenart7c3.nostrsigner.service.model import android.util.Log import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.hexToByteArray import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.bechToBytes import com.greenart7c3.nostrsigner.service.nip19.Tlv -import com.greenart7c3.nostrsigner.service.toByteArray -import com.greenart7c3.nostrsigner.service.toNAddress import fr.acinq.secp256k1.Hex @Immutable data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) { fun toTag() = "$kind:$pubKeyHex:$dTag" - fun toNAddr(): String { - val kind = kind.toByteArray() - val author = pubKeyHex.hexToByteArray() - val dTag = dTag.toByteArray(Charsets.UTF_8) - val relay = relay?.toByteArray(Charsets.UTF_8) - - var fullArray = byteArrayOf(Tlv.Type.SPECIAL.id, dTag.size.toByte()) + dTag - - if (relay != null) { - fullArray = fullArray + byteArrayOf(Tlv.Type.RELAY.id, relay.size.toByte()) + relay - } - - fullArray = fullArray + - byteArrayOf(Tlv.Type.AUTHOR.id, author.size.toByte()) + author + - byteArrayOf(Tlv.Type.KIND.id, kind.size.toByte()) + kind - - return fullArray.toNAddress() - } - companion object { fun isATag(key: String): Boolean { return key.startsWith("naddr1") || key.contains(":") diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiPackEvent.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiPackEvent.kt deleted file mode 100644 index 61d7752c..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiPackEvent.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.greenart7c3.nostrsigner.service.model - -import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.HexKey -import com.greenart7c3.nostrsigner.models.TimeUtils -import com.greenart7c3.nostrsigner.models.toHexKey -import com.greenart7c3.nostrsigner.service.CryptoUtils - -@Immutable -class EmojiPackEvent( - id: HexKey, - pubKey: HexKey, - createdAt: Long, - tags: List>, - content: String, - sig: HexKey -) : GeneralListEvent(id, pubKey, createdAt, kind, tags, content, sig) { - - companion object { - const val kind = 30030 - - fun create( - name: String = "", - privateKey: ByteArray, - createdAt: Long = TimeUtils.now() - ): EmojiPackEvent { - val content = "" - val pubKey = CryptoUtils.pubkeyCreate(privateKey) - - val tags = mutableListOf>() - tags.add(listOf("d", name)) - - val id = generateId(pubKey.toHexKey(), createdAt, kind, tags, content) - val sig = CryptoUtils.sign(id, privateKey) - return EmojiPackEvent(id.toHexKey(), pubKey.toHexKey(), createdAt, tags, content, sig.toHexKey()) - } - } -} - -@Immutable -data class EmojiUrl(val code: String, val url: String) { - fun encode(): String { - return ":$code:$url" - } - - companion object { - fun decode(encodedEmojiSetup: String): EmojiUrl? { - val emojiParts = encodedEmojiSetup.split(":", limit = 3) - return if (emojiParts.size > 2) { - EmojiUrl(emojiParts[1], emojiParts[2]) - } else { - null - } - } - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiUrl.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiUrl.kt new file mode 100644 index 00000000..094de2c5 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EmojiUrl.kt @@ -0,0 +1,6 @@ +package com.greenart7c3.nostrsigner.service.model + +import androidx.compose.runtime.Immutable + +@Immutable +data class EmojiUrl(val code: String, val url: String) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt index 5a9e75d9..850702ec 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt @@ -17,7 +17,6 @@ import com.greenart7c3.nostrsigner.models.HexKey import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.CryptoUtils -import com.greenart7c3.nostrsigner.service.nip19.Nip19 import fr.acinq.secp256k1.Hex import java.lang.reflect.Type import java.math.BigDecimal @@ -147,18 +146,6 @@ open class Event( } } - open fun toNIP19(): String { - return if (this is AddressableEvent) { - ATag(kind, pubKey, dTag(), null).toNAddr() - } else { - Nip19.createNEvent(id, pubKey, kind, null) - } - } - - fun toNostrUri(): String { - return "nostr:${toNIP19()}" - } - /** * Checks if the ID is correct and then if the pubKey's secret key signed the event. */ @@ -279,11 +266,9 @@ open class Event( .registerTypeAdapter(Request::class.java, RequestDeserializer()) .create() - fun fromJson(json: String, lenient: Boolean = false): Event = gson.fromJson(json, Event::class.java).getRefinedEvent(lenient) + fun fromJson(json: String): Event = gson.fromJson(json, Event::class.java).getRefinedEvent() - fun fromJson(json: JsonElement, lenient: Boolean = false): Event = gson.fromJson(json, Event::class.java).getRefinedEvent(lenient) - - fun Event.getRefinedEvent(lenient: Boolean = false): Event = when (kind) { + fun Event.getRefinedEvent(): Event = when (kind) { else -> this } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/RelayAuthEvent.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/RelayAuthEvent.kt deleted file mode 100644 index 49c9dd32..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/RelayAuthEvent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.greenart7c3.nostrsigner.service.model - -import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.HexKey -import com.greenart7c3.nostrsigner.models.TimeUtils -import com.greenart7c3.nostrsigner.models.toHexKey -import com.greenart7c3.nostrsigner.service.CryptoUtils - -@Immutable -class RelayAuthEvent( - id: HexKey, - pubKey: HexKey, - createdAt: Long, - tags: List>, - content: String, - sig: HexKey -) : Event(id, pubKey, createdAt, kind, tags, content, sig) { - fun relay() = tags.firstOrNull() { it.size > 1 && it[0] == "relay" }?.get(1) - fun challenge() = tags.firstOrNull() { it.size > 1 && it[0] == "challenge" }?.get(1) - - companion object { - const val kind = 22242 - - fun create(relay: String, challenge: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): RelayAuthEvent { - val content = "" - val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey() - val tags = listOf( - listOf("relay", relay), - listOf("challenge", challenge) - ) - val id = generateId(pubKey, createdAt, kind, tags, content) - val sig = CryptoUtils.sign(id, privateKey) - return RelayAuthEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) - } - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/nip19/Nip19.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/nip19/Nip19.kt deleted file mode 100644 index dea4e80a..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/nip19/Nip19.kt +++ /dev/null @@ -1,172 +0,0 @@ -package com.greenart7c3.nostrsigner.service.nip19 - -import android.util.Log -import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.hexToByteArray -import com.greenart7c3.nostrsigner.models.toHexKey -import com.greenart7c3.nostrsigner.service.bechToBytes -import com.greenart7c3.nostrsigner.service.toByteArray -import com.greenart7c3.nostrsigner.service.toNEvent -import java.util.regex.Pattern - -object Nip19 { - enum class Type { - USER, NOTE, EVENT, RELAY, ADDRESS - } - - val nip19regex = Pattern.compile("(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)", Pattern.CASE_INSENSITIVE) - - @Immutable - data class Return( - val type: Type, - val hex: String, - val relay: String? = null, - val author: String? = null, - val kind: Int? = null, - val additionalChars: String = "" - ) - - fun uriToRoute(uri: String?): Return? { - if (uri == null) return null - - try { - val matcher = nip19regex.matcher(uri) - if (!matcher.find()) { - return null - } - - val uriScheme = matcher.group(1) // nostr: - val type = matcher.group(2) // npub1 - val key = matcher.group(3) // bech32 - val additionalChars = matcher.group(4) // additional chars - - return parseComponents(uriScheme, type, key, additionalChars) - } catch (e: Throwable) { - Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e) - } - - return null - } - - fun parseComponents( - uriScheme: String?, - type: String, - key: String?, - additionalChars: String? - ): Return? { - return try { - val bytes = (type + key).bechToBytes() - val parsed = when (type.lowercase()) { - "npub1" -> npub(bytes) - "note1" -> note(bytes) - "nprofile1" -> nprofile(bytes) - "nevent1" -> nevent(bytes) - "nrelay1" -> nrelay(bytes) - "naddr1" -> naddr(bytes) - else -> null - } - parsed?.copy(additionalChars = additionalChars ?: "") - } catch (e: Throwable) { - Log.w("NIP19 Parser", "Issue trying to Decode NIP19 $key: ${e.message}", e) - null - } - } - - private fun npub(bytes: ByteArray): Return { - return Return(Type.USER, bytes.toHexKey()) - } - - private fun note(bytes: ByteArray): Return { - return Return(Type.NOTE, bytes.toHexKey()) - } - - private fun nprofile(bytes: ByteArray): Return? { - val tlv = Tlv.parse(bytes) - - val hex = tlv.get(Tlv.Type.SPECIAL.id) - ?.get(0) - ?.toHexKey() ?: return null - - val relay = tlv.get(Tlv.Type.RELAY.id) - ?.get(0) - ?.toString(Charsets.UTF_8) - - return Return(Type.USER, hex, relay) - } - - private fun nevent(bytes: ByteArray): Return? { - val tlv = Tlv.parse(bytes) - - val hex = tlv.get(Tlv.Type.SPECIAL.id) - ?.get(0) - ?.toHexKey() ?: return null - - val relay = tlv.get(Tlv.Type.RELAY.id) - ?.get(0) - ?.toString(Charsets.UTF_8) - - val author = tlv.get(Tlv.Type.AUTHOR.id) - ?.get(0) - ?.toHexKey() - - val kind = tlv.get(Tlv.Type.KIND.id) - ?.get(0) - ?.let { Tlv.toInt32(it) } - - return Return(Type.EVENT, hex, relay, author, kind) - } - - private fun nrelay(bytes: ByteArray): Return? { - val relayUrl = Tlv.parse(bytes) - .get(Tlv.Type.SPECIAL.id) - ?.get(0) - ?.toString(Charsets.UTF_8) ?: return null - - return Return(Type.RELAY, relayUrl) - } - - private fun naddr(bytes: ByteArray): Return? { - val tlv = Tlv.parse(bytes) - - val d = tlv.get(Tlv.Type.SPECIAL.id) - ?.get(0) - ?.toString(Charsets.UTF_8) ?: return null - - val relay = tlv.get(Tlv.Type.RELAY.id) - ?.get(0) - ?.toString(Charsets.UTF_8) - - val author = tlv.get(Tlv.Type.AUTHOR.id) - ?.get(0) - ?.toHexKey() - - val kind = tlv.get(Tlv.Type.KIND.id) - ?.get(0) - ?.let { Tlv.toInt32(it) } - - return Return(Type.ADDRESS, "$kind:$author:$d", relay, author, kind) - } - - public fun createNEvent(idHex: String, author: String?, kind: Int?, relay: String?): String { - val kind = kind?.toByteArray() - val author = author?.hexToByteArray() - val idHex = idHex.hexToByteArray() - val relay = relay?.toByteArray(Charsets.UTF_8) - - var fullArray = byteArrayOf(Tlv.Type.SPECIAL.id, idHex.size.toByte()) + idHex - - if (relay != null) { - fullArray = fullArray + byteArrayOf(Tlv.Type.RELAY.id, relay.size.toByte()) + relay - } - - if (author != null) { - fullArray = fullArray + byteArrayOf(Tlv.Type.AUTHOR.id, author.size.toByte()) + author - } - - if (kind != null) { - fullArray = fullArray + byteArrayOf(Tlv.Type.KIND.id, kind.size.toByte()) + kind - } - - return fullArray.toNEvent() - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/EOSETime.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/EOSETime.kt deleted file mode 100644 index b46b37dd..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/EOSETime.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.greenart7c3.nostrsigner.service.relays - -class EOSETime(var time: Long) { - override fun toString(): String { - return time.toString() - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/JsonFilter.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/JsonFilter.kt deleted file mode 100644 index 159a1a8c..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/JsonFilter.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.greenart7c3.nostrsigner.service.relays - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonArray -import com.google.gson.JsonObject - -class JsonFilter( - val ids: List? = null, - val authors: List? = null, - val kinds: List? = null, - val tags: Map>? = null, - val since: Map? = null, - val until: Long? = null, - val limit: Int? = null, - val search: String? = null -) { - fun toJson(forRelay: String? = null): String { - val jsonObject = JsonObject() - ids?.run { - jsonObject.add("ids", JsonArray().apply { ids.forEach { add(it) } }) - } - authors?.run { - jsonObject.add("authors", JsonArray().apply { authors.forEach { add(it) } }) - } - kinds?.run { - jsonObject.add("kinds", JsonArray().apply { kinds.forEach { add(it) } }) - } - tags?.run { - entries.forEach { kv -> - jsonObject.add("#${kv.key}", JsonArray().apply { kv.value.forEach { add(it) } }) - } - } - since?.run { - if (!isEmpty()) { - if (forRelay != null) { - val relaySince = get(forRelay) - if (relaySince != null) { - jsonObject.addProperty("since", relaySince.time) - } - } else { - val jsonObjectSince = JsonObject() - entries.forEach { sincePairs -> - jsonObjectSince.addProperty(sincePairs.key, "${sincePairs.value}") - } - jsonObject.add("since", jsonObjectSince) - } - } - } - until?.run { - jsonObject.addProperty("until", until) - } - limit?.run { - jsonObject.addProperty("limit", limit) - } - search?.run { - jsonObject.addProperty("search", search) - } - return gson.toJson(jsonObject) - } - - companion object { - val gson: Gson = GsonBuilder().create() - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/Relay.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/Relay.kt deleted file mode 100644 index 2a3812df..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/Relay.kt +++ /dev/null @@ -1,283 +0,0 @@ -package com.greenart7c3.nostrsigner.service.relays - -import android.util.Log -import com.google.gson.JsonElement -import com.greenart7c3.nostrsigner.BuildConfig -import com.greenart7c3.nostrsigner.models.TimeUtils -import com.greenart7c3.nostrsigner.service.model.Event -import com.greenart7c3.nostrsigner.service.model.EventInterface -import com.greenart7c3.nostrsigner.service.model.RelayAuthEvent -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.WebSocket -import okhttp3.WebSocketListener -import java.util.concurrent.atomic.AtomicBoolean - -enum class FeedType { - FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH, WALLET_CONNECT -} - -val COMMON_FEED_TYPES = setOf( - FeedType.FOLLOWS, - FeedType.PUBLIC_CHATS, - FeedType.PRIVATE_DMS, - FeedType.GLOBAL -) - -class Relay( - var url: String, - var read: Boolean = true, - var write: Boolean = true, - var activeTypes: Set = FeedType.values().toSet() -) { - companion object { - // waits 3 minutes to reconnect once things fail - const val RECONNECTING_IN_SECONDS = 60 * 3 - } - - private val httpClient = OkHttpClient.Builder() - .followRedirects(true) - .followSslRedirects(true) - .build() - - private var listeners = setOf() - private var socket: WebSocket? = null - private var isReady: Boolean = false - - var eventDownloadCounterInBytes = 0 - var eventUploadCounterInBytes = 0 - - var spamCounter = 0 - var errorCounter = 0 - var pingInMs: Long? = null - - var closingTimeInSeconds = 0L - - var afterEOSE = false - - fun register(listener: Listener) { - listeners = listeners.plus(listener) - } - - fun unregister(listener: Listener) { - listeners = listeners.minus(listener) - } - - fun isConnected(): Boolean { - return socket != null - } - - private var connectingBlock = AtomicBoolean() - - fun connectAndRun(onConnected: (Relay) -> Unit) { - // If there is a connection, don't wait. - if (connectingBlock.getAndSet(true)) { - return - } - - if (socket != null) return - - try { - val request = Request.Builder() - .header("User-Agent", "Amber/${BuildConfig.VERSION_NAME}") - .url(url.trim()) - .build() - - socket = httpClient.newWebSocket(request, RelayListener(onConnected)) - } catch (e: Exception) { - errorCounter++ - markConnectionAsClosed() - Log.e("Relay", "Relay Invalid $url") - e.printStackTrace() - } finally { - connectingBlock.set(false) - } - } - - inner class RelayListener(val onConnected: (Relay) -> Unit) : WebSocketListener() { - override fun onOpen(webSocket: WebSocket, response: Response) { - markConnectionAsReady(response.receivedResponseAtMillis - response.sentRequestAtMillis) - - // Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url") - onConnected(this@Relay) - - listeners.forEach { it.onRelayStateChange(this@Relay, Type.CONNECT, null) } - } - - override fun onMessage(webSocket: WebSocket, text: String) { - eventDownloadCounterInBytes += text.bytesUsedInMemory() - - try { - processNewRelayMessage(text) - } catch (t: Throwable) { - t.printStackTrace() - text.chunked(2000) { chunked -> - listeners.forEach { it.onError(this@Relay, "", Error("Problem with $chunked")) } - } - } - } - - override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - listeners.forEach { - it.onRelayStateChange( - this@Relay, - Type.DISCONNECTING, - null - ) - } - } - - override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - markConnectionAsClosed() - - listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) } - } - - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - errorCounter++ - - socket?.close(1000, "Normal close") - // Failures disconnect the relay. - markConnectionAsClosed() - - Log.w("Relay", "Relay onFailure $url, ${response?.message} $response") - t.printStackTrace() - listeners.forEach { - it.onError(this@Relay, "", Error("WebSocket Failure. Response: $response. Exception: ${t.message}", t)) - } - } - } - - fun markConnectionAsReady(pingInMs: Long) { - this.afterEOSE = false - this.isReady = true - this.pingInMs = pingInMs - } - - fun markConnectionAsClosed() { - this.socket = null - this.isReady = false - this.afterEOSE = false - this.closingTimeInSeconds = TimeUtils.now() - } - - fun processNewRelayMessage(newMessage: String) { - val msgArray = Event.gson.fromJson(newMessage, JsonElement::class.java).asJsonArray - val type = msgArray[0].asString - val channel = msgArray[1].asString - - when (type) { - "EVENT" -> { - val event = Event.fromJson(msgArray[2], false) - - // Log.w("Relay", "Relay onEVENT $url, $channel") - listeners.forEach { - it.onEvent(this@Relay, channel, event) - if (afterEOSE) { - it.onRelayStateChange(this@Relay, Type.EOSE, channel) - } - } - } - "EOSE" -> listeners.forEach { - afterEOSE = true - // Log.w("Relay", "Relay onEOSE $url, $channel") - it.onRelayStateChange(this@Relay, Type.EOSE, channel) - } - "NOTICE" -> listeners.forEach { - Log.w("Relay", "Relay onNotice $url, $channel") - it.onError(this@Relay, channel, Error("Relay sent notice: " + channel)) - } - "OK" -> listeners.forEach { - Log.w("Relay", "Relay on OK $url, ${msgArray[1].asString}, ${msgArray[2].asBoolean}, ${msgArray[3].asString}") - it.onSendResponse(this@Relay, msgArray[1].asString, msgArray[2].asBoolean, msgArray[3].asString) - } - "AUTH" -> listeners.forEach { - // Log.w("Relay", "Relay$url, ${msg[1].asString}") - it.onAuth(this@Relay, msgArray[1].asString) - } - else -> listeners.forEach { - // Log.w("Relay", "Relay something else $url, $channel") - it.onError( - this@Relay, - channel, - Error("Unknown type $type on channel $channel. Msg was $newMessage") - ) - } - } - } - - fun disconnect() { - // httpClient.dispatcher.executorService.shutdown() - closingTimeInSeconds = TimeUtils.now() - socket?.close(1000, "Normal close") - socket = null - isReady = false - afterEOSE = false - } - - fun send(signedEvent: EventInterface) { - if (signedEvent is RelayAuthEvent) { - val event = """["AUTH",${signedEvent.toJson()}]""" - socket?.send(event) - eventUploadCounterInBytes += event.bytesUsedInMemory() - } - - if (write) { - if (signedEvent !is RelayAuthEvent) { - val event = """["EVENT",${signedEvent.toJson()}]""" - socket?.send(event) - eventUploadCounterInBytes += event.bytesUsedInMemory() - } - } - } - - fun close(subscriptionId: String) { - socket?.send("""["CLOSE","$subscriptionId"]""") - } - - fun isSameRelayConfig(other: Relay): Boolean { - return url == other.url && - write == other.write && - read == other.read && - activeTypes == other.activeTypes - } - - enum class Type { - // Websocket connected - CONNECT, - - // Websocket disconnecting - DISCONNECTING, - - // Websocket disconnected - DISCONNECT, - - // End Of Stored Events - EOSE - } - - interface Listener { - /** - * A new message was received - */ - fun onEvent(relay: Relay, subscriptionId: String, event: Event) - - fun onError(relay: Relay, subscriptionId: String, error: Error) - - fun onSendResponse(relay: Relay, eventId: String, success: Boolean, message: String) - - fun onAuth(relay: Relay, challenge: String) - - /** - * Connected to or disconnected from a relay - * - * @param type is 0 for disconnect and 1 for connect - */ - fun onRelayStateChange(relay: Relay, type: Type, channel: String?) - } -} - -fun String.bytesUsedInMemory(): Int { - return (8 * ((((this.length) * 2) + 45) / 8)) -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/TypedFilter.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/TypedFilter.kt deleted file mode 100644 index fe14f1ca..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/relays/TypedFilter.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.greenart7c3.nostrsigner.service.relays - -import com.google.gson.GsonBuilder -import com.google.gson.JsonArray -import com.google.gson.JsonObject - -class TypedFilter( - val types: Set, - val filter: JsonFilter -) { - - fun toJson(): String { - return GsonBuilder().create().toJson(toJsonObject()) - } - - fun toJsonObject(): JsonObject { - val jsonObject = JsonObject() - jsonObject.add("types", typesToJson(types)) - jsonObject.add("filter", filterToJson(filter)) - return jsonObject - } - - fun typesToJson(types: Set): JsonArray { - return JsonArray().apply { types.forEach { add(it.name.lowercase()) } } - } - - fun filterToJson(filter: JsonFilter): JsonObject { - val jsonObject = JsonObject() - filter.ids?.run { - jsonObject.add("ids", JsonArray().apply { filter.ids.forEach { add(it) } }) - } - filter.authors?.run { - jsonObject.add("authors", JsonArray().apply { filter.authors.forEach { add(it) } }) - } - filter.kinds?.run { - jsonObject.add("kinds", JsonArray().apply { filter.kinds.forEach { add(it) } }) - } - filter.tags?.run { - entries.forEach { kv -> - jsonObject.add("#${kv.key}", JsonArray().apply { kv.value.forEach { add(it) } }) - } - } - /* - Does not include since in the json comparison - filter.since?.run { - val jsonObjectSince = JsonObject() - entries.forEach { sincePairs -> - jsonObjectSince.addProperty(sincePairs.key, "${sincePairs.value}") - } - jsonObject.add("since", jsonObjectSince) - }*/ - filter.until?.run { - jsonObject.addProperty("until", filter.until) - } - filter.limit?.run { - jsonObject.addProperty("limit", filter.limit) - } - filter.search?.run { - jsonObject.addProperty("search", filter.search) - } - return jsonObject - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt index 792836d9..808f29dd 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt @@ -31,5 +31,3 @@ fun AccountScreen( } } } - - diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index a7457f38..567ebe3b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -16,11 +16,9 @@ import androidx.compose.material.Text import androidx.compose.material.rememberDrawerState import androidx.compose.material.rememberScaffoldState 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager @@ -32,11 +30,9 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.model.Event -import com.greenart7c3.nostrsigner.service.relays.Relay -import com.greenart7c3.nostrsigner.ui.actions.ButtonBorder -import com.greenart7c3.nostrsigner.ui.actions.RelaySelectionDialog import com.greenart7c3.nostrsigner.ui.components.Drawer import com.greenart7c3.nostrsigner.ui.components.MainAppBar +import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder import kotlinx.coroutines.launch import org.json.JSONObject @@ -47,11 +43,6 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j mutableStateOf(null) } - val relays = listOf().toMutableList() - var showRelaysDialog by remember { - mutableStateOf(false) - } - val relaysToPost = remember { mutableListOf() } val clipboardManager = LocalClipboardManager.current val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) val coroutineScope = rememberCoroutineScope() @@ -86,11 +77,7 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j json.let { val data = it.data.replace("nostrsigner:", "") event.value = Event.fromJson(data) - val tempRelays = it.relays - relays.clear() - tempRelays.forEach { url -> - relays.add(Relay(url)) - } + Column( Modifier.fillMaxSize() ) { @@ -103,17 +90,8 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j Modifier .fillMaxWidth() .padding(10.dp), - Arrangement.SpaceBetween + Arrangement.Center ) { - Button( - shape = ButtonBorder, - onClick = { - showRelaysDialog = true - } - ) { - Text("Relays") - } - Button( shape = ButtonBorder, onClick = { @@ -145,65 +123,9 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j ) { Text("Sign") } - - Button( - shape = ButtonBorder, - onClick = { - if (event.value == null) { - return@Button - } - - if (event.value!!.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@Button - } - - val signedEvent = Event.create( - account.keyPair.privKey!!, - event.value!!.kind, - event.value!!.tags, - event.value!!.content, - event.value!!.createdAt - ) - - if (relaysToPost.isEmpty()) { - relaysToPost.addAll(relays) - } - - relaysToPost.forEach { item -> - item.connectAndRun { relay -> - relay.send(signedEvent) - } - } - - // mainActivity.finish() - } - ) { - Text("Post") - } } } } - - if (showRelaysDialog) { - RelaySelectionDialog( - list = relays, - selectRelays = relaysToPost, - onClose = { - showRelaysDialog = false - }, - onPost = { - relaysToPost.clear() - relaysToPost.addAll(it) - } - ) - } } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ShowQRDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ShowQRDialog.kt index 472dbe84..3a589750 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/ShowQRDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/ShowQRDialog.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.toNpub -import com.greenart7c3.nostrsigner.ui.actions.CloseButton +import com.greenart7c3.nostrsigner.ui.components.CloseButton import com.greenart7c3.nostrsigner.ui.theme.Size35dp @Composable diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt index f6fa89f0..71aa9c88 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/AccountBackupDialog.kt @@ -41,6 +41,8 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.service.Biometrics import com.greenart7c3.nostrsigner.service.toNpub import com.greenart7c3.nostrsigner.service.toNsec +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 diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RelaySelectionDialog.kt deleted file mode 100644 index b1063884..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/actions/RelaySelectionDialog.kt +++ /dev/null @@ -1,234 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.actions - -import android.widget.Toast -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -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.lazy.itemsIndexed -import androidx.compose.foundation.shape.RoundedCornerShape -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.Switch -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Cancel -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.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.greenart7c3.nostrsigner.service.relays.Relay -import kotlinx.coroutines.launch - -data class RelayList( - val relay: Relay, - val isSelected: Boolean -) - -val ButtonBorder = RoundedCornerShape(20.dp) -val Size20Modifier = Modifier.size(20.dp) - -@Composable -fun CloseButton(onCancel: () -> Unit) { - Button( - onClick = { - onCancel() - }, - shape = ButtonBorder, - colors = ButtonDefaults - .buttonColors( - backgroundColor = Color.Gray - ) - ) { - CloseIcon() - } -} - -@Composable -fun PostButton(modifier: Modifier = Modifier, onPost: () -> Unit = {}, isActive: Boolean) { - Button( - modifier = modifier, - onClick = { - if (isActive) { - onPost() - } - }, - shape = ButtonBorder, - colors = ButtonDefaults - .buttonColors( - backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray - ), - contentPadding = PaddingValues(0.dp) - ) { - Text(text = "Post", color = Color.White) - } -} - -@Composable -fun CloseIcon() { - Icon( - Icons.Default.Cancel, - contentDescription = "Cancel", - modifier = Size20Modifier, - tint = Color.White - ) -} - -@Composable -fun RelaySelectionDialog( - list: List, - selectRelays: List, - onClose: () -> Unit, - onPost: (list: List) -> Unit -) { - val scope = rememberCoroutineScope() - val context = LocalContext.current - var relays by remember { - mutableStateOf( - list.map { - RelayList( - it, - if (selectRelays.isNotEmpty()) selectRelays.any { relay -> it.url == relay.url } else true - ) - } - ) - } - - var selected by remember { - mutableStateOf(true) - } - - Dialog( - onDismissRequest = { onClose() }, - properties = DialogProperties( - usePlatformDefaultWidth = false, - dismissOnClickOutside = false, - decorFitsSystemWindows = false - ) - ) { - Surface( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(start = 10.dp, end = 10.dp, top = 10.dp) - - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - CloseButton( - onCancel = { - onClose() - } - ) - - PostButton( - onPost = { - val selectedRelays = relays.filter { it.isSelected } - if (selectedRelays.isEmpty()) { - scope.launch { - Toast.makeText(context, "Select a relay to continue", Toast.LENGTH_SHORT).show() - } - return@PostButton - } - onPost(selectedRelays.map { it.relay }) - onClose() - }, - isActive = true - ) - } - - RelaySwitch( - text = "Select/Deselect all", - checked = selected, - onClick = { - selected = !selected - relays = relays.mapIndexed { _, item -> - item.copy(isSelected = selected) - } - } - ) - - LazyColumn( - contentPadding = PaddingValues( - top = 10.dp, - bottom = 10.dp - ) - ) { - itemsIndexed( - relays, - key = { _, item -> item.relay.url } - ) { index, item -> - RelaySwitch( - text = item.relay.url - .removePrefix("ws://") - .removePrefix("wss://") - .removeSuffix("/"), - checked = item.isSelected, - onClick = { - relays = relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { - item - } - } - } - ) - } - } - } - } - } -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun RelaySwitch(text: String, checked: Boolean, onClick: () -> Unit, onLongPress: () -> Unit = { }) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .combinedClickable( - onClick = onClick, - onLongClick = onLongPress - ) - ) { - Text( - modifier = Modifier.weight(1f), - text = text - ) - Switch( - checked = checked, - onCheckedChange = { - onClick() - } - ) - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BundledUpdate.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BundledUpdate.kt deleted file mode 100644 index 0b258828..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BundledUpdate.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.greenart7c3.nostrsigner.ui.components - -import androidx.compose.runtime.Stable -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean - -/** - * This class is designed to have a waiting time between two calls of invalidate - */ -@Stable -class BundledUpdate( - val delay: Long, - val dispatcher: CoroutineDispatcher = Dispatchers.Default -) { - private var onlyOneInBlock = AtomicBoolean() - private var invalidatesAgain = false - - fun invalidate(ignoreIfDoing: Boolean = false, onUpdate: suspend () -> Unit) { - if (onlyOneInBlock.getAndSet(true)) { - if (!ignoreIfDoing) { - invalidatesAgain = true - } - return - } - - val scope = CoroutineScope(Job() + dispatcher) - scope.launch { - try { - onUpdate() - delay(delay) - if (invalidatesAgain) { - onUpdate() - } - } finally { - withContext(NonCancellable) { - invalidatesAgain = false - onlyOneInBlock.set(false) - } - } - } - } -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseButton.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseButton.kt new file mode 100644 index 00000000..79478b85 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseButton.kt @@ -0,0 +1,23 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder + +@Composable +fun CloseButton(onCancel: () -> Unit) { + Button( + onClick = { + onCancel() + }, + shape = ButtonBorder, + colors = ButtonDefaults + .buttonColors( + backgroundColor = Color.Gray + ) + ) { + CloseIcon() + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseIcon.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseIcon.kt new file mode 100644 index 00000000..0f3239b3 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/CloseIcon.kt @@ -0,0 +1,18 @@ +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.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.greenart7c3.nostrsigner.ui.theme.Size20Modifier + +@Composable +fun CloseIcon() { + Icon( + Icons.Default.Cancel, + contentDescription = "Cancel", + modifier = Size20Modifier, + tint = Color.White + ) +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/PostButton.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/PostButton.kt new file mode 100644 index 00000000..011bb523 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/PostButton.kt @@ -0,0 +1,32 @@ +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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder + +@Composable +fun PostButton(modifier: Modifier = Modifier, onPost: () -> Unit = {}, isActive: Boolean) { + Button( + modifier = modifier, + onClick = { + if (isActive) { + onPost() + } + }, + shape = ButtonBorder, + colors = ButtonDefaults + .buttonColors( + backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray + ), + contentPadding = PaddingValues(0.dp) + ) { + Text(text = "Post", color = Color.White) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt index ecb26de3..47cdc201 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/theme/Theme.kt @@ -2,6 +2,7 @@ package com.greenart7c3.nostrsigner.ui.theme 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 @@ -10,6 +11,7 @@ import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView @@ -31,6 +33,9 @@ val Purple500 = Color(0xFF6200EE) val Purple700 = Color(0xFF3700B3) val Teal200 = Color(0xFF03DAC5) +val ButtonBorder = RoundedCornerShape(20.dp) +val Size20Modifier = Modifier.size(20.dp) + private val DarkColorPalette = darkColors( primary = Purple200, primaryVariant = Purple700,