mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-22 15:52:22 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5a4d0b59 | ||
|
|
3116ea1ad9 | ||
|
|
72e474c9ec | ||
|
|
fe9e85c282 | ||
|
|
01ac90fbec | ||
|
|
dc079941e7 | ||
|
|
9a7a94cb77 | ||
|
|
a44703cb49 | ||
|
|
52bd6b4443 | ||
|
|
a9e1ce5aec | ||
|
|
639fdb174d | ||
|
|
3cd02ee6cf | ||
|
|
52199fe281 | ||
|
|
5616fa52d1 | ||
|
|
7241c8bd9f | ||
|
|
991131f8cd | ||
|
|
a24c95070c | ||
|
|
a346d31554 | ||
|
|
9b5c7ad23f | ||
|
|
a01a060abc | ||
|
|
1cd39e0d8f | ||
|
|
305ecc6a2e | ||
|
|
858f94ae74 | ||
|
|
fc224d99e3 | ||
|
|
b8584964d1 | ||
|
|
fbd0c96a8a | ||
|
|
e2251f9c86 | ||
|
|
eaaacbf218 | ||
|
|
243fbeed44 | ||
|
|
d62bab1b08 | ||
|
|
70e068ed17 | ||
|
|
824e8795d5 | ||
|
|
f768a09bd8 | ||
|
|
1a69a5a4c7 | ||
|
|
b9cc3781bb | ||
|
|
97964ed65c | ||
|
|
42a1d157bf | ||
|
|
e27efbc9c2 | ||
|
|
57a5d0b618 | ||
|
|
8f668cb5fb | ||
|
|
df5ea6f741 | ||
|
|
2e6847df1c | ||
|
|
76a49d6556 | ||
|
|
59fe3ccf3a | ||
|
|
ba884bd1aa | ||
|
|
e98bb76ee0 | ||
|
|
e08bf3eb97 | ||
|
|
9ef02d63a0 | ||
|
|
7007796497 | ||
|
|
9f030ffbf8 | ||
|
|
9ca172e0fc | ||
|
|
360758a2f3 | ||
|
|
ffe6f4b55c | ||
|
|
267b5e474c | ||
|
|
bf6bc5ba68 | ||
|
|
1efc4c91e2 | ||
|
|
a898196571 | ||
|
|
c158c7fc3d | ||
|
|
e3d83fe39a | ||
|
|
5327e1ddb2 | ||
|
|
a2ad13c54a | ||
|
|
816a22ec69 | ||
|
|
8a99dc2db4 | ||
|
|
f9cfe1479b | ||
|
|
5d8aa7bb58 | ||
|
|
f0638b1766 |
47
PRIVACY.md
47
PRIVACY.md
@@ -1,13 +1,48 @@
|
||||
# Amethyst Privacy Policy & Terms of Use
|
||||
# Amethyst Privacy Policy and Terms of Use
|
||||
|
||||
Effective as of Jan 20, 2023 for the distributed applications in the Play Store and the F-Droid Catalogue
|
||||
## Privacy Policy
|
||||
|
||||
The Amethyst app for Android does not collect or process any personal information from its users. The app is used to connect to third-party Nostr servers (also called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
|
||||
Effective as of Jun 12, 2023
|
||||
|
||||
The app may collect a per-device token, your public key and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts is only stored locally on the device when it is required for functionality and performance of Amethyst. This data is strictly confidental and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
|
||||
The Amethyst app for Android does not collect or process any personal information from its users. The app is used to connect to third-party Nostr servers (also called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
|
||||
|
||||
You cannot use the Amethyst app for Android to submit Objectionable Content to relays. Objectionable Content includes, but is not limited to: (i) sexually explicit materials; (ii) obscene, defamatory, libelous, slanderous, violent and/or unlawful content or profanity; (iii) content that infringes upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary right, or that is deceptive or fraudulent; (iv) content that promotes the use or sale of illegal or regulated substances, tobacco products, ammunition and/or firearms; and (v) illegal content related to gambling.
|
||||
The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts are only stored locally on the device when it's required for the functionality and performance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
|
||||
|
||||
We reserve the right to modify this Privacy Policy at any time. Any modifications to this Privacy Policy will be effective upon our posting the new terms and/or upon implementation of the new changes on the Service (or as otherwise indicated at the time of posting). In all cases, your continued use of the app after the posting of any modified Privacy Policy indicates your acceptance of the terms of the modified Privacy Policy.
|
||||
Amethyst offers a few options to upload pictures and videos in order to post them online. You can choose the server at your discretion. Similar to relays, such services are independent of the app and have their own privacy policy and terms of use.
|
||||
|
||||
### Privacy with Relays services
|
||||
|
||||
Your internet protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
|
||||
|
||||
The relay can also see which public keys you are using and what information you are requesting from the network. Your public key is tied to your IP address and your relay filters.
|
||||
|
||||
Relays have all your data in raw text. They know your IP, your name, your location (guessed from IP), your pub key, all your contacts, and other relays, and can read every action you do (post, like, boost, quote, report, etc) with the exception of the content inside Private Zaps and Private DMs.
|
||||
|
||||
While the content of direct messages (DMs) is only visible to you, and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link on the DM text. Your uploaded pictures are available to anyone with that direct link.
|
||||
|
||||
### Visibility & Permanence of Your Content on Nostr Relays
|
||||
|
||||
#### Information Visibility
|
||||
|
||||
Content that you share can be shared with other relays by any user of the network.
|
||||
Information that you share is publicly visible to anyone reading from relays that have your information. Your information may also be visible to Nostr users who do not share relays with you.
|
||||
|
||||
#### Information Permanence
|
||||
|
||||
Information shared on Nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted.
|
||||
|
||||
## Terms of Use
|
||||
|
||||
### For versions downloaded from Google's Play Store
|
||||
|
||||
You cannot use the Amethyst app for Android to submit Objectionable Content to relays. Objectionable Content includes but is not limited to: (i) sexually explicit materials; (ii) obscene, defamatory, libelous, slanderous, violent and/or unlawful content or profanity; (iii) content that infringes upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary rights, or that is deceptive or fraudulent; (iv) content that promotes the use or sale of illegal or regulated substances, tobacco products, ammunition and/or firearms; and (v) illegal content related to gambling.
|
||||
|
||||
### For versions downloaded from F-Droid
|
||||
|
||||
We do not control the distribution of the application in F-Droid. Legal matters should be resolved between the user and F-Droid.
|
||||
|
||||
## Other Notes
|
||||
|
||||
We reserve the right to modify this Privacy Policy and Terms of Use at any time. Any modifications to this document will be effective upon our posting of the new terms and/or upon implementation of the new changes on the Service (or as otherwise indicated at the time of posting). In all cases, your continued use of the app after the posting of any modified Privacy Policy and Terms of Use indicates your acceptance of the terms of the modified Privacy Policy and/or Terms of Use.
|
||||
|
||||
If you have any questions about Amethyst or this privacy policy, you can send a message to amethyst@vitorpamplona.com
|
||||
|
||||
@@ -61,14 +61,15 @@ Or get the latest APK from the [Releases Section](https://github.com/vitorpamplo
|
||||
- [x] Push Notifications (Zaps and Messages)
|
||||
- [x] Generic Tags (NIP-12)
|
||||
- [x] Sensitive Content (NIP-36)
|
||||
- [x] View Individual Reactions (Like, Boost, Zaps, Reports) per Post
|
||||
- [x] Recommended Application Handlers (NIP-89)
|
||||
- [x] Events with a Subject (NIP-14)
|
||||
- [ ] Marketplace (NIP-15)
|
||||
- [ ] Image/Video Capture in the app
|
||||
- [ ] Local Database
|
||||
- [ ] View Individual Reactions (Like, Boost, Zaps, Reports) per Post
|
||||
- [ ] Local Database
|
||||
- [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51)
|
||||
- [ ] Relay Pages (NIP-11)
|
||||
- [ ] Proof of Work in the Phone (NIP-13, NIP-20)
|
||||
- [ ] Events with a Subject (NIP-14)
|
||||
- [ ] Workspaces
|
||||
- [ ] Expiration Support (NIP-40)
|
||||
- [ ] Infinity Scroll
|
||||
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 202
|
||||
versionName "0.56.2"
|
||||
versionCode 210
|
||||
versionName "0.58.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
@@ -165,9 +165,14 @@ dependencies {
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
|
||||
// Markdown
|
||||
implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0"
|
||||
implementation "com.halilibo.compose-richtext:richtext-ui-material:0.16.0"
|
||||
implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
|
||||
//implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0"
|
||||
//implementation "com.halilibo.compose-richtext:richtext-ui-material:0.16.0"
|
||||
//implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
|
||||
|
||||
// Markdown (With fix for full-image bleeds)
|
||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui:main-SNAPSHOT')
|
||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-ui-material:main-SNAPSHOT')
|
||||
implementation('com.github.vitorpamplona.compose-richtext:richtext-commonmark:main-SNAPSHOT')
|
||||
|
||||
// Local model for language identification
|
||||
playImplementation 'com.google.mlkit:language-id:17.0.4'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
@@ -12,7 +13,7 @@ fun TranslatableRichTextViewer(
|
||||
canPreview: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) = ExpandableRichTextViewer(
|
||||
|
||||
@@ -5,15 +5,29 @@
|
||||
<package android:name="org.torproject.android"/>
|
||||
</queries>
|
||||
|
||||
<!-- Doesn't require a camera -->
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
|
||||
<!-- To connect with relays -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- To Upload media (newer devices) -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||
|
||||
<!-- To Upload media (old devices) -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- To read QRs that contain nostr:<NIP19> -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<!-- To read NFCs that contain nostr:<NIP19> -->
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<!-- To know receive notifications when the app connects/disconnects from the web -->
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
|
||||
<!-- Old permission to access media -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
|
||||
@@ -49,6 +49,7 @@ private object PrefKeys {
|
||||
const val LANGUAGE_PREFS = "languagePreferences"
|
||||
const val TRANSLATE_TO = "translateTo"
|
||||
const val ZAP_AMOUNTS = "zapAmounts"
|
||||
const val REACTION_CHOICES = "reactionChoices"
|
||||
const val DEFAULT_ZAPTYPE = "defaultZapType"
|
||||
const val DEFAULT_FILE_SERVER = "defaultFileServer"
|
||||
const val DEFAULT_HOME_FOLLOW_LIST = "defaultHomeFollowList"
|
||||
@@ -61,6 +62,8 @@ private object PrefKeys {
|
||||
const val USE_PROXY = "use_proxy"
|
||||
const val PROXY_PORT = "proxy_port"
|
||||
const val SHOW_SENSITIVE_CONTENT = "show_sensitive_content"
|
||||
const val WARN_ABOUT_REPORTS = "warn_about_reports"
|
||||
const val FILTER_SPAM_FROM_STRANGERS = "filter_spam_from_strangers"
|
||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||
}
|
||||
|
||||
@@ -206,6 +209,7 @@ object LocalPreferences {
|
||||
putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
|
||||
putString(PrefKeys.TRANSLATE_TO, account.translateTo)
|
||||
putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
|
||||
putString(PrefKeys.REACTION_CHOICES, gson.toJson(account.reactionChoices))
|
||||
putString(PrefKeys.DEFAULT_ZAPTYPE, gson.toJson(account.defaultZapType))
|
||||
putString(PrefKeys.DEFAULT_FILE_SERVER, gson.toJson(account.defaultFileServer))
|
||||
putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, account.defaultHomeFollowList)
|
||||
@@ -217,6 +221,8 @@ object LocalPreferences {
|
||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
||||
putBoolean(PrefKeys.USE_PROXY, account.proxy != null)
|
||||
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
||||
putBoolean(PrefKeys.WARN_ABOUT_REPORTS, account.warnAboutPostsWithReports)
|
||||
putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, account.filterSpamFromStrangers)
|
||||
|
||||
if (account.showSensitiveContent == null) {
|
||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||
@@ -254,6 +260,11 @@ object LocalPreferences {
|
||||
object : TypeToken<List<Long>>() {}.type
|
||||
) ?: listOf(500L, 1000L, 5000L)
|
||||
|
||||
val reactionChoices = gson.fromJson<List<String>>(
|
||||
getString(PrefKeys.REACTION_CHOICES, "[]"),
|
||||
object : TypeToken<List<String>>() {}.type
|
||||
).ifEmpty { listOf("+") } ?: listOf("+")
|
||||
|
||||
val defaultZapType = gson.fromJson(
|
||||
getString(PrefKeys.DEFAULT_ZAPTYPE, "PUBLIC"),
|
||||
object : TypeToken<LnZapEvent.ZapType>() {}.type
|
||||
@@ -306,28 +317,33 @@ object LocalPreferences {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val filterSpam = getBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, true)
|
||||
val warnAboutReports = getBoolean(PrefKeys.WARN_ABOUT_REPORTS, true)
|
||||
|
||||
val a = Account(
|
||||
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||
followingChannels,
|
||||
hiddenUsers,
|
||||
localRelays,
|
||||
dontTranslateFrom,
|
||||
languagePreferences,
|
||||
translateTo,
|
||||
zapAmountChoices,
|
||||
defaultZapType,
|
||||
defaultFileServer,
|
||||
defaultHomeFollowList,
|
||||
defaultStoriesFollowList,
|
||||
defaultNotificationFollowList,
|
||||
zapPaymentRequestServer,
|
||||
hideDeleteRequestDialog,
|
||||
hideBlockAlertDialog,
|
||||
latestContactList,
|
||||
proxy,
|
||||
proxyPort,
|
||||
showSensitiveContent
|
||||
loggedIn = Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||
followingChannels = followingChannels,
|
||||
hiddenUsers = hiddenUsers,
|
||||
localRelays = localRelays,
|
||||
dontTranslateFrom = dontTranslateFrom,
|
||||
languagePreferences = languagePreferences,
|
||||
translateTo = translateTo,
|
||||
zapAmountChoices = zapAmountChoices,
|
||||
reactionChoices = reactionChoices,
|
||||
defaultZapType = defaultZapType,
|
||||
defaultFileServer = defaultFileServer,
|
||||
defaultHomeFollowList = defaultHomeFollowList,
|
||||
defaultStoriesFollowList = defaultStoriesFollowList,
|
||||
defaultNotificationFollowList = defaultNotificationFollowList,
|
||||
zapPaymentRequest = zapPaymentRequestServer,
|
||||
hideDeleteRequestDialog = hideDeleteRequestDialog,
|
||||
hideBlockAlertDialog = hideBlockAlertDialog,
|
||||
backupContactList = latestContactList,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
warnAboutPostsWithReports = warnAboutReports,
|
||||
filterSpamFromStrangers = filterSpam
|
||||
)
|
||||
|
||||
return a
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
object OptOutFromFilters {
|
||||
var warnAboutPostsWithReports: Boolean = true
|
||||
var filterSpamFromStrangers: Boolean = true
|
||||
|
||||
fun start(warnAboutReports: Boolean, filterSpam: Boolean) {
|
||||
warnAboutPostsWithReports = warnAboutReports
|
||||
filterSpamFromStrangers = filterSpam
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
@@ -31,11 +32,15 @@ object ServiceManager {
|
||||
start(context)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun start(context: Context) {
|
||||
Log.d("ServiceManager", "Starting Relay Services")
|
||||
|
||||
val myAccount = account
|
||||
|
||||
// Resets Proxy Use
|
||||
HttpClient.start(account)
|
||||
OptOutFromFilters.start(account?.warnAboutPostsWithReports ?: true, account?.filterSpamFromStrangers ?: true)
|
||||
Coil.setImageLoader {
|
||||
ImageLoader.Builder(context).components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
@@ -75,6 +80,8 @@ object ServiceManager {
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
Log.d("ServiceManager", "Pausing Relay Services")
|
||||
|
||||
NostrAccountDataSource.stop()
|
||||
NostrHomeDataSource.stop()
|
||||
NostrChannelDataSource.stop()
|
||||
|
||||
@@ -19,6 +19,7 @@ object VideoCache {
|
||||
|
||||
lateinit var cacheDataSourceFactory: CacheDataSource.Factory
|
||||
|
||||
@Synchronized
|
||||
fun init(context: Context) {
|
||||
if (!this::simpleCache.isInitialized) {
|
||||
exoDatabaseProvider = StandaloneDatabaseProvider(context)
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.OptOutFromFilters
|
||||
import com.vitorpamplona.amethyst.service.FileHeader
|
||||
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.*
|
||||
@@ -55,6 +56,7 @@ class Account(
|
||||
var languagePreferences: Map<String, String> = mapOf(),
|
||||
var translateTo: String = Locale.getDefault().language,
|
||||
var zapAmountChoices: List<Long> = listOf(500L, 1000L, 5000L),
|
||||
var reactionChoices: List<String> = listOf("+"),
|
||||
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE,
|
||||
var defaultFileServer: ServersAvailable = ServersAvailable.NOSTR_BUILD,
|
||||
var defaultHomeFollowList: String = KIND3_FOLLOWS,
|
||||
@@ -66,7 +68,9 @@ class Account(
|
||||
var backupContactList: ContactListEvent? = null,
|
||||
var proxy: Proxy?,
|
||||
var proxyPort: Int,
|
||||
var showSensitiveContent: Boolean? = null
|
||||
var showSensitiveContent: Boolean? = null,
|
||||
var warnAboutPostsWithReports: Boolean = true,
|
||||
var filterSpamFromStrangers: Boolean = true
|
||||
) {
|
||||
var transientHiddenUsers: Set<String> = setOf()
|
||||
|
||||
@@ -77,6 +81,17 @@ class Account(
|
||||
|
||||
var userProfileCache: User? = null
|
||||
|
||||
fun updateOptOutOptions(warnReports: Boolean, filterSpam: Boolean) {
|
||||
warnAboutPostsWithReports = warnReports
|
||||
filterSpamFromStrangers = filterSpam
|
||||
OptOutFromFilters.start(warnAboutPostsWithReports, filterSpamFromStrangers)
|
||||
if (!filterSpamFromStrangers) {
|
||||
transientHiddenUsers = setOf()
|
||||
}
|
||||
live.invalidateData()
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
fun userProfile(): User {
|
||||
return userProfileCache ?: run {
|
||||
val myUser: User = LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
|
||||
@@ -129,8 +144,8 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun reactionTo(note: Note): List<Note> {
|
||||
return note.reactedBy(userProfile(), "+")
|
||||
fun reactionTo(note: Note, reaction: String): List<Note> {
|
||||
return note.reactedBy(userProfile(), reaction)
|
||||
}
|
||||
|
||||
fun hasBoosted(note: Note): Boolean {
|
||||
@@ -141,20 +156,20 @@ class Account(
|
||||
return note.boostedBy(userProfile())
|
||||
}
|
||||
|
||||
fun hasReacted(note: Note): Boolean {
|
||||
return note.hasReacted(userProfile(), "+")
|
||||
fun hasReacted(note: Note, reaction: String): Boolean {
|
||||
return note.hasReacted(userProfile(), reaction)
|
||||
}
|
||||
|
||||
fun reactTo(note: Note) {
|
||||
fun reactTo(note: Note, reaction: String) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (hasReacted(note)) {
|
||||
if (hasReacted(note, reaction)) {
|
||||
// has already liked this note
|
||||
return
|
||||
}
|
||||
|
||||
note.event?.let {
|
||||
val event = ReactionEvent.createLike(it, loggedIn.privKey!!)
|
||||
val event = ReactionEvent.create(reaction, it, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
@@ -483,7 +498,10 @@ class Account(
|
||||
mentions: List<User>?,
|
||||
tags: List<String>? = null,
|
||||
zapReceiver: String? = null,
|
||||
wantsToMarkAsSensitive: Boolean
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
@@ -499,6 +517,9 @@ class Account(
|
||||
extraTags = tags,
|
||||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
replyingTo = replyingTo,
|
||||
root = root,
|
||||
directMentions = directMentions,
|
||||
privateKey = loggedIn.privKey!!
|
||||
)
|
||||
|
||||
@@ -843,6 +864,12 @@ class Account(
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
fun changeReactionTypes(newTypes: List<String>) {
|
||||
reactionChoices = newTypes
|
||||
live.invalidateData()
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
fun changeZapPaymentRequest(newServer: Nip47URI?) {
|
||||
zapPaymentRequest = newServer
|
||||
live.invalidateData()
|
||||
@@ -1057,12 +1084,19 @@ class Account(
|
||||
}
|
||||
|
||||
fun isAcceptable(user: User): Boolean {
|
||||
if (!warnAboutPostsWithReports) {
|
||||
return !isHidden(user) && // if user hasn't hided this author
|
||||
user.reportsBy(userProfile()).isEmpty() // if user has not reported this post
|
||||
}
|
||||
return !isHidden(user) && // if user hasn't hided this author
|
||||
user.reportsBy(userProfile()).isEmpty() && // if user has not reported this post
|
||||
user.countReportAuthorsBy(followingKeySet()) < 5
|
||||
}
|
||||
|
||||
fun isAcceptableDirect(note: Note): Boolean {
|
||||
if (!warnAboutPostsWithReports) {
|
||||
return note.reportsBy(userProfile()).isEmpty()
|
||||
}
|
||||
return note.reportsBy(userProfile()).isEmpty() && // if user has not reported this post
|
||||
note.countReportAuthorsBy(followingKeySet()) < 5 // if it has 5 reports by reliable users
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.OptOutFromFilters
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.model.Event
|
||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||
@@ -20,6 +21,8 @@ class AntiSpamFilter {
|
||||
fun isSpam(event: Event, relay: Relay?): Boolean {
|
||||
checkNotInMainThread()
|
||||
|
||||
if (!OptOutFromFilters.filterSpamFromStrangers) return false
|
||||
|
||||
val idHex = event.id
|
||||
|
||||
// if short message, ok
|
||||
|
||||
@@ -51,6 +51,18 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun getUserIfExists(key: String): User? {
|
||||
return users[key]
|
||||
}
|
||||
|
||||
fun getNoteIfExists(key: String): Note? {
|
||||
return addressables[key] ?: notes[key]
|
||||
}
|
||||
|
||||
fun getChannelIfExists(key: String): Channel? {
|
||||
return channels[key]
|
||||
}
|
||||
|
||||
fun checkGetOrCreateNote(key: String): Note? {
|
||||
checkNotInMainThread()
|
||||
|
||||
@@ -542,17 +554,8 @@ object LocalCache {
|
||||
|
||||
// Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
if (
|
||||
event.content == "" ||
|
||||
event.content == "+" ||
|
||||
event.content == "\u2764\uFE0F" || // red heart
|
||||
event.content == "\uD83E\uDD19" || // call me hand
|
||||
event.content == "\uD83D\uDC4D" // thumbs up
|
||||
) {
|
||||
// Counts the replies
|
||||
repliesTo.forEach {
|
||||
it.addReaction(note)
|
||||
}
|
||||
repliesTo.forEach {
|
||||
it.addReaction(note)
|
||||
}
|
||||
|
||||
refreshObservers(note)
|
||||
|
||||
@@ -43,7 +43,7 @@ open class Note(val idHex: String) {
|
||||
// These fields are updated every time an event related to this note is received.
|
||||
var replies = setOf<Note>()
|
||||
private set
|
||||
var reactions = setOf<Note>()
|
||||
var reactions = mapOf<String, Set<Note>>()
|
||||
private set
|
||||
var boosts = setOf<Note>()
|
||||
private set
|
||||
@@ -134,8 +134,20 @@ open class Note(val idHex: String) {
|
||||
liveSet?.boosts?.invalidateData()
|
||||
}
|
||||
fun removeReaction(note: Note) {
|
||||
reactions = reactions - note
|
||||
liveSet?.reactions?.invalidateData()
|
||||
val reaction = note.event?.content() ?: "+"
|
||||
|
||||
if (reaction in reactions.keys && reactions[reaction]?.contains(note) == true) {
|
||||
reactions[reaction]?.let {
|
||||
val newList = it.minus(note)
|
||||
if (newList.isEmpty()) {
|
||||
reactions = reactions.minus(reaction)
|
||||
} else {
|
||||
reactions = reactions + Pair(reaction, newList)
|
||||
}
|
||||
|
||||
liveSet?.reactions?.invalidateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeReport(deleteNote: Note) {
|
||||
@@ -203,8 +215,13 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun addReaction(note: Note) {
|
||||
if (note !in reactions) {
|
||||
reactions = reactions + note
|
||||
val reaction = note.event?.content() ?: "+"
|
||||
|
||||
if (reaction !in reactions.keys) {
|
||||
reactions = reactions + Pair(reaction, setOf(note))
|
||||
liveSet?.reactions?.invalidateData()
|
||||
} else if (reactions[reaction]?.contains(note) == false) {
|
||||
reactions = reactions + Pair(reaction, (reactions[reaction] ?: emptySet()) + note)
|
||||
liveSet?.reactions?.invalidateData()
|
||||
}
|
||||
}
|
||||
@@ -243,8 +260,10 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun isReactedBy(user: User): Boolean {
|
||||
return reactions.any { it.author?.pubkeyHex == user.pubkeyHex }
|
||||
fun isReactedBy(user: User): String? {
|
||||
return reactions.filter {
|
||||
it.value.any { it.author?.pubkeyHex == user.pubkeyHex }
|
||||
}.keys.firstOrNull()
|
||||
}
|
||||
|
||||
fun isBoostedBy(user: User): Boolean {
|
||||
@@ -269,6 +288,10 @@ open class Note(val idHex: String) {
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
fun countReactions(): Int {
|
||||
return reactions.values.sumOf { it.size }
|
||||
}
|
||||
|
||||
fun zappedAmount(privKey: ByteArray?, walletServicePubkey: ByteArray?): BigDecimal {
|
||||
// Regular Zap Receipts
|
||||
val completedZaps = zaps.asSequence()
|
||||
@@ -361,7 +384,11 @@ open class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun reactedBy(loggedIn: User, content: String): List<Note> {
|
||||
return reactions.filter { it.author == loggedIn && it.event?.content() == content }
|
||||
return reactions[content]?.filter { it.author == loggedIn && it.event?.content() == content } ?: emptyList()
|
||||
}
|
||||
|
||||
fun reactedBy(loggedIn: User): List<String> {
|
||||
return reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
||||
}
|
||||
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
import android.util.LruCache
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.previews.BahaUrlPreview
|
||||
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||
|
||||
object UrlCachedPreviewer {
|
||||
var cache = mapOf<String, UrlInfoItem>()
|
||||
private set
|
||||
var failures = mapOf<String, Throwable>()
|
||||
var cache = LruCache<String, UrlPreviewState>(100)
|
||||
private set
|
||||
|
||||
fun previewInfo(url: String, callback: IUrlPreviewCallback? = null) {
|
||||
fun previewInfo(url: String, onReady: (UrlPreviewState) -> Unit) {
|
||||
checkNotInMainThread()
|
||||
|
||||
cache[url]?.let {
|
||||
callback?.onComplete(it)
|
||||
return
|
||||
}
|
||||
|
||||
failures[url]?.let {
|
||||
callback?.onFailed(it)
|
||||
onReady(it)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,13 +23,32 @@ object UrlCachedPreviewer {
|
||||
url,
|
||||
object : IUrlPreviewCallback {
|
||||
override fun onComplete(urlInfo: UrlInfoItem) {
|
||||
cache = cache + Pair(url, urlInfo)
|
||||
callback?.onComplete(urlInfo)
|
||||
cache[url]?.let {
|
||||
if (it is UrlPreviewState.Loaded || it is UrlPreviewState.Empty) {
|
||||
onReady(it)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val state = if (urlInfo.allFetchComplete() && urlInfo.url == url) {
|
||||
UrlPreviewState.Loaded(urlInfo)
|
||||
} else {
|
||||
UrlPreviewState.Empty
|
||||
}
|
||||
|
||||
cache.put(url, state)
|
||||
onReady(state)
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable) {
|
||||
failures = failures + Pair(url, throwable)
|
||||
callback?.onFailed(throwable)
|
||||
cache[url]?.let {
|
||||
onReady(it)
|
||||
return
|
||||
}
|
||||
|
||||
val state = UrlPreviewState.Error(throwable.message ?: "Error Loading url preview")
|
||||
cache.put(url, state)
|
||||
onReady(state)
|
||||
}
|
||||
}
|
||||
).fetchUrlPreview()
|
||||
|
||||
@@ -19,6 +19,10 @@ class TextNoteEvent(
|
||||
sig: HexKey
|
||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
|
||||
fun subject() = tags.firstOrNull() { it.size > 1 && it[0] == "subject" }?.get(1)
|
||||
|
||||
fun root() = tags.firstOrNull() { it.size > 3 && it[3] == "root" }?.get(1)
|
||||
|
||||
companion object {
|
||||
const val kind = 1
|
||||
|
||||
@@ -30,19 +34,44 @@ class TextNoteEvent(
|
||||
extraTags: List<String>?,
|
||||
zapReceiver: String?,
|
||||
markAsSensitive: Boolean,
|
||||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>,
|
||||
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = Date().time / 1000
|
||||
): TextNoteEvent {
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
val tags = mutableListOf<List<String>>()
|
||||
replyTos?.forEach {
|
||||
tags.add(listOf("e", it))
|
||||
if (it == replyingTo) {
|
||||
tags.add(listOf("e", it, "", "reply"))
|
||||
} else if (it == root) {
|
||||
tags.add(listOf("e", it, "", "root"))
|
||||
} else if (it in directMentions) {
|
||||
tags.add(listOf("e", it, "", "mention"))
|
||||
} else {
|
||||
tags.add(listOf("e", it))
|
||||
}
|
||||
}
|
||||
mentions?.forEach {
|
||||
tags.add(listOf("p", it))
|
||||
if (it in directMentions) {
|
||||
tags.add(listOf("p", it, "", "mention"))
|
||||
} else {
|
||||
tags.add(listOf("p", it))
|
||||
}
|
||||
}
|
||||
addresses?.forEach {
|
||||
tags.add(listOf("a", it.toTag()))
|
||||
val aTag = it.toTag()
|
||||
if (aTag == replyingTo) {
|
||||
tags.add(listOf("a", aTag, "", "reply"))
|
||||
} else if (aTag == root) {
|
||||
tags.add(listOf("a", aTag, "", "root"))
|
||||
} else if (aTag in directMentions) {
|
||||
tags.add(listOf("a", aTag, "", "mention"))
|
||||
} else {
|
||||
tags.add(listOf("a", aTag))
|
||||
}
|
||||
}
|
||||
findHashtags(msg).forEach {
|
||||
tags.add(listOf("t", it))
|
||||
|
||||
@@ -22,12 +22,14 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
fun consume(event: Event) {
|
||||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
scope.launch {
|
||||
// adds to database
|
||||
LocalCache.verifyAndConsume(event, null)
|
||||
if (LocalCache.notes[event.id] == null) {
|
||||
// adds to database
|
||||
LocalCache.verifyAndConsume(event, null)
|
||||
|
||||
when (event) {
|
||||
is PrivateDmEvent -> notify(event)
|
||||
is LnZapEvent -> notify(event)
|
||||
when (event) {
|
||||
is PrivateDmEvent -> notify(event)
|
||||
is LnZapEvent -> notify(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package com.vitorpamplona.amethyst.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -56,6 +61,15 @@ class MainActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val networkRequest = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.build()
|
||||
|
||||
val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||
|
||||
Client.lenient = true
|
||||
}
|
||||
|
||||
@@ -90,6 +104,37 @@ class MainActivity : FragmentActivity() {
|
||||
ServiceManager.cleanUp()
|
||||
}
|
||||
}
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
// network is available for use
|
||||
override fun onAvailable(network: Network) {
|
||||
super.onAvailable(network)
|
||||
Log.d("NETWORKCALLBACK", "onAvailable: Disconnecting and connecting again")
|
||||
// Only starts after login
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ServiceManager.pause()
|
||||
ServiceManager.start(this@MainActivity)
|
||||
}
|
||||
}
|
||||
|
||||
// Network capabilities have changed for the network
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
networkCapabilities: NetworkCapabilities
|
||||
) {
|
||||
super.onCapabilitiesChanged(network, networkCapabilities)
|
||||
}
|
||||
|
||||
// lost network connection
|
||||
override fun onLost(network: Network) {
|
||||
super.onLost(network)
|
||||
Log.d("NETWORKCALLBACK", "onLost: Disconnecting and pausing relay's connection")
|
||||
// Only starts after login
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ServiceManager.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GetMediaActivityResultContract : ActivityResultContracts.GetContent() {
|
||||
|
||||
@@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -128,7 +129,7 @@ fun JoinUserOrChannelView(searchBarViewModel: SearchBarViewModel, onClose: () ->
|
||||
|
||||
Text(
|
||||
text = "",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
@@ -270,7 +271,7 @@ private fun SearchEditTextForJoin(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.channel_list_user_or_group_id_demo),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
@@ -416,7 +417,7 @@ private fun DisplayUserAboutInfo(baseUser: User) {
|
||||
|
||||
Text(
|
||||
text = about,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -79,7 +80,7 @@ fun NewChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, chan
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_awesome_group),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -98,7 +99,7 @@ fun NewChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, chan
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "http://mygroup.com/logo.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -115,7 +116,7 @@ fun NewChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, chan
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.about_us),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.components.*
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -221,7 +222,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.content_description_example),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
@@ -8,11 +9,16 @@ import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||
|
||||
class NewMessageTagger(var channelHex: String?, var mentions: List<User>?, var replyTos: List<Note>?, var message: String) {
|
||||
|
||||
val directMentions = mutableSetOf<HexKey>()
|
||||
|
||||
fun addUserToMentions(user: User) {
|
||||
directMentions.add(user.pubkeyHex)
|
||||
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
|
||||
}
|
||||
|
||||
fun addNoteToReplyTos(note: Note) {
|
||||
directMentions.add(note.idHex)
|
||||
|
||||
note.author?.let { addUserToMentions(it) }
|
||||
replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewPollClosing(pollViewModel: NewPostViewModel) {
|
||||
@@ -43,7 +44,7 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) {
|
||||
)
|
||||
val colorValid = TextFieldDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = MaterialTheme.colors.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
unfocusedBorderColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Row(
|
||||
@@ -59,13 +60,13 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_closing_time),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_closing_time_days),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
|
||||
@@ -43,7 +44,7 @@ fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
|
||||
)
|
||||
val colorValid = TextFieldDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = MaterialTheme.colors.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
unfocusedBorderColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Row(
|
||||
@@ -59,13 +60,13 @@ fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_consensus_threshold),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_consensus_threshold_percent),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewPollOption(pollViewModel: NewPostViewModel, optionIndex: Int) {
|
||||
@@ -35,13 +36,13 @@ fun NewPollOption(pollViewModel: NewPostViewModel, optionIndex: Int) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_option_index).format(optionIndex + 1),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_option_description),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
@@ -38,7 +39,7 @@ fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) {
|
||||
)
|
||||
val colorValid = TextFieldDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = MaterialTheme.colors.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
unfocusedBorderColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -49,7 +50,7 @@ fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_primary_description),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -67,7 +68,7 @@ fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_primary_description),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
colors = if (isInputValid) colorValid else colorInValid,
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewPollRecipientsField(pollViewModel: NewPostViewModel, account: Account) {
|
||||
@@ -29,13 +30,13 @@ fun NewPollRecipientsField(pollViewModel: NewPostViewModel, account: Account) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_zap_recipients),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_zap_recipients),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
|
||||
@@ -71,7 +72,7 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
|
||||
)
|
||||
val colorValid = TextFieldDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = MaterialTheme.colors.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
unfocusedBorderColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Row(
|
||||
@@ -87,13 +88,13 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_zap_value_min),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.sats),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -106,13 +107,13 @@ fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.poll_zap_value_max),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.sats),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -71,6 +71,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -198,7 +203,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.what_s_on_your_mind),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults
|
||||
@@ -217,9 +222,9 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
||||
|
||||
Button(
|
||||
onClick = { postViewModel.pollOptions[postViewModel.pollOptions.size] = "" },
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.placeholderText),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
contentColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
@@ -288,11 +293,11 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
)
|
||||
)
|
||||
} else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
@@ -301,10 +306,15 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
||||
UrlPreview(myUrlPreview, myUrlPreview)
|
||||
}
|
||||
} else if (startsWithNIP19Scheme(myUrlPreview)) {
|
||||
val bgColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember {
|
||||
mutableStateOf(bgColor)
|
||||
}
|
||||
|
||||
BechLink(
|
||||
myUrlPreview,
|
||||
true,
|
||||
MaterialTheme.colors.background,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
@@ -388,7 +398,7 @@ fun Notifying(baseMentions: ImmutableList<User>?, onClick: (User) -> Unit) {
|
||||
Text(
|
||||
stringResource(R.string.reply_notify),
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
mentions.forEachIndexed { idx, user ->
|
||||
@@ -401,9 +411,9 @@ fun Notifying(baseMentions: ImmutableList<User>?, onClick: (User) -> Unit) {
|
||||
}
|
||||
|
||||
Button(
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
||||
backgroundColor = MaterialTheme.colors.mediumImportanceLink
|
||||
),
|
||||
onClick = {
|
||||
onClick(myUser)
|
||||
@@ -544,7 +554,7 @@ private fun ForwardZapTo(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_lnAddress),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
},
|
||||
@@ -619,7 +629,7 @@ fun CloseButton(onCancel: () -> Unit) {
|
||||
onClick = {
|
||||
onCancel()
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = Color.Gray
|
||||
@@ -643,7 +653,7 @@ fun PostButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier =
|
||||
onPost()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
@@ -662,7 +672,7 @@ fun SaveButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier =
|
||||
onPost()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
@@ -681,7 +691,7 @@ fun CreateButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
|
||||
onPost()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
@@ -700,7 +710,7 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
|
||||
onPost()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
@@ -764,11 +774,11 @@ fun ImageVideoDescription(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 30.dp, end = 30.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
@@ -812,7 +822,7 @@ fun ImageVideoDescription(
|
||||
modifier = Modifier
|
||||
.padding(end = 5.dp)
|
||||
.size(30.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -902,7 +912,7 @@ fun ImageVideoDescription(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.content_description_example),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -919,7 +929,7 @@ fun ImageVideoDescription(
|
||||
onClick = {
|
||||
onAdd(message, selectedServer)
|
||||
},
|
||||
shape = RoundedCornerShape(15.dp),
|
||||
shape = QuoteBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
|
||||
@@ -137,7 +137,24 @@ open class NewPostViewModel() : ViewModel() {
|
||||
} else if (originalNote?.event is PrivateDmEvent) {
|
||||
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
||||
} else {
|
||||
account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver, wantsToMarkAsSensitive)
|
||||
// adds markers
|
||||
val rootId =
|
||||
(originalNote?.event as? TextNoteEvent)?.root() // if it has a marker as root
|
||||
?: originalNote?.replyTo?.firstOrNull { it.event != null && it.replyTo?.isEmpty() == true }?.idHex // if it has loaded events with zero replies in the reply list
|
||||
?: originalNote?.replyTo?.firstOrNull()?.idHex // old rules, first item is root.
|
||||
val replyId = originalNote?.idHex
|
||||
|
||||
account?.sendPost(
|
||||
message = tagger.message,
|
||||
replyTo = tagger.replyTos,
|
||||
mentions = tagger.mentions,
|
||||
tags = null,
|
||||
zapReceiver = zapReceiver,
|
||||
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||
replyingTo = replyId,
|
||||
root = rootId,
|
||||
directMentions = tagger.directMentions
|
||||
)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
@@ -11,7 +11,6 @@ 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.Divider
|
||||
@@ -52,6 +51,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import java.lang.Math.round
|
||||
|
||||
@Composable
|
||||
@@ -157,7 +158,7 @@ fun ServerConfigHeader() {
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1.2f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
@@ -167,7 +168,7 @@ fun ServerConfigHeader() {
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1.2f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
@@ -177,7 +178,7 @@ fun ServerConfigHeader() {
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
@@ -187,7 +188,7 @@ fun ServerConfigHeader() {
|
||||
maxLines = 1,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
@@ -375,7 +376,7 @@ fun ServerConfig(
|
||||
maxLines = 1,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.weight(1.2f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
IconButton(
|
||||
@@ -403,7 +404,7 @@ fun ServerConfig(
|
||||
maxLines = 1,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.weight(1.2f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Icon(
|
||||
@@ -420,7 +421,7 @@ fun ServerConfig(
|
||||
maxLines = 1,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
Icon(
|
||||
@@ -435,7 +436,7 @@ fun ServerConfig(
|
||||
maxLines = 1,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -464,7 +465,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "server.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
@@ -478,7 +479,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
|
||||
modifier = Modifier
|
||||
.size(35.dp)
|
||||
.padding(horizontal = 5.dp),
|
||||
tint = if (read) Color.Green else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = if (read) Color.Green else MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
|
||||
@@ -489,7 +490,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
|
||||
modifier = Modifier
|
||||
.size(35.dp)
|
||||
.padding(horizontal = 5.dp),
|
||||
tint = if (write) Color.Green else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = if (write) Color.Green else MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
|
||||
@@ -504,10 +505,10 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni
|
||||
read = true
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else MaterialTheme.colors.placeholderText
|
||||
)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.add), color = Color.White)
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -95,7 +96,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_display_name),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -114,7 +115,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.my_username),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
@@ -133,7 +134,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.about_me),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -152,13 +153,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com/me.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
UploadFromGallery(
|
||||
isUploading = postViewModel.isUploadingImageForPicture,
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
tint = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(start = 5.dp)
|
||||
) {
|
||||
postViewModel.uploadForPicture(it, context)
|
||||
@@ -177,13 +178,13 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com/mybanner.jpg",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
UploadFromGallery(
|
||||
isUploading = postViewModel.isUploadingImageForBanner,
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
tint = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(start = 5.dp)
|
||||
) {
|
||||
postViewModel.uploadForBanner(it, context)
|
||||
@@ -202,7 +203,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "https://mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
@@ -218,7 +219,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "_@mywebsite.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
@@ -233,7 +234,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "me@mylightiningnode.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
@@ -249,7 +250,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.lnurl),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -264,7 +265,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.twitter_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -279,7 +280,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.mastodon_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -294,7 +295,7 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.github_proof_url_template),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.actions
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
@@ -12,11 +11,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -75,7 +74,7 @@ fun SaveToGallery(url: String) {
|
||||
writeStoragePermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = Color.Gray
|
||||
@@ -135,7 +134,7 @@ fun SaveToGallery(localFile: File, mimeType: String?) {
|
||||
writeStoragePermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = Color.Gray
|
||||
|
||||
@@ -90,7 +90,7 @@ private fun LoadNote(
|
||||
hex: String,
|
||||
content: @Composable (Note) -> Unit
|
||||
) {
|
||||
var noteBase by remember(hex) { mutableStateOf<Note?>(null) }
|
||||
var noteBase by remember(hex) { mutableStateOf(LocalCache.getNoteIfExists(hex)) }
|
||||
|
||||
LaunchedEffect(key1 = hex) {
|
||||
if (noteBase == null) {
|
||||
@@ -184,11 +184,13 @@ private fun DisplayAddress(
|
||||
nip19: Nip19.Return,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
var noteBase by remember(nip19) { mutableStateOf<Note?>(null) }
|
||||
var noteBase by remember(nip19) { mutableStateOf(LocalCache.getNoteIfExists(nip19.hex)) }
|
||||
|
||||
LaunchedEffect(key1 = nip19.hex) {
|
||||
launch(Dispatchers.IO) {
|
||||
noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
|
||||
if (noteBase == null) {
|
||||
launch(Dispatchers.IO) {
|
||||
noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,32 +223,18 @@ private fun DisplayUser(
|
||||
nip19: Nip19.Return,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
var userBase by remember(nip19) { mutableStateOf<User?>(null) }
|
||||
var userBase by remember(nip19) { mutableStateOf(LocalCache.getUserIfExists(nip19.hex)) }
|
||||
|
||||
LaunchedEffect(key1 = nip19.hex) {
|
||||
launch(Dispatchers.IO) {
|
||||
userBase = LocalCache.checkGetOrCreateUser(nip19.hex)
|
||||
if (userBase == null) {
|
||||
launch(Dispatchers.IO) {
|
||||
userBase = LocalCache.checkGetOrCreateUser(nip19.hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userBase?.let {
|
||||
val userState by it.live().metadata.observeAsState()
|
||||
val route = remember(userState) { "User/${it.pubkeyHex}" }
|
||||
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
||||
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
||||
val addedCharts = remember {
|
||||
"${nip19.additionalChars} "
|
||||
}
|
||||
|
||||
if (userDisplayName != null) {
|
||||
CreateClickableTextWithEmoji(
|
||||
clickablePart = userDisplayName,
|
||||
suffix = addedCharts,
|
||||
tags = userTags,
|
||||
route = route,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
RenderUserAsClickableText(it, nip19, nav)
|
||||
}
|
||||
|
||||
if (userBase == null) {
|
||||
@@ -258,6 +246,42 @@ private fun DisplayUser(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderUserAsClickableText(
|
||||
baseUser: User,
|
||||
nip19: Nip19.Return,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val userState by baseUser.live().metadata.observeAsState()
|
||||
val route = remember { "User/${baseUser.pubkeyHex}" }
|
||||
|
||||
val userDisplayName by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.toBestDisplayName()
|
||||
}
|
||||
}
|
||||
|
||||
val userTags by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists()
|
||||
}
|
||||
}
|
||||
|
||||
val addedCharts = remember(nip19) {
|
||||
"${nip19.additionalChars} "
|
||||
}
|
||||
|
||||
userDisplayName?.let {
|
||||
CreateClickableTextWithEmoji(
|
||||
clickablePart = it,
|
||||
suffix = addedCharts,
|
||||
tags = userTags,
|
||||
route = route,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateClickableText(
|
||||
clickablePart: String,
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -20,14 +19,16 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||
|
||||
const val SHORT_TEXT_LENGTH = 350
|
||||
|
||||
@@ -37,7 +38,7 @@ fun ExpandableRichTextViewer(
|
||||
canPreview: Boolean,
|
||||
modifier: Modifier,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -79,14 +80,16 @@ fun ExpandableRichTextViewer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
backgroundColor.copy(alpha = 0f),
|
||||
backgroundColor
|
||||
.drawBehind {
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
backgroundColor.value.copy(alpha = 0f),
|
||||
backgroundColor.value
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
ShowMoreButton() {
|
||||
showFullText = !showFullText
|
||||
@@ -97,14 +100,13 @@ fun ExpandableRichTextViewer(
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun ShowMoreButton(onClick: () -> Unit) {
|
||||
fun ShowMoreButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
||||
.compositeOver(MaterialTheme.colors.background)
|
||||
backgroundColor = MaterialTheme.colors.secondaryButtonBackground
|
||||
),
|
||||
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
|
||||
) {
|
||||
|
||||
@@ -8,7 +8,6 @@ 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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -25,6 +24,8 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.NumberFormat
|
||||
@@ -66,8 +67,8 @@ fun InvoicePreview(lnInvoice: String, amount: String?) {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 30.dp, end = 30.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(1.dp, MaterialTheme.colors.subtleBorder, QuoteBorder)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -118,7 +119,7 @@ fun InvoicePreview(lnInvoice: String, amount: String?) {
|
||||
startActivity(context, intent, null)
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(15.dp),
|
||||
shape = QuoteBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ 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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
@@ -37,6 +36,9 @@ import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -56,8 +58,8 @@ fun InvoiceRequest(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 30.dp, end = 30.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(1.dp, MaterialTheme.colors.subtleBorder, QuoteBorder)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -98,7 +100,7 @@ fun InvoiceRequest(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.thank_you_so_much),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -123,7 +125,7 @@ fun InvoiceRequest(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
@@ -153,7 +155,7 @@ fun InvoiceRequest(
|
||||
}
|
||||
)
|
||||
},
|
||||
shape = RoundedCornerShape(15.dp),
|
||||
shape = QuoteBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
|
||||
@@ -58,7 +58,8 @@ class MediaCompressor {
|
||||
}
|
||||
|
||||
override fun onFailure(index: Int, failureMessage: String) {
|
||||
onError(failureMessage)
|
||||
// keeps going with original video
|
||||
onReady(uri, contentType, null)
|
||||
}
|
||||
|
||||
override fun onCancelled(index: Int) {
|
||||
|
||||
@@ -6,13 +6,13 @@ import android.util.Patterns
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -24,11 +24,15 @@ import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.halilibo.richtext.markdown.Markdown
|
||||
import com.halilibo.richtext.markdown.MarkdownParseOptions
|
||||
import com.halilibo.richtext.ui.HeadingStyle
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.material.MaterialRichText
|
||||
import com.halilibo.richtext.ui.resolveDefaults
|
||||
@@ -43,6 +47,8 @@ import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
@@ -83,6 +89,36 @@ fun isValidURL(url: String?): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
internal val DefaultHeadingStyle: HeadingStyle = { level, textStyle ->
|
||||
when (level) {
|
||||
0 -> textStyle.copy(
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Light
|
||||
)
|
||||
1 -> textStyle.copy(
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Light
|
||||
)
|
||||
2 -> textStyle.copy(
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Light
|
||||
)
|
||||
3 -> textStyle.copy(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
4 -> textStyle.copy(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
5 -> textStyle.copy(
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
else -> textStyle
|
||||
}
|
||||
}
|
||||
|
||||
internal val DefaultParagraphSpacing: TextUnit = 12.sp
|
||||
val richTextDefaults = RichTextStyle().resolveDefaults()
|
||||
|
||||
fun isMarkdown(content: String): Boolean {
|
||||
@@ -100,7 +136,7 @@ fun RichTextViewer(
|
||||
canPreview: Boolean,
|
||||
modifier: Modifier,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -129,7 +165,7 @@ private fun RenderRegular(
|
||||
content: String,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -159,7 +195,7 @@ private fun RenderParagraph(
|
||||
paragraph: String,
|
||||
state: RichTextViewerState,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
tags: ImmutableListOfLists<String>
|
||||
@@ -234,7 +270,7 @@ private fun RenderWord(
|
||||
word: String,
|
||||
state: RichTextViewerState,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
tags: ImmutableListOfLists<String>
|
||||
@@ -288,7 +324,7 @@ private fun RenderWordWithoutPreview(
|
||||
word: String,
|
||||
state: RichTextViewerState,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -321,7 +357,7 @@ private fun RenderWordWithPreview(
|
||||
word: String,
|
||||
state: RichTextViewerState,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -402,8 +438,10 @@ fun RenderCustomEmoji(word: String, state: RichTextViewerState) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: ImmutableListOfLists<String>?, nav: (String) -> Unit) {
|
||||
private fun RenderContentAsMarkdown(content: String, backgroundColor: MutableState<Color>, tags: ImmutableListOfLists<String>?, nav: (String) -> Unit) {
|
||||
val myMarkDownStyle = richTextDefaults.copy(
|
||||
paragraphSpacing = DefaultParagraphSpacing,
|
||||
headingStyle = DefaultHeadingStyle,
|
||||
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
|
||||
textStyle = TextStyle(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
@@ -412,16 +450,16 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag
|
||||
modifier = Modifier
|
||||
.padding(0.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
)
|
||||
.background(
|
||||
MaterialTheme.colors.onSurface
|
||||
.copy(alpha = 0.05f)
|
||||
.compositeOver(backgroundColor)
|
||||
.compositeOver(backgroundColor.value)
|
||||
)
|
||||
),
|
||||
stringStyle = richTextDefaults.stringStyle?.copy(
|
||||
@@ -432,7 +470,7 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 14.sp,
|
||||
background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f)
|
||||
.compositeOver(backgroundColor)
|
||||
.compositeOver(backgroundColor.value)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -448,15 +486,17 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag
|
||||
Unit
|
||||
}
|
||||
|
||||
MaterialRichText(
|
||||
style = myMarkDownStyle
|
||||
) {
|
||||
RefreshableContent(content, tags) {
|
||||
Markdown(
|
||||
content = it,
|
||||
markdownParseOptions = MarkdownParseOptions.Default,
|
||||
onLinkClicked = onClick
|
||||
)
|
||||
ProvideTextStyle(TextStyle(lineHeight = 1.30.em)) {
|
||||
MaterialRichText(
|
||||
style = myMarkDownStyle
|
||||
) {
|
||||
RefreshableContent(content, tags) {
|
||||
Markdown(
|
||||
content = it,
|
||||
markdownParseOptions = MarkdownParseOptions.Default,
|
||||
onLinkClicked = onClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,7 +548,7 @@ private fun ObserveNIP19Event(
|
||||
it: Nip19.Return,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
var baseNote by remember(it) { mutableStateOf<Note?>(null) }
|
||||
var baseNote by remember(it) { mutableStateOf<Note?>(LocalCache.getNoteIfExists(it.hex)) }
|
||||
|
||||
LaunchedEffect(key1 = it.hex) {
|
||||
if (baseNote == null) {
|
||||
@@ -523,13 +563,18 @@ private fun ObserveNIP19Event(
|
||||
}
|
||||
|
||||
baseNote?.let { note ->
|
||||
val noteState by note.live().metadata.observeAsState()
|
||||
ObserveNote(note, onRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = noteState) {
|
||||
if (noteState?.note?.event != null) {
|
||||
launch(Dispatchers.IO) {
|
||||
onRefresh()
|
||||
}
|
||||
@Composable
|
||||
fun ObserveNote(note: Note, onRefresh: () -> Unit) {
|
||||
val noteState by note.live().metadata.observeAsState()
|
||||
|
||||
LaunchedEffect(key1 = noteState) {
|
||||
if (noteState?.note?.event != null) {
|
||||
launch(Dispatchers.IO) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,7 +585,7 @@ private fun ObserveNIP19User(
|
||||
it: Nip19.Return,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
var baseUser by remember(it) { mutableStateOf<User?>(null) }
|
||||
var baseUser by remember(it) { mutableStateOf<User?>(LocalCache.getUserIfExists(it.hex)) }
|
||||
|
||||
LaunchedEffect(key1 = it.hex) {
|
||||
if (baseUser == null) {
|
||||
@@ -555,13 +600,18 @@ private fun ObserveNIP19User(
|
||||
}
|
||||
|
||||
baseUser?.let { user ->
|
||||
val userState by user.live().metadata.observeAsState()
|
||||
ObserveUser(user, onRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = userState) {
|
||||
if (userState?.user?.info != null) {
|
||||
launch(Dispatchers.IO) {
|
||||
onRefresh()
|
||||
}
|
||||
@Composable
|
||||
private fun ObserveUser(user: User, onRefresh: () -> Unit) {
|
||||
val userState by user.live().metadata.observeAsState()
|
||||
|
||||
LaunchedEffect(key1 = userState) {
|
||||
if (userState?.user?.info != null) {
|
||||
launch(Dispatchers.IO) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,7 +701,7 @@ private fun returnMarkdownWithSpecialContent(content: String, tags: ImmutableLis
|
||||
if (isValidURL(word)) {
|
||||
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
||||
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
returnContent += "$word "
|
||||
returnContent += " "
|
||||
} else {
|
||||
returnContent += "[$word]($word) "
|
||||
}
|
||||
@@ -722,7 +772,7 @@ fun startsWithNIP19Scheme(word: String): Boolean {
|
||||
data class LoadedBechLink(val baseNote: Note?, val nip19: Nip19.Return)
|
||||
|
||||
@Composable
|
||||
fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
fun BechLink(word: String, canPreview: Boolean, backgroundColor: MutableState<Color>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
var loadedLink by remember { mutableStateOf<LoadedBechLink?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = word) {
|
||||
@@ -763,21 +813,21 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
|
||||
private fun DisplayFullNote(
|
||||
it: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
loadedLink: LoadedBechLink
|
||||
) {
|
||||
val borderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
|
||||
val borderColor = MaterialTheme.colors.subtleBorder
|
||||
|
||||
val modifier = remember {
|
||||
Modifier
|
||||
.padding(top = 2.dp, bottom = 0.dp, start = 0.dp, end = 0.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
borderColor,
|
||||
RoundedCornerShape(15.dp)
|
||||
QuoteBorder
|
||||
)
|
||||
}
|
||||
|
||||
@@ -885,7 +935,7 @@ fun HashTag(word: String, nav: (String) -> Unit) {
|
||||
data class LoadedTag(val user: User?, val note: Note?, val addedChars: String)
|
||||
|
||||
@Composable
|
||||
fun TagLink(word: String, tags: ImmutableListOfLists<String>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
fun TagLink(word: String, tags: ImmutableListOfLists<String>, canPreview: Boolean, backgroundColor: MutableState<Color>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
var loadedTag by remember { mutableStateOf<LoadedTag?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = word) {
|
||||
@@ -942,7 +992,7 @@ private fun DisplayNoteFromTag(
|
||||
addedChars: String,
|
||||
canPreview: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
if (canPreview) {
|
||||
@@ -952,11 +1002,11 @@ private fun DisplayNoteFromTag(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp, bottom = 0.dp, start = 0.dp, end = 0.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
),
|
||||
parentBackgroundColor = backgroundColor,
|
||||
isQuotedNote = true,
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
@@ -35,6 +34,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
|
||||
@Composable
|
||||
fun SensitivityWarning(
|
||||
@@ -108,7 +108,7 @@ fun ContentWarningNote(onDismiss: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onDismiss,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -68,7 +69,7 @@ fun DotsIndicator(
|
||||
totalDots: Int,
|
||||
selectedIndex: Int,
|
||||
selectedColor: Color = MaterialTheme.colors.primary /* Color.Yellow */,
|
||||
unSelectedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) /* Color.Gray */,
|
||||
unSelectedColor: Color = MaterialTheme.colors.placeholderText /* Color.Gray */,
|
||||
dotSize: Dp
|
||||
) {
|
||||
LazyRow(
|
||||
|
||||
@@ -8,56 +8,25 @@ 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.LocalContext
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun UrlPreview(url: String, urlText: String) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var urlPreviewState by remember(url) {
|
||||
val default = UrlCachedPreviewer.cache[url]?.let {
|
||||
if (it.allFetchComplete() && it.url == url) {
|
||||
UrlPreviewState.Loaded(it)
|
||||
} else {
|
||||
UrlPreviewState.Empty
|
||||
}
|
||||
} ?: UrlPreviewState.Loading
|
||||
|
||||
mutableStateOf(default)
|
||||
mutableStateOf(
|
||||
UrlCachedPreviewer.cache.get(url)?.let { it } ?: UrlPreviewState.Loading
|
||||
)
|
||||
}
|
||||
|
||||
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
|
||||
LaunchedEffect(url) {
|
||||
if (urlPreviewState == UrlPreviewState.Loading) {
|
||||
launch(Dispatchers.IO) {
|
||||
UrlCachedPreviewer.previewInfo(
|
||||
url,
|
||||
object : IUrlPreviewCallback {
|
||||
override fun onComplete(urlInfo: UrlInfoItem) {
|
||||
if (urlInfo.allFetchComplete() && urlInfo.url == url) {
|
||||
urlPreviewState = UrlPreviewState.Loaded(urlInfo)
|
||||
} else {
|
||||
urlPreviewState = UrlPreviewState.Empty
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable) {
|
||||
urlPreviewState = UrlPreviewState.Error(
|
||||
context.getString(
|
||||
R.string.error_parsing_preview_for,
|
||||
url,
|
||||
throwable.message
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
UrlCachedPreviewer.previewInfo(url) {
|
||||
urlPreviewState = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -21,6 +20,8 @@ import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
|
||||
@Composable
|
||||
fun UrlPreviewCard(
|
||||
@@ -32,11 +33,11 @@ fun UrlPreviewCard(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { runCatching { uri.openUri(url) } }
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
|
||||
@@ -5,8 +5,16 @@ import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
|
||||
|
||||
@Immutable
|
||||
sealed class UrlPreviewState {
|
||||
|
||||
@Immutable
|
||||
object Loading : UrlPreviewState()
|
||||
|
||||
@Immutable
|
||||
class Loaded(val previewInfo: UrlInfoItem) : UrlPreviewState()
|
||||
|
||||
@Immutable
|
||||
object Empty : UrlPreviewState()
|
||||
|
||||
@Immutable
|
||||
class Error(val errorMessage: String) : UrlPreviewState()
|
||||
}
|
||||
|
||||
@@ -131,14 +131,13 @@ fun VideoView(
|
||||
onDialog: ((Boolean) -> Unit)? = null
|
||||
) {
|
||||
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
|
||||
val defaultVolume = remember { if (DefaultMutedSetting.value) 0f else 1f }
|
||||
|
||||
val media = remember { MediaItem.Builder().setUri(videoUri).build() }
|
||||
|
||||
exoPlayerData.exoPlayer.apply {
|
||||
repeatMode = Player.REPEAT_MODE_ALL
|
||||
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
|
||||
volume = defaultVolume
|
||||
volume = if (DefaultMutedSetting.value) 0f else 1f
|
||||
if (videoUri.startsWith("file") == true) {
|
||||
setMediaItem(media)
|
||||
} else {
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material.Icon
|
||||
@@ -49,7 +48,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -75,7 +73,9 @@ import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveToGallery
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.theme.Nip05
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.hashVerified
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -177,11 +177,11 @@ fun ZoomableContentView(content: ZoomableContent, images: ImmutableList<Zoomable
|
||||
|
||||
var mainImageModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||
RoundedCornerShape(15.dp)
|
||||
MaterialTheme.colors.subtleBorder,
|
||||
QuoteBorder
|
||||
)
|
||||
|
||||
if (content is ZoomableUrlContent) {
|
||||
@@ -572,7 +572,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_verified),
|
||||
"Hash Verified",
|
||||
tint = Nip05.copy(0.52f).compositeOver(MaterialTheme.colors.background),
|
||||
tint = MaterialTheme.colors.hashVerified,
|
||||
modifier = Modifier.size(30.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,10 +135,17 @@ private fun RowScope.HasNewItemsIcon(
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val size = remember {
|
||||
if ("Home" == route.base) 25.dp else 23.dp
|
||||
}
|
||||
val iconSize = remember {
|
||||
if ("Home" == route.base) 24.dp else 20.dp
|
||||
}
|
||||
|
||||
BottomIcon(
|
||||
icon = route.icon,
|
||||
size = if ("Home" == route.base) 25.dp else 23.dp,
|
||||
iconSize = if ("Home" == route.base) 24.dp else 20.dp,
|
||||
size = size,
|
||||
iconSize = iconSize,
|
||||
base = route.base,
|
||||
hasNewItems = hasNewItems,
|
||||
navEntryState = navEntryState
|
||||
|
||||
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
@@ -463,7 +464,7 @@ fun SimpleTextSpinner(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
Box(
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
class AddBountyAmountViewModel : ViewModel() {
|
||||
private var account: Account? = null
|
||||
@@ -56,7 +57,10 @@ class AddBountyAmountViewModel : ViewModel() {
|
||||
replyTo = listOfNotNull(bounty),
|
||||
mentions = listOfNotNull(bounty?.author),
|
||||
tags = listOf("bounty-added-reward"),
|
||||
wantsToMarkAsSensitive = false
|
||||
wantsToMarkAsSensitive = false,
|
||||
replyingTo = null,
|
||||
root = null,
|
||||
directMentions = setOf()
|
||||
)
|
||||
|
||||
nextAmount = TextFieldValue("")
|
||||
@@ -124,7 +128,7 @@ fun AddBountyAmountDialog(bounty: Note, accountViewModel: AccountViewModel, onCl
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "10000, 50000, 5000000",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -27,6 +26,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -37,6 +38,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -54,25 +56,33 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
|
||||
if (note == null) {
|
||||
BlankNote(Modifier, isInnerNote)
|
||||
} else {
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
val newItemColor = MaterialTheme.colors.newItemBackgroundColor
|
||||
|
||||
LaunchedEffect(key1 = likeSetCard) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||
val isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt())
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundColor = if (isNew) {
|
||||
MaterialTheme.colors.newItemBackgroundColor.compositeOver(MaterialTheme.colors.background)
|
||||
} else {
|
||||
MaterialTheme.colors.background
|
||||
val newBackgroundColor = if (isNew) {
|
||||
newItemColor.compositeOver(defaultBackgroundColor)
|
||||
} else {
|
||||
defaultBackgroundColor
|
||||
}
|
||||
|
||||
if (backgroundColor.value != newBackgroundColor) {
|
||||
backgroundColor.value = newBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(backgroundColor)
|
||||
.drawBehind {
|
||||
drawRect(backgroundColor.value)
|
||||
}
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -123,7 +133,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
|
||||
|
||||
Text(
|
||||
timeAgo(note.createdAt(), context = context),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
@@ -135,7 +145,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Divider
|
||||
@@ -22,13 +21,14 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
@Composable
|
||||
fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: String? = null) {
|
||||
fun BlankNote(modifier: Modifier = Modifier, showDivider: Boolean = false, idHex: String? = null) {
|
||||
Column(modifier = modifier) {
|
||||
Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) {
|
||||
Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) {
|
||||
Row() {
|
||||
Column() {
|
||||
Row(
|
||||
modifier = Modifier.padding(
|
||||
start = 20.dp,
|
||||
@@ -46,10 +46,12 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: St
|
||||
)
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(vertical = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
if (!showDivider) {
|
||||
Divider(
|
||||
modifier = Modifier.padding(vertical = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +96,7 @@ fun HiddenNote(
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -149,7 +150,7 @@ private fun ChannelRoomCompose(
|
||||
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
) {
|
||||
@@ -224,7 +225,7 @@ private fun WatchNotificationChanges(
|
||||
@Composable
|
||||
fun LoadUser(baseUserHex: String, content: @Composable (User) -> Unit) {
|
||||
var user by remember(baseUserHex) {
|
||||
mutableStateOf<User?>(null)
|
||||
mutableStateOf<User?>(LocalCache.getUserIfExists(baseUserHex))
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = baseUserHex) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
@@ -27,7 +26,9 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -38,6 +39,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
@@ -66,21 +68,26 @@ import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeMe
|
||||
import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeThem
|
||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
||||
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ChatroomMessageCompose(
|
||||
baseNote: Note,
|
||||
routeForLastRead: String?,
|
||||
innerQuote: Boolean = false,
|
||||
parentBackgroundColor: Color? = null,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
onWantsToReply: (Note) -> Unit
|
||||
@@ -145,22 +152,30 @@ fun ChatroomMessageCompose(
|
||||
onClick = { showHiddenNote = true }
|
||||
)
|
||||
} else {
|
||||
val backgroundBubbleColor: Color
|
||||
val alignment: Arrangement.Horizontal
|
||||
val shape: Shape
|
||||
val loggedInColors = MaterialTheme.colors.mediumImportanceLink
|
||||
val otherColors = MaterialTheme.colors.subtleBorder
|
||||
val defaultBackground = MaterialTheme.colors.background
|
||||
|
||||
if (note.author == loggedIn) {
|
||||
backgroundBubbleColor = MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
||||
.compositeOver(parentBackgroundColor ?: MaterialTheme.colors.background)
|
||||
|
||||
alignment = Arrangement.End
|
||||
shape = ChatBubbleShapeMe
|
||||
} else {
|
||||
backgroundBubbleColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
|
||||
.compositeOver(parentBackgroundColor ?: MaterialTheme.colors.background)
|
||||
|
||||
alignment = Arrangement.Start
|
||||
shape = ChatBubbleShapeThem
|
||||
val backgroundBubbleColor = remember {
|
||||
if (note.author == loggedIn) {
|
||||
mutableStateOf(loggedInColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground))
|
||||
} else {
|
||||
mutableStateOf(otherColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground))
|
||||
}
|
||||
}
|
||||
val alignment: Arrangement.Horizontal = remember {
|
||||
if (note.author == loggedIn) {
|
||||
Arrangement.End
|
||||
} else {
|
||||
Arrangement.Start
|
||||
}
|
||||
}
|
||||
val shape: Shape = remember {
|
||||
if (note.author == loggedIn) {
|
||||
ChatBubbleShapeMe
|
||||
} else {
|
||||
ChatBubbleShapeThem
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -208,7 +223,7 @@ fun ChatroomMessageCompose(
|
||||
}
|
||||
) {
|
||||
Surface(
|
||||
color = backgroundBubbleColor,
|
||||
color = backgroundBubbleColor.value,
|
||||
shape = shape,
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
@@ -320,7 +335,7 @@ private fun StatusRow(
|
||||
accountViewModel: AccountViewModel,
|
||||
onWantsToReply: (Note) -> Unit
|
||||
) {
|
||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
val grayTint = MaterialTheme.colors.placeholderText
|
||||
val time = remember { baseNote.createdAt() ?: 0 }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -354,7 +369,7 @@ fun ChatTimeAgo(time: Long) {
|
||||
|
||||
Text(
|
||||
timeStr,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
@@ -363,7 +378,7 @@ fun ChatTimeAgo(time: Long) {
|
||||
private fun RenderRegularTextNote(
|
||||
note: Note,
|
||||
canPreview: Boolean,
|
||||
backgroundBubbleColor: Color,
|
||||
backgroundBubbleColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -495,10 +510,11 @@ private fun DrawAuthorInfo(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class RelayBadgesState(
|
||||
val shouldDisplayExpandButton: Boolean,
|
||||
val noteRelays: List<String>,
|
||||
val noteRelaysSimple: List<String>
|
||||
val noteRelays: ImmutableList<String>,
|
||||
val noteRelaysSimple: ImmutableList<String>
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@@ -508,8 +524,8 @@ private fun RelayBadges(baseNote: Note) {
|
||||
|
||||
val state: RelayBadgesState by remember(noteRelaysState) {
|
||||
val newShouldDisplayExpandButton = (noteRelaysState?.note?.relays?.size ?: 0) > 3
|
||||
val noteRelays = noteRelaysState?.note?.relays?.toList() ?: emptyList()
|
||||
val noteRelaysSimple = noteRelaysState?.note?.relays?.take(3)?.toList() ?: emptyList()
|
||||
val noteRelays = noteRelaysState?.note?.relays?.toImmutableList() ?: persistentListOf()
|
||||
val noteRelaysSimple = noteRelaysState?.note?.relays?.take(3)?.toImmutableList() ?: persistentListOf()
|
||||
|
||||
mutableStateOf(RelayBadgesState(newShouldDisplayExpandButton, noteRelays, noteRelaysSimple))
|
||||
}
|
||||
@@ -537,7 +553,7 @@ private fun RelayBadges(baseNote: Note) {
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -562,10 +578,13 @@ fun RenderRelay(dirtyUrl: String) {
|
||||
.clickable(onClick = { uri.openUri(website) })
|
||||
}
|
||||
|
||||
val backgroundColor = MaterialTheme.colors.background
|
||||
|
||||
val iconModifier = remember(dirtyUrl) {
|
||||
Modifier
|
||||
.size(13.dp)
|
||||
.clip(shape = CircleShape)
|
||||
.drawBehind { drawRect(backgroundColor) }
|
||||
}
|
||||
|
||||
Box(
|
||||
@@ -573,11 +592,11 @@ fun RenderRelay(dirtyUrl: String) {
|
||||
) {
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = iconUrl,
|
||||
robotSize = 13.dp,
|
||||
robotSize = remember { 13.dp },
|
||||
model = iconUrl,
|
||||
contentDescription = stringResource(id = R.string.relay_icon),
|
||||
colorFilter = RelayIconFilter,
|
||||
modifier = iconModifier.background(MaterialTheme.colors.background)
|
||||
modifier = iconModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -46,30 +48,35 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String,
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var isNew by remember { mutableStateOf(false) }
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
val newItemColor = MaterialTheme.colors.newItemBackgroundColor
|
||||
|
||||
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newIsNew =
|
||||
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||
LaunchedEffect(key1 = messageSetCard) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val isNew = messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
|
||||
|
||||
if (newIsNew != isNew) {
|
||||
isNew = newIsNew
|
||||
val newBackgroundColor = if (isNew) {
|
||||
newItemColor.compositeOver(defaultBackgroundColor)
|
||||
} else {
|
||||
defaultBackgroundColor
|
||||
}
|
||||
|
||||
if (backgroundColor.value != newBackgroundColor) {
|
||||
backgroundColor.value = newBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundColor = if (isNew) {
|
||||
MaterialTheme.colors.newItemBackgroundColor.compositeOver(MaterialTheme.colors.background)
|
||||
} else {
|
||||
MaterialTheme.colors.background
|
||||
}
|
||||
|
||||
val columnModifier = remember(isNew) {
|
||||
val columnModifier = remember(backgroundColor.value) {
|
||||
Modifier
|
||||
.background(backgroundColor)
|
||||
.drawBehind {
|
||||
drawRect(
|
||||
backgroundColor.value
|
||||
)
|
||||
}
|
||||
.padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
|
||||
@@ -22,7 +22,9 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bolt
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -33,6 +35,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -45,17 +48,19 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
||||
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -66,73 +71,55 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
||||
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
var isNew by remember { mutableStateOf(false) }
|
||||
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
val newItemColor = MaterialTheme.colors.newItemBackgroundColor
|
||||
|
||||
LaunchedEffect(key1 = multiSetCard) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newIsNew = multiSetCard.maxCreatedAt > NotificationCache.load(routeForLastRead)
|
||||
val isNew = multiSetCard.maxCreatedAt > NotificationCache.load(routeForLastRead)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, multiSetCard.maxCreatedAt)
|
||||
|
||||
if (newIsNew != isNew) {
|
||||
isNew = newIsNew
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val primaryColor = MaterialTheme.colors.newItemBackgroundColor
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
|
||||
val backgroundColor by remember(isNew) {
|
||||
derivedStateOf {
|
||||
if (isNew) {
|
||||
primaryColor.compositeOver(defaultBackgroundColor)
|
||||
val newBackgroundColor = if (isNew) {
|
||||
newItemColor.compositeOver(defaultBackgroundColor)
|
||||
} else {
|
||||
defaultBackgroundColor
|
||||
}
|
||||
|
||||
if (backgroundColor.value != newBackgroundColor) {
|
||||
backgroundColor.value = newBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val columnModifier = Modifier
|
||||
.background(backgroundColor)
|
||||
.padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 10.dp
|
||||
)
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||
}
|
||||
},
|
||||
onLongClick = { popupExpanded = true }
|
||||
)
|
||||
.fillMaxWidth()
|
||||
|
||||
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
|
||||
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
|
||||
val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } }
|
||||
|
||||
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
|
||||
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
|
||||
val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } }
|
||||
val columnModifier = remember(backgroundColor.value) {
|
||||
Modifier
|
||||
.drawBehind {
|
||||
drawRect(backgroundColor.value)
|
||||
}
|
||||
.padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 10.dp
|
||||
)
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||
}
|
||||
},
|
||||
onLongClick = { popupExpanded = true }
|
||||
)
|
||||
.fillMaxWidth()
|
||||
}
|
||||
|
||||
Column(modifier = columnModifier) {
|
||||
if (hasZapEvents) {
|
||||
RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
Galeries(multiSetCard, backgroundColor, nav, accountViewModel)
|
||||
|
||||
if (hasBoostEvents) {
|
||||
RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
|
||||
if (hasLikeEvents) {
|
||||
RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.width(65.dp))
|
||||
Row(remember { Modifier.fillMaxWidth() }) {
|
||||
Spacer(modifier = remember { Modifier.width(65.dp) })
|
||||
|
||||
NoteCompose(
|
||||
baseNote = baseNote,
|
||||
@@ -150,44 +137,92 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderLikeGallery(
|
||||
likeEvents: ImmutableList<Note>,
|
||||
backgroundColor: Color,
|
||||
private fun Galeries(
|
||||
multiSetCard: MultiSetCard,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Box(
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.width(55.dp)
|
||||
.padding(end = 5.dp)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
|
||||
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
|
||||
val likeEvents by remember { derivedStateOf { multiSetCard.likeEventsByType } }
|
||||
|
||||
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
|
||||
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
|
||||
val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } }
|
||||
|
||||
if (hasZapEvents) {
|
||||
RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
|
||||
if (hasBoostEvents) {
|
||||
RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
|
||||
if (hasLikeEvents) {
|
||||
likeEvents.forEach {
|
||||
RenderLikeGallery(it.key, it.value, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderLikeGallery(
|
||||
reactionType: String,
|
||||
likeEvents: ImmutableList<Note>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val isNotEmpty = remember(likeEvents) {
|
||||
likeEvents.isNotEmpty()
|
||||
}
|
||||
|
||||
val shortReaction by remember {
|
||||
derivedStateOf {
|
||||
reactionType.take(2)
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotEmpty) {
|
||||
Row(remember { Modifier.fillMaxWidth() }) {
|
||||
Box(
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.size(16.dp)
|
||||
.width(55.dp)
|
||||
.padding(end = 5.dp)
|
||||
}
|
||||
) {
|
||||
val modifier = remember {
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
},
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
||||
when (shortReaction) {
|
||||
"+" -> Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
modifier = remember { modifier.size(18.dp) },
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
"-" -> Text(text = "\uD83D\uDC4E", modifier = modifier)
|
||||
else -> Text(text = shortReaction, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderZapGallery(
|
||||
zapEvents: ImmutableMap<Note, Note?>,
|
||||
backgroundColor: Color,
|
||||
zapEvents: ImmutableList<CombinedZap>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Row(remember { Modifier.fillMaxWidth() }) {
|
||||
Box(
|
||||
modifier = remember {
|
||||
Modifier
|
||||
@@ -214,7 +249,7 @@ fun RenderZapGallery(
|
||||
@Composable
|
||||
fun RenderBoostGallery(
|
||||
boostEvents: ImmutableList<Note>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
@@ -249,29 +284,44 @@ fun RenderBoostGallery(
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun AuthorGalleryZaps(
|
||||
authorNotes: ImmutableMap<Note, Note?>,
|
||||
backgroundColor: Color,
|
||||
authorNotes: ImmutableList<CombinedZap>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
|
||||
FlowRow() {
|
||||
authorNotes.forEach {
|
||||
AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel)
|
||||
AuthorPictureAndComment(it.request, it.response, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class ZapAmountCommentNotification(
|
||||
val user: User?,
|
||||
val comment: String?,
|
||||
val amount: String?
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun AuthorPictureAndComment(
|
||||
zapRequest: Note,
|
||||
zapEvent: Note?,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
var content by remember { mutableStateOf<Triple<User?, String?, String?>>(Triple(zapRequest.author, null, null)) }
|
||||
var content by remember {
|
||||
mutableStateOf(
|
||||
ZapAmountCommentNotification(
|
||||
user = zapRequest.author,
|
||||
comment = null,
|
||||
amount = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) {
|
||||
launch(Dispatchers.Default) {
|
||||
@@ -280,27 +330,27 @@ private fun AuthorPictureAndComment(
|
||||
val amount = (zapEvent?.event as? LnZapEvent)?.amount
|
||||
if (decryptedContent != null) {
|
||||
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||
content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount))
|
||||
content = ZapAmountCommentNotification(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount))
|
||||
} else {
|
||||
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
|
||||
content = Triple(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount))
|
||||
content = ZapAmountCommentNotification(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content.first?.let {
|
||||
val route by remember {
|
||||
derivedStateOf {
|
||||
"User/${it.pubkeyHex}"
|
||||
}
|
||||
val route by remember {
|
||||
derivedStateOf {
|
||||
"User/${content.user?.pubkeyHex}"
|
||||
}
|
||||
}
|
||||
|
||||
content.user?.let { user ->
|
||||
AuthorPictureAndComment(
|
||||
author = it,
|
||||
comment = content.second,
|
||||
amount = content.third,
|
||||
author = user,
|
||||
comment = content.comment,
|
||||
amount = content.amount,
|
||||
route = route,
|
||||
backgroundColor = backgroundColor,
|
||||
nav = nav,
|
||||
@@ -309,18 +359,32 @@ private fun AuthorPictureAndComment(
|
||||
}
|
||||
}
|
||||
|
||||
val amountBoxModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(shape = CircleShape)
|
||||
|
||||
val textBoxModifier = Modifier.padding(start = 5.dp).fillMaxWidth()
|
||||
|
||||
val simpleModifier = Modifier
|
||||
|
||||
val size = 35.dp
|
||||
|
||||
val sizedModifier = Modifier.size(size)
|
||||
|
||||
val bottomPadding1dp = Modifier.padding(bottom = 1.dp)
|
||||
|
||||
val commentTextSize = 12.sp
|
||||
|
||||
@Composable
|
||||
private fun AuthorPictureAndComment(
|
||||
author: User,
|
||||
comment: String?,
|
||||
amount: String?,
|
||||
route: String,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val authorPictureModifier = remember { Modifier }
|
||||
|
||||
val modifier = remember {
|
||||
Modifier.clickable {
|
||||
nav(route)
|
||||
@@ -331,28 +395,34 @@ private fun AuthorPictureAndComment(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(modifier = remember { Modifier.size(35.dp) }, contentAlignment = Alignment.BottomCenter) {
|
||||
Box(modifier = sizedModifier, contentAlignment = Alignment.BottomCenter) {
|
||||
FastNoteAuthorPicture(
|
||||
author = author,
|
||||
size = remember { 35.dp },
|
||||
size = size,
|
||||
accountViewModel = accountViewModel,
|
||||
pictureModifier = authorPictureModifier
|
||||
pictureModifier = simpleModifier
|
||||
)
|
||||
|
||||
amount?.let {
|
||||
Box(modifier = Modifier.fillMaxSize().clip(shape = CircleShape), contentAlignment = Alignment.BottomCenter) {
|
||||
Box(
|
||||
modifier = amountBoxModifier,
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
val backgroundColor = MaterialTheme.colors.overPictureBackground
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.background.copy(0.62f)),
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.drawBehind { drawRect(backgroundColor) }
|
||||
},
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.secondaryVariant,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(bottom = 1.dp)
|
||||
fontSize = commentTextSize,
|
||||
modifier = bottomPadding1dp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -360,12 +430,11 @@ private fun AuthorPictureAndComment(
|
||||
}
|
||||
|
||||
comment?.let {
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = true,
|
||||
tags = remember { ImmutableListOfLists() },
|
||||
modifier = remember { Modifier.fillMaxWidth() },
|
||||
modifier = textBoxModifier,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
@@ -378,25 +447,35 @@ private fun AuthorPictureAndComment(
|
||||
@Composable
|
||||
fun AuthorGallery(
|
||||
authorNotes: ImmutableList<Note>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
|
||||
FlowRow() {
|
||||
authorNotes.forEach { note ->
|
||||
Box(remember { Modifier.size(35.dp) }) {
|
||||
NotePictureAndComment(note, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
BoxedAuthor(note, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxedAuthor(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
Box(sizedModifier) {
|
||||
NotePictureAndComment(note, backgroundColor, nav, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotePictureAndComment(
|
||||
baseNote: Note,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
@@ -422,17 +501,20 @@ fun FastNoteAuthorPicture(
|
||||
pictureModifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val userState by author.live().metadata.observeAsState()
|
||||
val profilePicture by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.profilePicture()
|
||||
}
|
||||
var profilePicture by remember {
|
||||
mutableStateOf(author.info?.picture)
|
||||
}
|
||||
|
||||
val authorPubKey = remember {
|
||||
author.pubkeyHex
|
||||
}
|
||||
|
||||
WatchUserMetadata(author) {
|
||||
if (it.picture != profilePicture) {
|
||||
profilePicture = it.picture
|
||||
}
|
||||
}
|
||||
|
||||
UserPicture(
|
||||
userHex = authorPubKey,
|
||||
userPicture = profilePicture,
|
||||
@@ -441,3 +523,15 @@ fun FastNoteAuthorPicture(
|
||||
accountViewModel = accountViewModel
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchUserMetadata(userBase: User, onMetadataChanges: (UserMetadata) -> Unit) {
|
||||
val userState by userBase.live().metadata.observeAsState()
|
||||
LaunchedEffect(key1 = userState) {
|
||||
launch(Dispatchers.Default) {
|
||||
userState?.user?.info?.let {
|
||||
onMetadataChanges(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.compose.material.icons.filled.Downloading
|
||||
import androidx.compose.material.icons.filled.Report
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -34,12 +34,13 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Nip05
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State<Boolean?> {
|
||||
fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): MutableState<Boolean?> {
|
||||
val nip05Verified = remember(user.nip05) {
|
||||
// starts with null if must verify or already filled in if verified in the last hour
|
||||
val default = if ((user.nip05LastVerificationTime ?: 0) > (Date().time / 1000 - 60 * 60)) { // 1hour
|
||||
@@ -131,7 +132,7 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
||||
private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modifier = Modifier) {
|
||||
Column(modifier = columnModifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val nip05Verified by nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex)
|
||||
val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex)
|
||||
DisplayNIP05(nip05, nip05Verified)
|
||||
}
|
||||
}
|
||||
@@ -140,7 +141,7 @@ private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modi
|
||||
@Composable
|
||||
private fun DisplayNIP05(
|
||||
nip05: String,
|
||||
nip05Verified: Boolean?
|
||||
nip05Verified: MutableState<Boolean?>
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
val (user, domain) = remember(nip05) {
|
||||
@@ -150,13 +151,26 @@ private fun DisplayNIP05(
|
||||
if (user != "_") {
|
||||
Text(
|
||||
text = AnnotatedString(user),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
if (nip05Verified == null) {
|
||||
NIP05VerifiedSymbol(nip05Verified)
|
||||
|
||||
ClickableText(
|
||||
text = AnnotatedString(domain),
|
||||
onClick = { runCatching { uri.openUri("https://$domain") } },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Visible
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NIP05VerifiedSymbol(nip05Verified: MutableState<Boolean?>) {
|
||||
if (nip05Verified.value == null) {
|
||||
Icon(
|
||||
tint = Color.Yellow,
|
||||
imageVector = Icons.Default.Downloading,
|
||||
@@ -165,7 +179,7 @@ private fun DisplayNIP05(
|
||||
.size(14.dp)
|
||||
.padding(top = 1.dp)
|
||||
)
|
||||
} else if (nip05Verified == true) {
|
||||
} else if (nip05Verified.value == true) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_verified),
|
||||
"NIP-05 Verified",
|
||||
@@ -184,14 +198,6 @@ private fun DisplayNIP05(
|
||||
.padding(top = 1.dp)
|
||||
)
|
||||
}
|
||||
|
||||
ClickableText(
|
||||
text = AnnotatedString(domain),
|
||||
onClick = { runCatching { uri.openUri("https://$domain") } },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Visible
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,7 +45,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
@@ -69,6 +68,7 @@ import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.WarningColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -110,6 +110,16 @@ private fun VerticalDivider(color: Color) =
|
||||
.width(1.dp)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LongPressToQuickAction(baseNote: Note, accountViewModel: AccountViewModel, content: @Composable (() -> Unit) -> Unit) {
|
||||
var popupExpanded = remember { mutableStateOf(false) }
|
||||
val showPopup = remember(popupExpanded) { { popupExpanded.value = true } }
|
||||
|
||||
content(showPopup)
|
||||
|
||||
NoteQuickActionMenu(baseNote, popupExpanded.value, { popupExpanded.value = false }, accountViewModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
||||
var showSelectTextDialog by remember(note) { mutableStateOf(false) }
|
||||
@@ -127,7 +137,7 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
val backgroundColor = if (MaterialTheme.colors.isLight) {
|
||||
MaterialTheme.colors.primary
|
||||
} else {
|
||||
MaterialTheme.colors.primary.copy(alpha = 0.32f).compositeOver(MaterialTheme.colors.background)
|
||||
MaterialTheme.colors.secondaryButtonBackground
|
||||
}
|
||||
|
||||
val showToast = { stringResource: Int ->
|
||||
@@ -143,7 +153,7 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
val isOwnNote = accountViewModel.isLoggedUser(note.author)
|
||||
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
|
||||
|
||||
Popup(onDismissRequest = onDismiss) {
|
||||
Popup(onDismissRequest = onDismiss, alignment = Alignment.Center) {
|
||||
Card(
|
||||
modifier = Modifier.shadow(elevation = 6.dp, shape = cardShape),
|
||||
shape = cardShape,
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.note
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bolt
|
||||
@@ -32,6 +31,10 @@ import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
@@ -41,7 +44,7 @@ import kotlin.math.roundToInt
|
||||
fun PollNote(
|
||||
baseNote: Note,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -66,7 +69,7 @@ fun PollNote(
|
||||
baseNote: Note,
|
||||
pollViewModel: PollNoteViewModel,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -108,7 +111,7 @@ private fun OptionNote(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val tags = remember(baseNote) {
|
||||
@@ -123,7 +126,7 @@ private fun OptionNote(
|
||||
val color = if (poolOption.consensusThreadhold) {
|
||||
Color.Green.copy(alpha = 0.32f)
|
||||
} else {
|
||||
MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
||||
MaterialTheme.colors.mediumImportanceLink
|
||||
}
|
||||
|
||||
ZapVote(
|
||||
@@ -168,7 +171,7 @@ private fun RenderOptionAfterVote(
|
||||
color: Color,
|
||||
canPreview: Boolean,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
@@ -179,11 +182,11 @@ private fun RenderOptionAfterVote(
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
2.dp,
|
||||
color,
|
||||
RoundedCornerShape(15.dp)
|
||||
QuoteBorder
|
||||
)
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
@@ -235,18 +238,18 @@ private fun RenderOptionBeforeVote(
|
||||
description: String,
|
||||
canPreview: Boolean,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.clip(shape = RoundedCornerShape(15.dp))
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
2.dp,
|
||||
MaterialTheme.colors.primary,
|
||||
RoundedCornerShape(15.dp)
|
||||
QuoteBorder
|
||||
)
|
||||
) {
|
||||
TranslatableRichTextViewer(
|
||||
@@ -410,7 +413,7 @@ fun ZapVote(
|
||||
imageVector = Icons.Outlined.Bolt,
|
||||
contentDescription = stringResource(id = R.string.zaps),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.width(3.dp))
|
||||
@@ -428,7 +431,7 @@ fun ZapVote(
|
||||
Text(
|
||||
showAmount(poolOption.zappedValue),
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -490,7 +493,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -55,6 +54,7 @@ import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Popup
|
||||
@@ -65,8 +65,11 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -77,17 +80,17 @@ import java.math.RoundingMode
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun ReactionsRow(baseNote: Note, showReactions: Boolean, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
fun ReactionsRow(baseNote: Note, showReactionDetail: Boolean, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val grayTint = MaterialTheme.colors.placeholderText
|
||||
|
||||
var wantsToSeeReactions = remember {
|
||||
val wantsToSeeReactions = remember {
|
||||
mutableStateOf<Boolean>(false)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(7.dp))
|
||||
|
||||
Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp)) {
|
||||
if (showReactions) {
|
||||
if (showReactionDetail) {
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier
|
||||
@@ -116,9 +119,11 @@ fun ReactionsRow(baseNote: Note, showReactions: Boolean, accountViewModel: Accou
|
||||
}
|
||||
}
|
||||
|
||||
if (showReactions && wantsToSeeReactions.value) {
|
||||
if (showReactionDetail && wantsToSeeReactions.value) {
|
||||
ReactionDetailGallery(baseNote, nav, accountViewModel)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(7.dp))
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -171,6 +176,9 @@ private fun ReactionDetailGallery(
|
||||
val boostsState by baseNote.live().boosts.observeAsState()
|
||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
|
||||
val hasReactions by remember(zapsState, boostsState, reactionsState) {
|
||||
derivedStateOf {
|
||||
baseNote.zaps.isNotEmpty() ||
|
||||
@@ -182,9 +190,9 @@ private fun ReactionDetailGallery(
|
||||
if (hasReactions) {
|
||||
Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp)) {
|
||||
Column() {
|
||||
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.toImmutableMap() } }
|
||||
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.mapNotNull { it.value?.let { zapEvent -> CombinedZap(it.key, zapEvent) } }.toImmutableList() } }
|
||||
val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } }
|
||||
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableList() } }
|
||||
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableMap() } }
|
||||
|
||||
val hasZapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.isNotEmpty() } }
|
||||
val hasBoostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.isNotEmpty() } }
|
||||
@@ -193,7 +201,7 @@ private fun ReactionDetailGallery(
|
||||
if (hasZapEvents) {
|
||||
RenderZapGallery(
|
||||
zapEvents,
|
||||
MaterialTheme.colors.background,
|
||||
backgroundColor,
|
||||
nav,
|
||||
accountViewModel
|
||||
)
|
||||
@@ -202,19 +210,23 @@ private fun ReactionDetailGallery(
|
||||
if (hasBoostEvents) {
|
||||
RenderBoostGallery(
|
||||
boostEvents,
|
||||
MaterialTheme.colors.background,
|
||||
backgroundColor,
|
||||
nav,
|
||||
accountViewModel
|
||||
)
|
||||
}
|
||||
|
||||
if (hasLikeEvents) {
|
||||
RenderLikeGallery(
|
||||
likeEvents,
|
||||
MaterialTheme.colors.background,
|
||||
nav,
|
||||
accountViewModel
|
||||
)
|
||||
likeEvents.forEach {
|
||||
val reactions = remember(it.value) { it.value.toImmutableList() }
|
||||
RenderLikeGallery(
|
||||
it.key,
|
||||
reactions,
|
||||
backgroundColor,
|
||||
nav,
|
||||
accountViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +279,7 @@ fun ReplyReaction(
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
showCounter: Boolean = true,
|
||||
iconSize: Dp = 20.dp,
|
||||
iconSize: Dp = 18.dp,
|
||||
onPress: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -423,13 +435,15 @@ fun BoostText(baseNote: Note, grayTint: Color) {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LikeReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
iconSize: Dp = 20.dp,
|
||||
heartSize: Dp = 16.dp
|
||||
heartSize: Dp = 16.dp,
|
||||
iconFontSize: TextUnit = 14.sp
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -438,47 +452,75 @@ fun LikeReaction(
|
||||
Modifier.size(iconSize)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = iconButtonModifier,
|
||||
onClick = {
|
||||
if (accountViewModel.isWriteable()) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (accountViewModel.hasReactedTo(baseNote)) {
|
||||
accountViewModel.deleteReactionTo(baseNote)
|
||||
} else {
|
||||
accountViewModel.reactTo(baseNote)
|
||||
var wantsToChangeReactionSymbol by remember { mutableStateOf(false) }
|
||||
var wantsToReact by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = iconButtonModifier.combinedClickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||
onClick = {
|
||||
likeClick(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
scope,
|
||||
context,
|
||||
onMultipleChoices = {
|
||||
wantsToReact = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.login_with_a_private_key_to_like_posts),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
},
|
||||
onLongClick = {
|
||||
wantsToChangeReactionSymbol = true
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
LikeIcon(baseNote, heartSize, grayTint, accountViewModel.userProfile())
|
||||
LikeIcon(baseNote, iconFontSize, heartSize, grayTint, accountViewModel.userProfile())
|
||||
|
||||
if (wantsToChangeReactionSymbol) {
|
||||
UpdateReactionTypeDialog({ wantsToChangeReactionSymbol = false }, accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
if (wantsToReact) {
|
||||
ReactionChoicePopup(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
onDismiss = {
|
||||
wantsToReact = false
|
||||
},
|
||||
onChangeAmount = {
|
||||
wantsToReact = false
|
||||
wantsToChangeReactionSymbol = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LikeText(baseNote, grayTint)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LikeIcon(baseNote: Note, iconSize: Dp = 20.dp, grayTint: Color, loggedIn: User) {
|
||||
fun LikeIcon(
|
||||
baseNote: Note,
|
||||
iconFontSize: TextUnit = 14.sp,
|
||||
iconSize: Dp = 20.dp,
|
||||
grayTint: Color,
|
||||
loggedIn: User
|
||||
) {
|
||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||
|
||||
var wasReactedByLoggedIn by remember(reactionsState) {
|
||||
mutableStateOf(false)
|
||||
var reactionType by remember(baseNote) {
|
||||
mutableStateOf<String?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = reactionsState) {
|
||||
launch(Dispatchers.Default) {
|
||||
val newWasReactedByLoggedIn = reactionsState?.note?.isReactedBy(loggedIn) == true
|
||||
if (wasReactedByLoggedIn != newWasReactedByLoggedIn) {
|
||||
wasReactedByLoggedIn = newWasReactedByLoggedIn
|
||||
val newReactionType = reactionsState?.note?.isReactedBy(loggedIn)
|
||||
if (reactionType != newReactionType) {
|
||||
reactionType = newReactionType?.take(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,13 +529,19 @@ fun LikeIcon(baseNote: Note, iconSize: Dp = 20.dp, grayTint: Color, loggedIn: Us
|
||||
Modifier.size(iconSize)
|
||||
}
|
||||
|
||||
if (wasReactedByLoggedIn) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
modifier = iconModifier,
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
if (reactionType != null) {
|
||||
when (reactionType) {
|
||||
"+" -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
modifier = iconModifier,
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
}
|
||||
"-" -> Text(text = "\uD83D\uDC4E", fontSize = iconFontSize)
|
||||
else -> Text(text = reactionType!!, fontSize = iconFontSize)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_like),
|
||||
@@ -514,7 +562,7 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
||||
|
||||
LaunchedEffect(key1 = reactionsState) {
|
||||
launch(Dispatchers.Default) {
|
||||
val newReactionsCount = " " + showCount(reactionsState?.note?.reactions?.size)
|
||||
val newReactionsCount = " " + showCount(reactionsState?.note?.countReactions())
|
||||
if (reactionsCount != newReactionsCount) {
|
||||
reactionsCount = newReactionsCount
|
||||
}
|
||||
@@ -528,6 +576,47 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun likeClick(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
scope: CoroutineScope,
|
||||
context: Context,
|
||||
onMultipleChoices: () -> Unit
|
||||
) {
|
||||
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.no_reaction_type_setup_long_press_to_change),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
} else if (!accountViewModel.isWriteable()) {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
} else if (accountViewModel.account.reactionChoices.size == 1) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val reaction = accountViewModel.account.reactionChoices.first()
|
||||
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
|
||||
accountViewModel.deleteReactionTo(baseNote, reaction)
|
||||
} else {
|
||||
accountViewModel.reactTo(baseNote, reaction)
|
||||
}
|
||||
}
|
||||
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
||||
onMultipleChoices()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun ZapReaction(
|
||||
@@ -833,7 +922,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -845,7 +934,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onQuote,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -857,6 +946,95 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ReactionChoicePopup(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit
|
||||
) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val toRemove = remember {
|
||||
baseNote.reactedBy(account.userProfile()).toSet()
|
||||
}
|
||||
|
||||
Popup(
|
||||
alignment = Alignment.BottomCenter,
|
||||
offset = IntOffset(0, -50),
|
||||
onDismissRequest = { onDismiss() }
|
||||
) {
|
||||
FlowRow(horizontalArrangement = Arrangement.Center) {
|
||||
account.reactionChoices.forEach { reactionType ->
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
accountViewModel.reactToOrDelete(
|
||||
baseNote,
|
||||
reactionType
|
||||
)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
val thisModifier = remember(reactionType) {
|
||||
Modifier.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
accountViewModel.reactToOrDelete(
|
||||
baseNote,
|
||||
reactionType
|
||||
)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
onChangeAmount()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val removeSymbol = remember(reactionType) {
|
||||
if (reactionType in toRemove) {
|
||||
" ✖"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
when (reactionType) {
|
||||
"+" -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
modifier = remember { thisModifier.size(16.dp) },
|
||||
tint = Color.White
|
||||
)
|
||||
Text(text = removeSymbol, color = Color.White, textAlign = TextAlign.Center, modifier = thisModifier)
|
||||
}
|
||||
"-" -> Text(text = "\uD83D\uDC4E$removeSymbol", color = Color.White, textAlign = TextAlign.Center, modifier = thisModifier)
|
||||
else -> Text(
|
||||
"$reactionType$removeSymbol",
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = thisModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ZapAmountChoicePopup(
|
||||
@@ -898,7 +1076,7 @@ fun ZapAmountChoicePopup(
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Divider
|
||||
@@ -25,6 +24,8 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
@@ -67,7 +68,7 @@ fun RelayCompose(
|
||||
|
||||
Text(
|
||||
"${relay.counter} ${stringResource(R.string.posts_received)}",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -94,7 +95,7 @@ fun AddRelayButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -110,7 +111,7 @@ fun RemoveRelayButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.vitorpamplona.amethyst.model.*
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -69,7 +71,7 @@ private fun ReplyInformation(
|
||||
Text(
|
||||
stringResource(R.string.replying_to),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
repliesToDisplay.forEachIndexed { idx, user ->
|
||||
@@ -80,13 +82,13 @@ private fun ReplyInformation(
|
||||
Text(
|
||||
", ",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
} else if (idx < repliesToDisplay.size - 1) {
|
||||
Text(
|
||||
stringResource(R.string.and),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -94,25 +96,25 @@ private fun ReplyInformation(
|
||||
Text(
|
||||
", ",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
} else if (idx < repliesToDisplay.size) {
|
||||
Text(
|
||||
stringResource(R.string.and),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
ClickableText(
|
||||
AnnotatedString("${sortedMentions.size - 2}"),
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.lessImportantLink, fontSize = 13.sp),
|
||||
onClick = { expanded = true }
|
||||
)
|
||||
|
||||
Text(
|
||||
" ${stringResource(R.string.others)}",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -195,12 +197,12 @@ fun ReplyInformationChannel(
|
||||
Text(
|
||||
stringResource(R.string.in_channel),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
ClickableText(
|
||||
text = channelName,
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.lessImportantLink, fontSize = 13.sp),
|
||||
onClick = { onChannelTagClick(channel) }
|
||||
)
|
||||
|
||||
@@ -209,7 +211,7 @@ fun ReplyInformationChannel(
|
||||
Text(
|
||||
stringResource(id = R.string.replying_to),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
mentions.forEachIndexed { idx, user ->
|
||||
@@ -219,13 +221,13 @@ fun ReplyInformationChannel(
|
||||
Text(
|
||||
", ",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
} else if (idx < mentions.size - 1) {
|
||||
Text(
|
||||
" ${stringResource(id = R.string.add)} ",
|
||||
" ${stringResource(id = R.string.and)} ",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -246,7 +248,7 @@ private fun ReplyInfoMention(
|
||||
clickablePart = remember(innerUserState) { "$prefix${innerUserState?.user?.toBestDisplayName()}" },
|
||||
tags = remember(innerUserState) { innerUserState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() },
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colors.primary.copy(alpha = 0.52f),
|
||||
color = MaterialTheme.colors.lessImportantLink,
|
||||
fontSize = 13.sp
|
||||
),
|
||||
onClick = { onUserTagClick(user) }
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
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.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
class UpdateReactionTypeViewModel(val account: Account) : ViewModel() {
|
||||
var nextChoice by mutableStateOf(TextFieldValue(""))
|
||||
var reactionSet by mutableStateOf(listOf<String>())
|
||||
|
||||
fun load() {
|
||||
this.reactionSet = account.reactionChoices
|
||||
}
|
||||
|
||||
fun toListOfChoices(commaSeparatedAmounts: String): List<Long> {
|
||||
return commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 }
|
||||
}
|
||||
|
||||
fun addChoice() {
|
||||
val newValue = nextChoice.text.trim().take(2)
|
||||
reactionSet = reactionSet + newValue
|
||||
|
||||
nextChoice = TextFieldValue("")
|
||||
}
|
||||
|
||||
fun removeChoice(reaction: String) {
|
||||
reactionSet = reactionSet - reaction
|
||||
}
|
||||
|
||||
fun sendPost() {
|
||||
account.changeReactionTypes(reactionSet)
|
||||
nextChoice = TextFieldValue("")
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
nextChoice = TextFieldValue("")
|
||||
}
|
||||
|
||||
fun hasChanged(): Boolean {
|
||||
return reactionSet != account.reactionChoices
|
||||
}
|
||||
|
||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||
override fun <UpdateReactionTypeViewModel : ViewModel> create(modelClass: Class<UpdateReactionTypeViewModel>): UpdateReactionTypeViewModel {
|
||||
return UpdateReactionTypeViewModel(account) as UpdateReactionTypeViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun UpdateReactionTypeDialog(onClose: () -> Unit, nip47uri: String? = null, accountViewModel: AccountViewModel) {
|
||||
val postViewModel: UpdateReactionTypeViewModel = viewModel(
|
||||
key = accountViewModel.userProfile().pubkeyHex,
|
||||
factory = UpdateReactionTypeViewModel.Factory(accountViewModel.account)
|
||||
)
|
||||
|
||||
LaunchedEffect(accountViewModel) {
|
||||
postViewModel.load()
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { onClose() },
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
dismissOnClickOutside = false,
|
||||
decorFitsSystemWindows = false
|
||||
)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column(modifier = Modifier.padding(10.dp).imePadding()) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CloseButton(onCancel = {
|
||||
postViewModel.cancel()
|
||||
onClose()
|
||||
})
|
||||
|
||||
SaveButton(
|
||||
onPost = {
|
||||
postViewModel.sendPost()
|
||||
onClose()
|
||||
},
|
||||
isActive = postViewModel.hasChanged()
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
postViewModel.reactionSet.forEach { reactionType ->
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
onClick = {
|
||||
postViewModel.removeChoice(reactionType)
|
||||
}
|
||||
) {
|
||||
when (reactionType) {
|
||||
"+" -> {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_liked),
|
||||
null,
|
||||
modifier = remember { Modifier.size(16.dp) },
|
||||
tint = Color.White
|
||||
)
|
||||
Text(text = " ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||
}
|
||||
"-" -> Text(text = "\uD83D\uDC4E ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||
else -> Text(text = "$reactionType ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.new_reaction_symbol)) },
|
||||
value = postViewModel.nextChoice,
|
||||
onValueChange = {
|
||||
postViewModel.nextChoice = it
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
keyboardType = KeyboardType.Text
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "\uD83D\uDCAF, \uD83C\uDF89, \uD83D\uDC4E",
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
.padding(end = 10.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = { postViewModel.addChoice() },
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
Text(text = stringResource(R.string.add), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
@@ -78,6 +77,8 @@ import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.getFragmentActivity
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -280,7 +281,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
postViewModel.amountSet.forEach { amountInSats ->
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
@@ -324,7 +325,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "100, 1000, 5000",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -335,7 +336,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
|
||||
Button(
|
||||
onClick = { postViewModel.addAmount() },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
@@ -413,7 +414,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
Text(
|
||||
stringResource(id = R.string.wallet_connect_service_explainer),
|
||||
Modifier.weight(1f),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
@@ -452,7 +453,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "npub, hex",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -474,7 +475,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "wss://relay.server.com",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
@@ -518,7 +519,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, nip47uri: String? = null, account
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.wallet_connect_service_secret_placeholder),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.ui.components.BundledInsert
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.RoyalBlue
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -80,7 +81,7 @@ fun UserReactionsRow(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
||||
@@ -53,7 +54,7 @@ private fun UserNameDisplay(
|
||||
CreateTextWithEmoji(
|
||||
text = remember { "@$bestUserName" },
|
||||
tags = tags,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
@@ -26,6 +25,8 @@ import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -148,7 +149,7 @@ fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, bas
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "100, 1000, 5000",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -199,7 +200,7 @@ fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, bas
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.custom_zaps_add_a_message_example),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
@@ -217,7 +218,7 @@ fun ZapCustomDialog(onClose: () -> Unit, accountViewModel: AccountViewModel, bas
|
||||
fun ZapButton(isActive: Boolean, onPost: () -> Unit) {
|
||||
Button(
|
||||
onClick = { onPost() },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -225,7 +226,7 @@ fun AboutDisplay(baseAuthor: User) {
|
||||
|
||||
Text(
|
||||
userAboutMe,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -35,29 +37,33 @@ import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
val newItemColor = MaterialTheme.colors.newItemBackgroundColor
|
||||
|
||||
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newIsNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||
val isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt)
|
||||
|
||||
if (newIsNew != isNew) {
|
||||
isNew = newIsNew
|
||||
val newBackgroundColor = if (isNew) {
|
||||
newItemColor.compositeOver(defaultBackgroundColor)
|
||||
} else {
|
||||
defaultBackgroundColor
|
||||
}
|
||||
|
||||
if (backgroundColor.value != newBackgroundColor) {
|
||||
backgroundColor.value = newBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
MaterialTheme.colors.newItemBackgroundColor.compositeOver(MaterialTheme.colors.background)
|
||||
} else {
|
||||
MaterialTheme.colors.background
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(backgroundColor)
|
||||
.drawBehind {
|
||||
drawRect(backgroundColor.value)
|
||||
}
|
||||
.clickable {
|
||||
nav("User/${zapSetCard.user.pubkeyHex}")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
|
||||
@Immutable
|
||||
abstract class Card() {
|
||||
@@ -33,8 +34,8 @@ class NoteCard(val note: Note) : Card() {
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap<Note, Note>) : Card() {
|
||||
val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 }
|
||||
class ZapUserSetCard(val user: User, val zapEvents: ImmutableList<CombinedZap>) : Card() {
|
||||
val createdAt = zapEvents.maxOf { it.createdAt() ?: 0 }
|
||||
override fun createdAt(): Long {
|
||||
return createdAt
|
||||
}
|
||||
@@ -42,19 +43,28 @@ class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap<Note, Note>) :
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val likeEvents: ImmutableList<Note>, val zapEvents: ImmutableMap<Note, Note>) : Card() {
|
||||
class MultiSetCard(
|
||||
val note: Note,
|
||||
val boostEvents: ImmutableList<Note>,
|
||||
val likeEvents: ImmutableList<Note>,
|
||||
val zapEvents: ImmutableList<CombinedZap>
|
||||
) : Card() {
|
||||
val maxCreatedAt = maxOf(
|
||||
zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0,
|
||||
zapEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||
boostEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0
|
||||
)
|
||||
|
||||
val minCreatedAt = minOf(
|
||||
zapEvents.minOfOrNull { it.value.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE,
|
||||
zapEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE,
|
||||
likeEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE,
|
||||
boostEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE
|
||||
)
|
||||
|
||||
val likeEventsByType = likeEvents.groupBy { it.event?.content() ?: "+" }.mapValues {
|
||||
it.value.toImmutableList()
|
||||
}.toImmutableMap()
|
||||
|
||||
override fun createdAt(): Long {
|
||||
return maxCreatedAt
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -152,6 +153,7 @@ fun RenderCardFeed(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun FeedLoaded(
|
||||
state: CardFeedState.Loaded,
|
||||
@@ -160,12 +162,6 @@ private fun FeedLoaded(
|
||||
nav: (String) -> Unit,
|
||||
routeForLastRead: String
|
||||
) {
|
||||
val defaultModifier = remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 100.dp)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = remember { Modifier.fillMaxSize() },
|
||||
contentPadding = remember {
|
||||
@@ -177,6 +173,11 @@ private fun FeedLoaded(
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
|
||||
val defaultModifier = remember {
|
||||
Modifier
|
||||
.fillMaxWidth().animateItemPlacement()
|
||||
}
|
||||
|
||||
Row(defaultModifier) {
|
||||
RenderCardItem(item, accountViewModel, nav, routeForLastRead)
|
||||
}
|
||||
@@ -241,7 +242,7 @@ fun NoteCompose(
|
||||
unPackReply: Boolean = true,
|
||||
makeItShort: Boolean = false,
|
||||
addMarginTop: Boolean = true,
|
||||
parentBackgroundColor: Color? = null,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -25,7 +26,6 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -132,8 +132,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||
}
|
||||
|
||||
// val reactionCards = reactionsPerEvent.map { LikeSetCard(it.key, it.value) }
|
||||
val zapsPerUser = mutableMapOf<User, MutableMap<Note, Note>>()
|
||||
val zapsPerEvent = mutableMapOf<Note, MutableMap<Note, Note>>()
|
||||
val zapsPerUser = mutableMapOf<User, MutableList<CombinedZap>>()
|
||||
val zapsPerEvent = mutableMapOf<Note, MutableList<CombinedZap>>()
|
||||
notes
|
||||
.filter { it.event is LnZapEvent }
|
||||
.forEach { zapEvent ->
|
||||
@@ -143,27 +143,23 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||
if (zapRequest != null) {
|
||||
// var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event)
|
||||
// zapRequest.event = newZapRequestEvent
|
||||
zapsPerEvent.getOrPut(zappedPost, { mutableMapOf() }).put(zapRequest, zapEvent)
|
||||
zapsPerEvent.getOrPut(zappedPost, { mutableListOf() }).add(CombinedZap(zapRequest, zapEvent))
|
||||
}
|
||||
} else {
|
||||
val event = (zapEvent.event as LnZapEvent)
|
||||
val author = event.zappedAuthor().mapNotNull {
|
||||
LocalCache.checkGetOrCreateUser(
|
||||
it
|
||||
)
|
||||
}.firstOrNull()
|
||||
val author = event.zappedAuthor().firstNotNullOfOrNull {
|
||||
LocalCache.users[it] // don't create user if it doesn't exist
|
||||
}
|
||||
if (author != null) {
|
||||
val zapRequest = author.zaps.filter { it.value == zapEvent }.keys.firstOrNull()
|
||||
if (zapRequest != null) {
|
||||
zapsPerUser.getOrPut(author, { mutableMapOf() })
|
||||
.put(zapRequest, zapEvent)
|
||||
zapsPerUser.getOrPut(author, { mutableListOf() })
|
||||
.add(CombinedZap(zapRequest, zapEvent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// val zapCards = zapsPerEvent.map { ZapSetCard(it.key, it.value) }
|
||||
|
||||
val boostsPerEvent = mutableMapOf<Note, MutableList<Note>>()
|
||||
notes
|
||||
.filter { it.event is RepostEvent }
|
||||
@@ -178,9 +174,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||
val multiCards = allBaseNotes.map { baseNote ->
|
||||
val boostsInCard = boostsPerEvent[baseNote] ?: emptyList()
|
||||
val reactionsInCard = reactionsPerEvent[baseNote] ?: emptyList()
|
||||
val zapsInCard = zapsPerEvent[baseNote] ?: emptyMap()
|
||||
val zapsInCard = zapsPerEvent[baseNote] ?: emptyList()
|
||||
|
||||
val singleList = (boostsInCard + zapsInCard.values + reactionsInCard)
|
||||
val singleList = (boostsInCard + zapsInCard.map { it.response } + reactionsInCard)
|
||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
.reversed()
|
||||
|
||||
@@ -189,7 +185,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||
baseNote,
|
||||
boostsInCard.filter { it in chunk }.toImmutableList(),
|
||||
reactionsInCard.filter { it in chunk }.toImmutableList(),
|
||||
zapsInCard.filter { it.value in chunk }.toImmutableMap()
|
||||
zapsInCard.filter { it.response in chunk }.toImmutableList()
|
||||
)
|
||||
}
|
||||
}.flatten()
|
||||
@@ -197,7 +193,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||
val userZaps = zapsPerUser.map {
|
||||
ZapUserSetCard(
|
||||
it.key,
|
||||
it.value.toImmutableMap()
|
||||
it.value.toImmutableList()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -346,3 +342,9 @@ fun <T> equalImmutableLists(list1: ImmutableList<T>, list2: ImmutableList<T>): B
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class CombinedZap(val request: Note, val response: Note) {
|
||||
fun createdAt() = response.createdAt()
|
||||
fun idHex() = response.idHex
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -168,6 +168,7 @@ private fun WatchScrollToTop(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun FeedLoaded(
|
||||
state: FeedState.Loaded,
|
||||
@@ -188,7 +189,12 @@ private fun FeedLoaded(
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||
Row(Modifier.fillMaxWidth().defaultMinSize(minHeight = 75.dp)) {
|
||||
val defaultModifier = remember {
|
||||
Modifier
|
||||
.fillMaxWidth().animateItemPlacement()
|
||||
}
|
||||
|
||||
Row(defaultModifier) {
|
||||
NoteCompose(
|
||||
item,
|
||||
routeForLastRead = routeForLastRead,
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.SnackbarDefaults.backgroundColor
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -42,12 +43,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -61,18 +63,13 @@ import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RelaySetEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.*
|
||||
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayPoW
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayReward
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
@@ -82,8 +79,9 @@ import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedNote
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@@ -138,27 +136,34 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
|
||||
) {
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
|
||||
if (index == 0) {
|
||||
NoteMaster(
|
||||
item,
|
||||
modifier = Modifier.drawReplyLevel(
|
||||
item.replyLevel(),
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
if (item.idHex == noteId) MaterialTheme.colors.primary.copy(alpha = 0.52f) else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
ProvideTextStyle(TextStyle(fontSize = 16.sp, lineHeight = 1.20.em)) {
|
||||
NoteMaster(
|
||||
item,
|
||||
modifier = Modifier.drawReplyLevel(
|
||||
item.replyLevel(),
|
||||
MaterialTheme.colors.placeholderText,
|
||||
if (item.idHex == noteId) MaterialTheme.colors.lessImportantLink else MaterialTheme.colors.placeholderText
|
||||
),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column() {
|
||||
Row() {
|
||||
val selectedNoteColor = MaterialTheme.colors.selectedNote
|
||||
val background = remember {
|
||||
if (item.idHex == noteId) mutableStateOf(selectedNoteColor) else null
|
||||
}
|
||||
|
||||
NoteCompose(
|
||||
item,
|
||||
modifier = Modifier.drawReplyLevel(
|
||||
item.replyLevel(),
|
||||
MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
if (item.idHex == noteId) MaterialTheme.colors.primary.copy(alpha = 0.52f) else MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
MaterialTheme.colors.placeholderText,
|
||||
if (item.idHex == noteId) MaterialTheme.colors.lessImportantLink else MaterialTheme.colors.placeholderText
|
||||
),
|
||||
parentBackgroundColor = if (item.idHex == noteId) MaterialTheme.colors.newItemBackgroundColor.compositeOver(MaterialTheme.colors.background) else null,
|
||||
parentBackgroundColor = background,
|
||||
isBoostedNote = false,
|
||||
unPackReply = false,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -232,6 +237,9 @@ fun NoteMaster(
|
||||
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
||||
|
||||
if (noteEvent == null) {
|
||||
BlankNote()
|
||||
} else if (!account.isAcceptable(noteForReports) && !showHiddenNote) {
|
||||
@@ -277,7 +285,7 @@ fun NoteMaster(
|
||||
|
||||
Text(
|
||||
timeAgo(note.createdAt(), context = context),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
@@ -289,7 +297,7 @@ fun NoteMaster(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
NoteDropDownMenu(baseNote, moreActionsExpanded, { moreActionsExpanded = false }, accountViewModel)
|
||||
@@ -317,7 +325,7 @@ fun NoteMaster(
|
||||
if (noteEvent is BadgeDefinitionEvent) {
|
||||
BadgeDisplay(baseNote = note)
|
||||
} else if (noteEvent is LongTextNoteEvent) {
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp, bottom = 12.dp)) {
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
|
||||
Column {
|
||||
noteEvent.image()?.let {
|
||||
AsyncImage(
|
||||
@@ -329,25 +337,23 @@ fun NoteMaster(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
noteEvent.title()?.let {
|
||||
Text(
|
||||
text = it,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
noteEvent.summary()?.let {
|
||||
Text(
|
||||
text = it,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
@@ -365,20 +371,20 @@ fun NoteMaster(
|
||||
) {
|
||||
Column() {
|
||||
if (noteEvent is PeopleListEvent) {
|
||||
DisplayPeopleList(baseNote, MaterialTheme.colors.background, accountViewModel, nav)
|
||||
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
} else if (noteEvent is AudioTrackEvent) {
|
||||
AudioTrackHeader(noteEvent, accountViewModel, nav)
|
||||
} else if (noteEvent is PinListEvent) {
|
||||
PinListHeader(
|
||||
baseNote,
|
||||
MaterialTheme.colors.background,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
} else if (noteEvent is RelaySetEvent) {
|
||||
DisplayRelaySet(
|
||||
baseNote,
|
||||
MaterialTheme.colors.background,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
@@ -395,47 +401,32 @@ fun NoteMaster(
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
} else {
|
||||
val eventContent = note.event?.content()
|
||||
|
||||
} else if (noteEvent is PollNoteEvent) {
|
||||
val canPreview = note.author == account.userProfile() ||
|
||||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
|
||||
!noteForReports.hasAnyReports()
|
||||
|
||||
if (eventContent != null) {
|
||||
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false }
|
||||
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() }
|
||||
RenderPoll(
|
||||
baseNote,
|
||||
false,
|
||||
canPreview,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
} else {
|
||||
val canPreview = note.author == account.userProfile() ||
|
||||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
|
||||
!noteForReports.hasAnyReports()
|
||||
|
||||
SensitivityWarning(
|
||||
hasSensitiveContent = hasSensitiveContent,
|
||||
accountViewModel = accountViewModel
|
||||
) {
|
||||
TranslatableRichTextViewer(
|
||||
eventContent,
|
||||
canPreview,
|
||||
remember { Modifier.fillMaxWidth() },
|
||||
tags,
|
||||
MaterialTheme.colors.background,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
}
|
||||
|
||||
val hashtags = remember {
|
||||
noteEvent.hashtags().toImmutableList()
|
||||
}
|
||||
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
||||
|
||||
if (noteEvent is PollNoteEvent) {
|
||||
PollNote(
|
||||
note,
|
||||
canPreview,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
}
|
||||
}
|
||||
RenderTextEvent(
|
||||
baseNote,
|
||||
false,
|
||||
canPreview,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,7 +434,6 @@ fun NoteMaster(
|
||||
ReactionsRow(note, true, accountViewModel, nav)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ 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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
@@ -47,6 +46,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.note.authenticate
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import nostr.postr.toNsec
|
||||
@@ -124,7 +124,7 @@ private fun NSecCopyButton(
|
||||
copyNSec(context, scope, account, clipboardManager)
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
|
||||
@@ -39,16 +39,25 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
||||
return account.userProfile()
|
||||
}
|
||||
|
||||
fun reactTo(note: Note) {
|
||||
account.reactTo(note)
|
||||
fun reactTo(note: Note, reaction: String) {
|
||||
account.reactTo(note, reaction)
|
||||
}
|
||||
|
||||
fun hasReactedTo(baseNote: Note): Boolean {
|
||||
return account.hasReacted(baseNote)
|
||||
fun reactToOrDelete(note: Note, reaction: String) {
|
||||
val currentReactions = account.reactionTo(note, reaction)
|
||||
if (currentReactions.isNotEmpty()) {
|
||||
account.delete(currentReactions)
|
||||
} else {
|
||||
account.reactTo(note, reaction)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteReactionTo(note: Note) {
|
||||
account.delete(account.reactionTo(note))
|
||||
fun hasReactedTo(baseNote: Note, reaction: String): Boolean {
|
||||
return account.hasReacted(baseNote, reaction)
|
||||
}
|
||||
|
||||
fun deleteReactionTo(note: Note, reaction: String) {
|
||||
account.delete(account.reactionTo(note, reaction))
|
||||
}
|
||||
|
||||
fun hasBoosted(baseNote: Note): Boolean {
|
||||
|
||||
@@ -79,6 +79,8 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -280,7 +282,7 @@ fun DisplayReplyingToNote(
|
||||
modifier = Modifier
|
||||
.padding(end = 5.dp)
|
||||
.size(30.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -317,7 +319,7 @@ fun EditFieldRow(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.reply_here),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
@@ -333,7 +335,7 @@ fun EditFieldRow(
|
||||
leadingIcon = {
|
||||
UploadFromGallery(
|
||||
isUploading = channelScreenModel.isUploadingImage,
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
tint = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(start = 5.dp)
|
||||
) {
|
||||
val fileServer = if (isPrivate) {
|
||||
@@ -417,7 +419,7 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, nav:
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
"${channel.info.about}",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontSize = 12.sp
|
||||
@@ -478,7 +480,6 @@ private fun ChannelActionOptions(
|
||||
private fun NoteCopyButton(
|
||||
note: Channel
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Button(
|
||||
@@ -486,10 +487,10 @@ private fun NoteCopyButton(
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { popupExpanded = true },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
backgroundColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
@@ -502,6 +503,8 @@ private fun NoteCopyButton(
|
||||
expanded = popupExpanded,
|
||||
onDismissRequest = { popupExpanded = false }
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString("nostr:" + note.idNote())); popupExpanded = false }) {
|
||||
Text(stringResource(R.string.copy_channel_id_note_to_the_clipboard))
|
||||
}
|
||||
@@ -524,7 +527,7 @@ private fun EditButton(accountViewModel: AccountViewModel, channel: Channel) {
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { wantsToPost = true },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -545,7 +548,7 @@ private fun JoinButton(accountViewModel: AccountViewModel, channel: Channel, nav
|
||||
onClick = {
|
||||
accountViewModel.account.joinChannel(channel.idHex)
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -564,7 +567,7 @@ private fun LeaveButton(accountViewModel: AccountViewModel, channel: Channel, na
|
||||
accountViewModel.account.leaveChannel(channel.idHex)
|
||||
nav(Route.Message.route)
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -123,7 +124,7 @@ fun ChatroomListScreen(
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
ChatroomTabMenu(
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
@@ -36,6 +35,8 @@ import com.halilibo.richtext.ui.material.MaterialRichText
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.components.richTextDefaults
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -121,7 +122,7 @@ fun ConnectOrbotDialog(onClose: () -> Unit, onPost: () -> Unit, portNumber: Muta
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "9050",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -141,7 +142,7 @@ fun UseOrbotButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifie
|
||||
onPost()
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.TabRow
|
||||
@@ -15,7 +17,11 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
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.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -23,6 +29,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||
@@ -67,6 +74,34 @@ fun HiddenUsersScreen(
|
||||
Column(modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
||||
val pagerState = rememberPagerState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var warnAboutReports by remember { mutableStateOf(accountViewModel.account.warnAboutPostsWithReports) }
|
||||
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = warnAboutReports,
|
||||
onCheckedChange = {
|
||||
warnAboutReports = it
|
||||
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
||||
}
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.warn_when_posts_have_reports_from_your_follows))
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = filterSpam,
|
||||
onCheckedChange = {
|
||||
filterSpam = it
|
||||
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
|
||||
LocalPreferences.saveToEncryptedStorage(accountViewModel.account)
|
||||
}
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.filter_spam_from_strangers))
|
||||
}
|
||||
|
||||
TabRow(
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
|
||||
@@ -156,4 +156,9 @@ fun WatchAccountForHomeScreen(
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class TabItem(val resource: Int, val viewModel: FeedViewModel, val routeForLastRead: String, val scrollStateKey: String)
|
||||
class TabItem(
|
||||
val resource: Int,
|
||||
val viewModel: FeedViewModel,
|
||||
val routeForLastRead: String,
|
||||
val scrollStateKey: String
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -35,8 +36,11 @@ fun LoadRedirectScreen(eventId: String?, navController: NavController) {
|
||||
|
||||
val nav = remember(navController) {
|
||||
{ route: String ->
|
||||
navController.backQueue.removeLast()
|
||||
navController.navigate(route)
|
||||
navController.navigate(route) {
|
||||
popUpTo(Route.Event.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -97,6 +96,8 @@ import com.vitorpamplona.amethyst.ui.screen.RelayFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -425,8 +426,6 @@ private fun ProfileHeader(
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
Box {
|
||||
DrawBanner(baseUser)
|
||||
|
||||
@@ -441,7 +440,7 @@ private fun ProfileHeader(
|
||||
.size(30.dp)
|
||||
.align(Alignment.Center),
|
||||
onClick = { popupExpanded = true },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.background
|
||||
@@ -449,7 +448,7 @@ private fun ProfileHeader(
|
||||
contentPadding = PaddingValues(0.dp)
|
||||
) {
|
||||
Icon(
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
tint = MaterialTheme.colors.placeholderText,
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.more_options)
|
||||
)
|
||||
@@ -468,6 +467,8 @@ private fun ProfileHeader(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
UserPicture(
|
||||
baseUser = baseUser,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -610,7 +611,7 @@ private fun DrawAdditionalInfo(
|
||||
CreateTextWithEmoji(
|
||||
text = "@$it",
|
||||
tags = tags,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -620,7 +621,7 @@ private fun DrawAdditionalInfo(
|
||||
Text(
|
||||
text = user.pubkeyDisplayHex(),
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
IconButton(
|
||||
@@ -633,7 +634,7 @@ private fun DrawAdditionalInfo(
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
|
||||
@@ -660,7 +661,7 @@ private fun DrawAdditionalInfo(
|
||||
painter = painterResource(R.drawable.ic_qrcode),
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -673,7 +674,7 @@ private fun DrawAdditionalInfo(
|
||||
if (!website.isNullOrEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
tint = MaterialTheme.colors.placeholderText,
|
||||
imageVector = Icons.Default.Link,
|
||||
contentDescription = stringResource(R.string.website),
|
||||
modifier = Modifier.size(16.dp)
|
||||
@@ -719,11 +720,16 @@ private fun DrawAdditionalInfo(
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
|
||||
) {
|
||||
val defaultBackground = MaterialTheme.colors.background
|
||||
val background = remember {
|
||||
mutableStateOf(defaultBackground)
|
||||
}
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = false,
|
||||
tags = remember { ImmutableListOfLists(emptyList()) },
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
backgroundColor = background,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
@@ -1216,10 +1222,10 @@ private fun MessageButton(user: User, nav: (String) -> Unit) {
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { nav("Room/${user.pubkeyHex}") },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
backgroundColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
@@ -1246,7 +1252,7 @@ private fun EditButton(account: Account) {
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = { wantsToEdit = true },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -1265,7 +1271,7 @@ fun UnfollowButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -1281,7 +1287,7 @@ fun FollowButton(onClick: () -> Unit, text: Int = R.string.follow) {
|
||||
Button(
|
||||
modifier = Modifier.padding(start = 3.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
@@ -1297,7 +1303,7 @@ fun ShowUserButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(start = 3.dp),
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
shape = ButtonBorder,
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
|
||||
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -316,7 +317,7 @@ private fun SearchTextField(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.npub_hex_username),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
|
||||
@@ -87,6 +87,7 @@ import com.vitorpamplona.amethyst.ui.screen.LoadingFeed
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
||||
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -301,7 +302,7 @@ private fun RenderVideoOrPicture(
|
||||
remember {
|
||||
Modifier
|
||||
.padding(start = 10.dp, end = 10.dp)
|
||||
.height(60.dp)
|
||||
.height(65.dp)
|
||||
.weight(1f)
|
||||
},
|
||||
verticalArrangement = Arrangement.Center
|
||||
@@ -318,7 +319,7 @@ private fun RenderVideoOrPicture(
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
) {
|
||||
RelayBadges(baseNote = note)
|
||||
}
|
||||
@@ -334,14 +335,14 @@ private fun VideoUserOptionAction(
|
||||
var moreActionsExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
modifier = remember { Modifier.size(24.dp) },
|
||||
modifier = remember { Modifier.size(22.dp) },
|
||||
onClick = { moreActionsExpanded = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = remember { Modifier.size(20.dp) },
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
tint = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
|
||||
NoteDropDownMenu(
|
||||
@@ -396,7 +397,7 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, nav: (St
|
||||
BoostReaction(baseNote, accountViewModel, iconSize = 40.dp) {
|
||||
wantsToQuote = baseNote
|
||||
}*/
|
||||
LikeReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, accountViewModel, iconSize = 40.dp, heartSize = 35.dp)
|
||||
LikeReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, accountViewModel, iconSize = 40.dp, heartSize = 35.dp, 28.sp)
|
||||
ZapReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, accountViewModel, iconSize = 40.dp, animationSize = 35.dp)
|
||||
ViewCountReaction(baseNote.idHex, grayTint = MaterialTheme.colors.onBackground, iconSize = 40.dp, barChartSize = 39.dp)
|
||||
}
|
||||
@@ -494,8 +495,8 @@ private fun ShowProgress(postViewModel: NewMediaModel) {
|
||||
progress = postViewModel.uploadingPercentage.value,
|
||||
modifier = Modifier
|
||||
.size(55.dp)
|
||||
.background(MaterialTheme.colors.background)
|
||||
.clip(CircleShape),
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colors.background),
|
||||
strokeWidth = 5.dp
|
||||
)
|
||||
postViewModel.uploadingDescription.value?.let {
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import java.util.*
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@@ -122,7 +123,7 @@ fun LoginPage(
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.nsec_npub_hex_private_key),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
|
||||
@@ -9,3 +9,9 @@ val Shapes = Shapes(
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp)
|
||||
)
|
||||
|
||||
val QuoteBorder = RoundedCornerShape(15.dp)
|
||||
val ButtonBorder = RoundedCornerShape(20.dp)
|
||||
|
||||
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
||||
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
@@ -24,20 +25,72 @@ private val LightColorPalette = lightColors(
|
||||
primaryVariant = Purple700,
|
||||
secondary = Teal200,
|
||||
secondaryVariant = Color(0xFFB66605)
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.Black,
|
||||
onBackground = Color.Black,
|
||||
onSurface = Color.Black,
|
||||
*/
|
||||
)
|
||||
|
||||
private val DarkNewItemBackground = DarkColorPalette.primary.copy(0.12f)
|
||||
private val LightNewItemBackground = LightColorPalette.primary.copy(0.12f)
|
||||
|
||||
private val DarkReplyItemBackground = DarkColorPalette.onSurface.copy(alpha = 0.05f)
|
||||
private val LightReplyItemBackground = LightColorPalette.onSurface.copy(alpha = 0.05f)
|
||||
|
||||
private val DarkSelectedNote = DarkNewItemBackground.compositeOver(DarkColorPalette.background)
|
||||
private val LightSelectedNote = LightNewItemBackground.compositeOver(LightColorPalette.background)
|
||||
|
||||
private val DarkButtonBackground = DarkColorPalette.primary.copy(alpha = 0.32f).compositeOver(DarkColorPalette.background)
|
||||
private val LightButtonBackground = LightColorPalette.primary.copy(alpha = 0.32f).compositeOver(LightColorPalette.background)
|
||||
|
||||
private val DarkLessImportantLink = DarkColorPalette.primary.copy(alpha = 0.52f)
|
||||
private val LightLessImportantLink = LightColorPalette.primary.copy(alpha = 0.52f)
|
||||
|
||||
private val DarkMediumImportantLink = DarkColorPalette.primary.copy(alpha = 0.32f)
|
||||
private val LightMediumImportantLink = LightColorPalette.primary.copy(alpha = 0.32f)
|
||||
|
||||
private val DarkVeryImportantLink = DarkColorPalette.primary.copy(alpha = 0.12f)
|
||||
private val LightVeryImportantLink = LightColorPalette.primary.copy(alpha = 0.12f)
|
||||
|
||||
private val DarkPlaceholderText = DarkColorPalette.onSurface.copy(alpha = 0.32f)
|
||||
private val LightPlaceholderText = LightColorPalette.onSurface.copy(alpha = 0.32f)
|
||||
|
||||
private val DarkSubtleBorder = DarkColorPalette.onSurface.copy(alpha = 0.12f)
|
||||
private val LightSubtleBorder = LightColorPalette.onSurface.copy(alpha = 0.12f)
|
||||
|
||||
private val DarkImageVerifier = Nip05.copy(0.52f).compositeOver(DarkColorPalette.background)
|
||||
private val LightImageVerifier = Nip05.copy(0.52f).compositeOver(LightColorPalette.background)
|
||||
|
||||
private val DarkOverPictureBackground = DarkColorPalette.background.copy(0.62f)
|
||||
private val LightOverPictureBackground = LightColorPalette.background.copy(0.62f)
|
||||
|
||||
val Colors.newItemBackgroundColor: Color
|
||||
@Composable
|
||||
get() = if (isLight) primary.copy(0.05f) else primary.copy(0.12f)
|
||||
get() = if (isLight) LightNewItemBackground else DarkNewItemBackground
|
||||
|
||||
val Colors.replyBackground: Color
|
||||
get() = if (isLight) LightReplyItemBackground else DarkReplyItemBackground
|
||||
|
||||
val Colors.selectedNote: Color
|
||||
get() = if (isLight) LightSelectedNote else DarkSelectedNote
|
||||
|
||||
val Colors.secondaryButtonBackground: Color
|
||||
get() = if (isLight) LightButtonBackground else DarkButtonBackground
|
||||
|
||||
val Colors.lessImportantLink: Color
|
||||
get() = if (isLight) LightLessImportantLink else DarkLessImportantLink
|
||||
|
||||
val Colors.mediumImportanceLink: Color
|
||||
get() = if (isLight) LightMediumImportantLink else DarkMediumImportantLink
|
||||
val Colors.veryImportantLink: Color
|
||||
get() = if (isLight) LightVeryImportantLink else DarkVeryImportantLink
|
||||
|
||||
val Colors.placeholderText: Color
|
||||
get() = if (isLight) LightPlaceholderText else DarkPlaceholderText
|
||||
|
||||
val Colors.subtleBorder: Color
|
||||
get() = if (isLight) LightSubtleBorder else DarkSubtleBorder
|
||||
|
||||
val Colors.hashVerified: Color
|
||||
get() = if (isLight) LightImageVerifier else DarkImageVerifier
|
||||
|
||||
val Colors.overPictureBackground: Color
|
||||
get() = if (isLight) LightOverPictureBackground else DarkOverPictureBackground
|
||||
|
||||
@Composable
|
||||
fun AmethystTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
|
||||
|
||||
@@ -393,4 +393,6 @@
|
||||
<string name="content_warning_see_warnings">A tartalomra vonatkozó figyelmeztetéseket mindig jelenítse meg</string>
|
||||
|
||||
<string name="recommended_apps">Ajánlottak: </string>
|
||||
<string name="filter_spam_from_strangers">Idegenektől SPAM szűrése</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Figyelmeztessen ha a bejegyzések azok által akiket követek jelentve vannak</string>
|
||||
</resources>
|
||||
|
||||
@@ -412,4 +412,9 @@
|
||||
<string name="content_warning_see_warnings">Always show content warnings</string>
|
||||
|
||||
<string name="recommended_apps">Recommends: </string>
|
||||
<string name="filter_spam_from_strangers">Filter spam from strangers</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Warn when posts have reports from your follows</string>
|
||||
|
||||
<string name="new_reaction_symbol">New Reaction Symbol</string>
|
||||
<string name="no_reaction_type_setup_long_press_to_change">No reaction types selected. Long Press to change</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,8 +18,7 @@ import java.util.regex.Pattern
|
||||
data class ResultOrError(
|
||||
val result: String?,
|
||||
val sourceLang: String?,
|
||||
val targetLang: String?,
|
||||
val error: Exception?
|
||||
val targetLang: String?
|
||||
)
|
||||
|
||||
object LanguageTranslatorService {
|
||||
@@ -78,7 +77,7 @@ object LanguageTranslatorService {
|
||||
var fixedText = task.result.replace("# [", "#[") // fixes tags that always return with a space
|
||||
results.add(decodeDictionary(fixedText, dict))
|
||||
}
|
||||
ResultOrError(results.joinToString("\n"), source, target, null)
|
||||
ResultOrError(results.joinToString("\n"), source, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -35,6 +36,7 @@ import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
|
||||
import com.vitorpamplona.amethyst.service.lang.ResultOrError
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
@@ -45,13 +47,13 @@ fun TranslatableRichTextViewer(
|
||||
canPreview: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: Color,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
Column {
|
||||
var translatedTextState by remember {
|
||||
mutableStateOf(ResultOrError(content, null, null, null))
|
||||
mutableStateOf(ResultOrError(content, null, null))
|
||||
}
|
||||
|
||||
var showOriginal by remember { mutableStateOf(false) }
|
||||
@@ -116,7 +118,7 @@ private fun TranslationMessage(
|
||||
.padding(top = 5.dp)
|
||||
) {
|
||||
val clickableTextStyle =
|
||||
SpanStyle(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
|
||||
SpanStyle(color = MaterialTheme.colors.lessImportantLink)
|
||||
|
||||
val annotatedTranslationString = buildAnnotatedString {
|
||||
withStyle(clickableTextStyle) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
buildscript {
|
||||
ext {
|
||||
fragment_version = "1.5.7"
|
||||
fragment_version = "1.6.0"
|
||||
lifecycle_version = '2.6.1'
|
||||
compose_ui_version = '1.4.3'
|
||||
nav_version = "2.5.3"
|
||||
nav_version = "2.6.0"
|
||||
room_version = "2.4.3"
|
||||
accompanist_version = '0.30.0'
|
||||
coil_version = '2.4.0'
|
||||
|
||||
@@ -23,4 +23,5 @@ kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
android.enableR8.fullMode=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonFinalResIds=false
|
||||
android.nonFinalResIds=false
|
||||
org.gradle.unsafe.configuration-cache=true
|
||||
Reference in New Issue
Block a user