Merge remote-tracking branch 'origin/HEAD' into less_memory_test_branch

This commit is contained in:
Vitor Pamplona 2023-03-14 17:46:29 -04:00
commit a71078f90f
20 changed files with 413 additions and 112 deletions

View File

@ -28,10 +28,19 @@ jobs:
- name: Build AAB
run: ./gradlew clean bundleRelease --stacktrace
- name: Sign AAB
- name: Sign AAB (Google Play)
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/bundle/release
releaseDirectory: app/build/outputs/bundle/playRelease
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Sign AAB (F-Droid)
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/bundle/fdroidRelease
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
@ -40,10 +49,19 @@ jobs:
- name: Build APK
run: ./gradlew assembleRelease --stacktrace --no-daemon
- name: Sign APK
- name: Sign APK (Google Play)
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
releaseDirectory: app/build/outputs/apk/play/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Sign APK (F-Droid)
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/fdroid/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
@ -60,24 +78,96 @@ jobs:
draft: false
prerelease: false
- name: Upload APK Asset
id: upload-release-asset-apk
# Google Play APK
- name: Upload Play APK Universal Asset
id: upload-release-asset-play-universal-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/release/app-release-unsigned-signed.apk
asset_name: app-release-${{ github.ref_name }}.apk
asset_path: app/build/outputs/apk/play/release/app-play-universal-release-unsigned-signed.apk
asset_name: amethyst-googleplay-universal-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload AAB Asset
id: upload-release-asset-aab
- name: Upload Play APK x86 Asset
id: upload-release-asset-play-x86-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/bundle/release/app-release.aab
asset_name: app-release-${{ github.ref_name }}.aab
asset_path: app/build/outputs/apk/play/release/app-play-x86-release-unsigned-signed.apk
asset_name: amethyst-googleplay-x86-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload Play APK x86_64 Asset
id: upload-release-asset-play-x86-64-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/play/release/app-play-x86_64-release-unsigned-signed.apk
asset_name: amethyst-googleplay-x86_64-${{ github.ref_name }}.apk
asset_content_type: application/zip
# F-Droid APK
- name: Upload F-Droid APK Universal Asset
id: upload-release-asset-fdroid-universal-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-universal-release-unsigned-signed.apk
asset_name: amethyst-fdroid-universal-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload F-Droid APK x86 Asset
id: upload-release-asset-fdroid-x86-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86-release-unsigned-signed.apk
asset_name: amethyst-fdroid-x86-${{ github.ref_name }}.apk
asset_content_type: application/zip
- name: Upload F-Droid APK x86_64 Asset
id: upload-release-asset-fdroid-x86-64-apk
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-x86_64-release-unsigned-signed.apk
asset_name: amethyst-fdroid-x86_64-${{ github.ref_name }}.apk
asset_content_type: application/zip
# Google Play AAB
- name: Upload Google Play AAB Asset
id: upload-release-asset-play-aab
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/bundle/playRelease/app-play-release.aab
asset_name: amethyst-googleplay-${{ github.ref_name }}.aab
asset_content_type: application/zip
# FDroid AAB
- name: Upload F-Droid AAB Asset
id: upload-release-asset-fdroid-aab
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/bundle/fdroidRelease/app-fdroid-release.aab
asset_name: amethyst-fdroid-${{ github.ref_name }}.aab
asset_content_type: application/zip

View File

@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
versionCode 91
versionName "0.24.2"
versionCode 95
versionName "0.25.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -33,19 +33,45 @@ android {
resValue "string", "app_name", "@string/app_name_debug"
}
}
flavorDimensions "channel"
productFlavors {
play {
dimension "channel"
}
fdroid {
dimension "channel"
}
}
splits {
abi {
enable true
reset()
include "x86", "x86_64"
universalApk true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion "1.4.3"
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
@ -66,10 +92,10 @@ dependencies {
implementation("androidx.navigation:navigation-compose:$nav_version")
// Observe Live data as State
implementation 'androidx.compose.runtime:runtime-livedata:1.4.0-beta02'
implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version"
implementation 'androidx.compose.material:material:1.4.0-beta02'
implementation "androidx.compose.material:material-icons-extended:1.4.0-beta02"
implementation "androidx.compose.material:material:$compose_ui_version"
implementation "androidx.compose.material:material-icons-extended:$compose_ui_version"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
@ -135,10 +161,10 @@ dependencies {
implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
// Local model for language identification
implementation 'com.google.mlkit:language-id:17.0.4'
playImplementation 'com.google.mlkit:language-id:17.0.4'
// Google services model the translate text
implementation 'com.google.mlkit:translate:17.0.1'
playImplementation 'com.google.mlkit:translate:17.0.1'
// Automatic memory leak detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

View File

@ -0,0 +1,26 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable
fun TranslateableRichTextViewer(
content: String,
canPreview: Boolean,
modifier: Modifier = Modifier,
tags: List<List<String>>?,
backgroundColor: Color,
accountViewModel: AccountViewModel,
navController: NavController
) = ExpandableRichTextViewer(
content,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
navController
)

View File

@ -79,7 +79,7 @@ object LocalPreferences {
}
val prefs = encryptedPreferences()
prefs.edit().apply {
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma))
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
}.apply()
}
@ -101,7 +101,7 @@ object LocalPreferences {
if (accounts.remove(npub)) {
val prefs = encryptedPreferences()
prefs.edit().apply {
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma))
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
}.apply()
}
}

View File

@ -319,7 +319,7 @@ class Account(
LocalCache.consume(signedEvent, null)
}
fun sendPrivateMeesage(message: String, toUser: String, replyingTo: Note? = null) {
fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null) {
if (!isWriteable()) return
val user = LocalCache.users[toUser] ?: return

View File

@ -218,7 +218,9 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
Row(modifier = Modifier.fillMaxWidth()) {
UploadFromGallery(
isUploading = postViewModel.isUploadingImage
isUploading = postViewModel.isUploadingImage,
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(bottom = 10.dp)
) {
postViewModel.upload(it, context)
}

View File

@ -4,23 +4,38 @@ import android.net.Uri
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddPhotoAlternate
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
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 java.util.concurrent.atomic.AtomicBoolean
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun UploadFromGallery(
isUploading: Boolean,
tint: Color,
modifier: Modifier,
onImageChosen: (Uri) -> Unit
) {
val cameraPermissionState =
@ -43,64 +58,111 @@ fun UploadFromGallery(
}
}
)
} else {
Box() {
TextButton(
modifier = Modifier
.align(Alignment.TopCenter),
enabled = !isUploading,
onClick = {
showGallerySelect = true
}
) {
Icon(
painter = painterResource(id = R.drawable.ic_add_photo),
contentDescription = stringResource(id = R.string.upload_image),
modifier = Modifier
.height(20.dp)
.padding(end = 8.dp),
tint = MaterialTheme.colors.primary
)
}
if (!isUploading) {
Text(stringResource(R.string.upload_image))
} else {
Text(stringResource(R.string.uploading))
}
}
}
UploadBoxButton(isUploading, tint, modifier) {
showGallerySelect = true
}
} else {
Column {
Button(
onClick = { cameraPermissionState.launchPermissionRequest() },
enabled = !isUploading
) {
if (!isUploading) {
Text(stringResource(R.string.upload_image))
} else {
Text(stringResource(R.string.uploading))
}
UploadBoxButton(isUploading, tint, modifier) {
cameraPermissionState.launchPermissionRequest()
}
}
}
@Composable
private fun UploadBoxButton(
isUploading: Boolean,
tint: Color,
modifier: Modifier,
onClick: () -> Unit
) {
Box() {
IconButton(
modifier = modifier.align(Alignment.Center),
enabled = !isUploading,
onClick = {
onClick()
}
) {
if (!isUploading) {
Icon(
imageVector = Icons.Default.AddPhotoAlternate,
contentDescription = stringResource(id = R.string.upload_image),
modifier = Modifier.height(25.dp),
tint = tint
)
} else {
LoadingAnimation()
}
}
}
}
@Composable
fun LoadingAnimation(
indicatorSize: Dp = 20.dp,
circleColors: List<Color> = listOf(
Color(0xFF5851D8),
Color(0xFF833AB4),
Color(0xFFC13584),
Color(0xFFE1306C),
Color(0xFFFD1D1D),
Color(0xFFF56040),
Color(0xFFF77737),
Color(0xFFFCAF45),
Color(0xFFFFDC80),
Color(0xFF5851D8)
),
animationDuration: Int = 1000
) {
val infiniteTransition = rememberInfiniteTransition()
val rotateAnimation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = animationDuration,
easing = LinearEasing
)
)
)
CircularProgressIndicator(
modifier = Modifier
.size(size = indicatorSize)
.rotate(degrees = rotateAnimation)
.border(
width = 4.dp,
brush = Brush.sweepGradient(circleColors),
shape = CircleShape
),
progress = 1f,
strokeWidth = 1.dp,
color = MaterialTheme.colors.background // Set background color
)
}
@Composable
fun GallerySelect(
onImageUri: (Uri?) -> Unit = { }
) {
var hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) }
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? ->
onImageUri(uri)
hasLaunched.set(false)
}
)
@Composable
fun LaunchGallery() {
SideEffect {
launcher.launch("image/*")
if (!hasLaunched.getAndSet(true)) {
launcher.launch("image/*")
}
}
}

View File

@ -1,11 +1,20 @@
package com.vitorpamplona.amethyst.ui.components
import android.content.Context
import android.net.Uri
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.ImageRequest
import java.nio.ByteBuffer
import coil.request.Options
import okio.Buffer
import java.security.MessageDigest
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
private fun toHex(color: Color): String {
val argb = color.toArgb()
@ -40,29 +49,54 @@ private fun svgString(msg: String): String {
val mouth = mouths[mouthIndex]
val accessory = accessories[accIndex]
return """
<svg id="$hashHex" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
<defs>
<style>
.cls-bg{fill:${toHex(bgColor)}}.cls-fill-1{fill:${toHex(fgColor)};}.cls-fill-2{fill:${toHex(fgColor)};}
${body.style}${face.style}${eye.style}${mouth.style}${accessory.style}
</style>
</defs>
<title>Robohash $hashHex</title>
${background}${body.paths}${face.paths}${eye.paths}${mouth.paths}${accessory.paths}
</svg>
""".trimIndent()
return """<svg id="$hashHex" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
<defs>
<style>
.cls-bg{fill:${toHex(bgColor)}}.cls-fill-1{fill:${toHex(fgColor)};}.cls-fill-2{fill:${toHex(fgColor)};}
${body.style}${face.style}${eye.style}${mouth.style}${accessory.style}
</style>
</defs>
<title>Robohash $hashHex</title>
${background}${body.paths}${face.paths}${eye.paths}${mouth.paths}${accessory.paths}
</svg>"""
}
class HashImageFetcher(
private val context: Context,
private val data: Uri
) : Fetcher {
@OptIn(ExperimentalTime::class)
override suspend fun fetch(): SourceResult {
val (value, elapsed) = measureTimedValue {
val source = try {
Buffer().apply { write(svgString(data.toString()).toByteArray()) }
} finally {
}
SourceResult(
source = ImageSource(source, context),
mimeType = null,
dataSource = DataSource.MEMORY
)
}
println("Elapsed: $elapsed")
return value
}
class Factory : Fetcher.Factory<Uri> {
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher {
return HashImageFetcher(options.context, data)
}
}
}
object Robohash {
fun imageRequest(context: Context, message: String): ImageRequest {
return ImageRequest
.Builder(context)
.data(
ByteBuffer.wrap(
svgString(message).toByteArray()
)
)
.data("robohash:$message")
.fetcherFactory(HashImageFetcher.Factory())
.crossfade(100)
.build()
}

View File

@ -99,6 +99,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
Column() {
TopAppBar(
elevation = 0.dp,
backgroundColor = MaterialTheme.colors.surface,
title = {
Column(
modifier = Modifier.fillMaxWidth(),

View File

@ -344,7 +344,7 @@ fun ChatroomMessageCompose(
}
}
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
}
}
}

View File

@ -52,7 +52,6 @@ class AccountStateViewModel() : ViewModel() {
Account(Persona(Hex.decode(key)))
}
LocalPreferences.updatePrefsForLogin(account)
login(account)
}
@ -64,7 +63,6 @@ class AccountStateViewModel() : ViewModel() {
fun newKey() {
val account = Account(Persona())
LocalPreferences.updatePrefsForLogin(account)
login(account)
}

View File

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -65,7 +66,9 @@ import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter
@ -82,9 +85,10 @@ fun ChannelScreen(
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account
val context = LocalContext.current
val channelScreenModel: NewPostViewModel = viewModel()
if (account != null && channelId != null) {
val newPost = remember { mutableStateOf(TextFieldValue("")) }
val replyTo = remember { mutableStateOf<Note?>(null) }
ChannelFeedFilter.loadMessagesBetween(account, channelId)
@ -98,6 +102,9 @@ fun ChannelScreen(
LaunchedEffect(Unit) {
feedViewModel.invalidateData()
channelScreenModel.imageUploadingError.collect { error ->
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
}
}
DisposableEffect(channelId) {
@ -178,8 +185,10 @@ fun ChannelScreen(
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = newPost.value,
onValueChange = { newPost.value = it },
value = channelScreenModel.message,
onValueChange = {
channelScreenModel.updateMessage(it)
},
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
),
@ -195,15 +204,24 @@ fun ChannelScreen(
trailingIcon = {
PostButton(
onPost = {
account.sendChannelMessage(newPost.value.text, channel.idHex, replyTo.value, null)
newPost.value = TextFieldValue("")
account.sendChannelMessage(channelScreenModel.message.text, channel.idHex, replyTo.value, null)
channelScreenModel.message = TextFieldValue("")
replyTo.value = null
feedViewModel.refresh() // Don't wait a full second before updating
},
newPost.value.text.isNotBlank(),
isActive = channelScreenModel.message.text.isNotBlank() && !channelScreenModel.isUploadingImage,
modifier = Modifier.padding(end = 10.dp)
)
},
leadingIcon = {
UploadFromGallery(
isUploading = channelScreenModel.isUploadingImage,
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
modifier = Modifier.padding(start = 5.dp)
) {
channelScreenModel.upload(it, context)
}
},
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent

View File

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -36,6 +37,7 @@ 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.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
@ -50,7 +52,9 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
@ -64,9 +68,10 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel
fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account
val context = LocalContext.current
val chatRoomScreenModel: NewPostViewModel = viewModel()
if (account != null && userId != null) {
val newPost = remember { mutableStateOf(TextFieldValue("")) }
val replyTo = remember { mutableStateOf<Note?>(null) }
ChatroomFeedFilter.loadMessagesBetween(account, userId)
@ -77,6 +82,9 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
LaunchedEffect(userId) {
feedViewModel.refresh()
chatRoomScreenModel.imageUploadingError.collect { error ->
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
}
}
DisposableEffect(userId) {
@ -156,8 +164,8 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = newPost.value,
onValueChange = { newPost.value = it },
value = chatRoomScreenModel.message,
onValueChange = { chatRoomScreenModel.updateMessage(it) },
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
),
@ -173,15 +181,24 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
trailingIcon = {
PostButton(
onPost = {
account.sendPrivateMeesage(newPost.value.text, userId, replyTo.value)
newPost.value = TextFieldValue("")
account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value)
chatRoomScreenModel.message = TextFieldValue("")
replyTo.value = null
feedViewModel.refresh() // Don't wait a full second before updating
},
newPost.value.text.isNotBlank(),
isActive = chatRoomScreenModel.message.text.isNotBlank() && !chatRoomScreenModel.isUploadingImage,
modifier = Modifier.padding(end = 10.dp)
)
},
leadingIcon = {
UploadFromGallery(
isUploading = chatRoomScreenModel.isUploadingImage,
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
modifier = Modifier.padding(start = 5.dp)
) {
chatRoomScreenModel.upload(it, context)
}
},
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent

View File

@ -25,6 +25,8 @@ import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.screen.FeedView
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
@ -43,10 +45,12 @@ fun HomeScreen(
scrollToTop: Boolean = false
) {
val coroutineScope = rememberCoroutineScope()
val account = accountViewModel.accountLiveData.value?.account ?: return
LaunchedEffect(accountViewModel) {
HomeNewThreadFeedFilter.account = account
HomeConversationsFeedFilter.account = account
NostrHomeDataSource.resetFilters()
homeFeedViewModel.refresh()
repliesFeedViewModel.refresh()
}

View File

@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
import com.vitorpamplona.amethyst.ui.note.ChannelName
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.UserCompose
@ -81,8 +82,11 @@ fun SearchScreen(
scrollToTop: Boolean = false
) {
val lifeCycleOwner = LocalLifecycleOwner.current
val account = accountViewModel.accountLiveData.value?.account ?: return
LaunchedEffect(accountViewModel) {
GlobalFeedFilter.account = account
NostrGlobalDataSource.resetFilters()
feedViewModel.refresh()
}

View File

@ -8,10 +8,10 @@
<string name="show_anyway">Laat toch zien</string>
<string name="post_was_flagged_as_inappropriate_by">Bericht gemarkeerd als ongepast door</string>
<string name="post_not_found">Bericht niet gevonden</string>
<string name="channel_image">Groepsafbeelding</string>
<string name="channel_image">Kanaalafbeelding</string>
<string name="referenced_event_not_found">Verwezen event niet gevonden</string>
<string name="could_not_decrypt_the_message">Note versleuteld met encryptie</string>
<string name="group_picture">Kanaal-afbeelding</string>
<string name="group_picture">Groepafbeelding</string>
<string name="explicit_content">Expliciete inhoud</string>
<string name="spam">Spam</string>
<string name="impersonation">Imitatie</string>
@ -53,7 +53,7 @@
<string name="lightning_invoice">Lightning invoice</string>
<string name="pay">Betalen</string>
<string name="lightning_tips">Lightning Tips</string>
<string name="note_to_receiver">Notitie aan ontvanger</string>
<string name="note_to_receiver">Note aan ontvanger</string>
<string name="thank_you_so_much">Hartelijk bedankt!</string>
<string name="amount_in_sats">Bedrag in sats</string>
<string name="send_sats">Verstuur sats</string>
@ -97,16 +97,16 @@
<string name="uploading">Uploaden…</string>
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Gebruiker heeft geen Lightning Adress ingesteld om sats te ontvangen</string>
<string name="reply_here">"hier reageren.. "</string>
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Kopieert Notitie ID naar klembord om te delen</string>
<string name="copy_channel_id_note_to_the_clipboard">Kopieer kanaal ID (Notitie) naar klembord</string>
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Kopieert note ID naar klembord om te delen</string>
<string name="copy_channel_id_note_to_the_clipboard">Kopieer kanaal ID (note) naar klembord</string>
<string name="edits_the_channel_metadata">Past de kanaal-metadata aan</string>
<string name="join">Lid worden</string>
<string name="known">Bekend</string>
<string name="new_requests">Nieuw verzoek</string>
<string name="blocked_users">Geblokeerde gebruikers</string>
<string name="new_threads">Nieuwe notities</string>
<string name="new_threads">Nieuwe notes</string>
<string name="conversations">Gesprekken</string>
<string name="notes">Notities</string>
<string name="notes">Notes</string>
<string name="replies">Reacties</string>
<string name="follows">"Volgend"</string>
<string name="reports">"Rapporten"</string>
@ -174,9 +174,9 @@
<string name="report_hateful_speech">Haatdragende taal rapporteren</string>
<string name="report_nudity_porn">Naaktheid / porno rapporteren</string>
<string name="others">anderen</string>
<string name="mark_all_known_as_read">Alle bekende als gelezen markeren</string>
<string name="mark_all_new_as_read">Alle nieuwe als gelezen markeren</string>
<string name="mark_all_as_read">Alles als gelezen markeren</string>
<string name="mark_all_known_as_read">Bekende markeren als gelezen</string>
<string name="mark_all_new_as_read">Nieuwe markeren als gelezen</string>
<string name="mark_all_as_read">Alles markeren als gelezen</string>
<string name="backup_keys">Back-up sleutels</string>
<string name="account_backup_tips_md" tools:ignore="Typos">
## Sleutel back-up en veiligheidstips
@ -193,19 +193,38 @@
<string name="badge_award_image_for">"Badge award afbeelding voor %1$s"</string>
<string name="new_badge_award_notif">Je hebt een nieuwe Badge award ontvangen</string>
<string name="award_granted_to">Badge award gegeven aan</string>
<string name="copied_note_text_to_clipboard">Notitie tekst gekopieerd naar klembord</string>
<string name="copied_note_text_to_clipboard">Note tekst gekopieerd naar klembord</string>
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">@npub auteur gekopieerd naar klembord</string>
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Notitie ID (@note1) gekopieerd naar klembord</string>
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Note ID (@note1) gekopieerd naar klembord</string>
<string name="select_text_dialog_top">Selecteer tekst</string>
<string name="quick_action_select">Selecteer</string>
<string name="quick_action_share_browser_link">Deel browserlink</string>
<string name="quick_action_share">Delen</string>
<string name="quick_action_copy_user_id">Auteur ID</string>
<string name="quick_action_copy_note_id">Notitie ID</string>
<string name="quick_action_copy_note_id">Note ID</string>
<string name="quick_action_copy_text">Kopieer tekst</string>
<string name="quick_action_delete">Verwijderen</string>
<string name="quick_action_unfollow">Ontvolgen</string>
<string name="quick_action_follow">Volgen</string>
<string name="quick_action_request_deletion_alert_title">Verzoek om te verwijderen</string>
<string name="quick_action_request_deletion_alert_body">Amethyst zal vragen om uw notitie te verwijderen van de relays waarmee u momenteel verbonden bent. Er is geen garantie dat uw notitie permanent wordt verwijderd van deze relays, of van andere relays waar het kan worden opgeslagen.</string>
<string name="quick_action_request_deletion_alert_body">Amethyst zal vragen om uw note te verwijderen van de relays waarmee u momenteel verbonden bent. Er is geen garantie dat uw note permanent wordt verwijderd van deze relays, of van andere relays waar het kan worden opgeslagen.</string>
<string name="github" translatable="false">Github Gist w/ Proof</string>
<string name="telegram" translatable="false">Telegram</string>
<string name="mastodon" translatable="false">Mastodon Post ID w/ Proof</string>
<string name="twitter" translatable="false">Twitter Status w/ Proof</string>
<string name="github_proof_url_template" translatable="false">https://gist.github.com/&lt;user&gt;/&lt;gist&gt;</string>
<string name="telegram_proof_url_template" translatable="false">https://t.me/&lt;proof post&gt;</string>
<string name="mastodon_proof_url_template" translatable="false">https://&lt;server&gt;/&lt;user&gt;/&lt;proof post&gt;</string>
<string name="twitter_proof_url_template" translatable="false">https://twitter.com/&lt;user&gt;/status/&lt;proof post&gt;</string>
<string name="private_conversation_notification">"&lt;Kan privébericht niet ontsleutelen&gt;\n\nJe werd genoemd in een privégesprek tussen %1$s en %2$s."</string>
<string name="quick_action_delete_button">Verwijderen</string>
<string name="quick_action_dont_show_again_button">Niet meer laten zien</string>
<string name="account_switch_add_account_dialog_title">Nieuw account toevoegen</string>
<string name="drawer_accounts">Accounts</string>
<string name="account_switch_select_account">Account selecteren</string>
<string name="account_switch_add_account_btn">Nieuw account toevoegen</string>
<string name="account_switch_active_account">Account activeren</string>
<string name="account_switch_has_private_key">Heeft privésleutel</string>
<string name="account_switch_pubkey_only">Alleen lezen, geen privésleutel</string>
<string name="back">Terug</string>
</resources>

View File

@ -1,16 +1,16 @@
buildscript {
ext {
fragment_version = "1.5.5"
lifecycle_version = '2.6.0-rc1'
compose_ui_version = '1.4.0-beta02'
lifecycle_version = '2.6.0'
compose_ui_version = '1.4.0-rc01'
nav_version = "2.5.3"
room_version = "2.4.3"
accompanist_version = "0.28.0"
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.4.1' apply false
id 'com.android.library' version '7.4.1' apply false
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false
}