diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt index 2a3fb994a..456e7e327 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.components +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,45 +32,47 @@ data class ResizeImage(val url: String?, val size: Dp) { } @Composable fun AsyncUserImageProxy( - pubkeyHex: String, - model: ResizeImage, - contentDescription: String?, - modifier: Modifier = Modifier, - alignment: Alignment = Alignment.Center, - contentScale: ContentScale = ContentScale.Fit, - alpha: Float = DefaultAlpha, - colorFilter: ColorFilter? = null, - filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, + pubkeyHex: String, + model: ResizeImage, + contentDescription: String?, + modifier: Modifier = Modifier, + alignment: Alignment = Alignment.Center, + contentScale: ContentScale = ContentScale.Fit, + alpha: Float = DefaultAlpha, + colorFilter: ColorFilter? = null, + filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { - var loading by remember { mutableStateOf(false) } - var error by remember { mutableStateOf(false) } + var loading by remember { mutableStateOf(false) } + var error by remember { mutableStateOf(false) } - if (model.url == null || loading || error) { - RoboHashAsyncImage( - message = pubkeyHex, - contentDescription = contentDescription, - modifier = modifier, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter, - filterQuality = filterQuality, - ) - } else { - AsyncImage( - model = model.proxyUrl(), - contentDescription = contentDescription, - modifier = modifier, - onLoading = { loading = true }, - onSuccess = { loading = false; error = false }, - onError = { loading = false; error = true }, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter, - filterQuality = filterQuality - ) - } + Box() { + AsyncImage( + model = model.proxyUrl(), + contentDescription = contentDescription, + modifier = modifier, + onLoading = { loading = true }, + onSuccess = { loading = false; error = false }, + onError = { loading = false; error = true }, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality + ) + + if (model.url == null || loading || error) { + RoboHashAsyncImage( + message = pubkeyHex, + contentDescription = contentDescription, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality + ) + } + } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RoboHashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RoboHashAsyncImage.kt index 0b06e3f38..4e2dd9a11 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RoboHashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RoboHashAsyncImage.kt @@ -37,7 +37,7 @@ private fun bytesToRGB(b1: Byte, b2: Byte, b3: Byte): Color { fun generateRoboHashSvg(msg: String): String { val hash = sha256.digest(msg.toByteArray()) - val hashHex = hash.joinToString(separator = "") {b -> "%02x".format(b)} + val hashHex = hash.joinToString(separator = "") { b -> "%02x".format(b) } val bgColor1 = bytesToRGB(hash[0], hash[1], hash[2]) val bgColor2 = bytesToRGB(hash[3], hash[4], hash[5]) val fgColor = bytesToRGB(hash[6], hash[7], hash[8]) @@ -71,9 +71,11 @@ fun generateRoboHashSvg(msg: String): String { fun roboHashImageRequest(context: Context, message: String): ImageRequest { return ImageRequest .Builder(context) - .data(ByteBuffer.wrap( - generateRoboHashSvg(message).toByteArray() - )) + .data( + ByteBuffer.wrap( + generateRoboHashSvg(message).toByteArray() + ) + ) .build() } @@ -88,7 +90,7 @@ fun RoboHashAsyncImage( contentScale: ContentScale = ContentScale.Fit, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, - filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, + filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { AsyncImage( model = roboHashImageRequest(LocalContext.current, message), @@ -100,7 +102,7 @@ fun RoboHashAsyncImage( contentScale = contentScale, alpha = alpha, colorFilter = colorFilter, - filterQuality = filterQuality, + filterQuality = filterQuality ) } @@ -114,7 +116,7 @@ private val backgrounds: List = listOf( """""", """""", """""", - """""", + """""" ) private val accessories: List = listOf( @@ -157,7 +159,7 @@ private val accessories: List = listOf( Part( """.cls-09-2{fill:none;}.cls-09-2,.cls-09-4{stroke:#000;stroke-miterlimit:10;}.cls-09-3,.cls-09-4{fill:#fff;}.cls-09-3{fill-opacity:0.2;}.cls-09-5{fill:#d0d2d3;}.cls-09-6{fill-opacity:0.4;}""", """""" - ), + ) ) private val bodies: List = listOf( @@ -200,7 +202,7 @@ private val bodies: List = listOf( Part( """.cls-19-2{fill-opacity:0.6;}.cls-19-11,.cls-19-13,.cls-19-14,.cls-19-2,.cls-19-3,.cls-19-4,.cls-19-6,.cls-19-8{stroke:#000;}.cls-19-11,.cls-19-13,.cls-19-2,.cls-19-4,.cls-19-6,.cls-19-8{stroke-miterlimit:10;}.cls-19-11,.cls-19-14,.cls-19-3,.cls-19-8{fill:none;}.cls-19-14,.cls-19-3{stroke-linecap:round;stroke-linejoin:round;}.cls-19-10,.cls-19-12,.cls-19-4,.cls-19-5{fill:#fff;}.cls-19-12,.cls-19-4{fill-opacity:0.2;}.cls-19-4{stroke-opacity:0;}.cls-19-5{fill-opacity:0.1;}.cls-19-6{fill:#6d6e70;}.cls-19-7{fill:#58595b;}.cls-19-13,.cls-19-9{fill-opacity:0.4;}.cls-19-10{fill-opacity:0.5;}.cls-19-11,.cls-19-14{stroke-width:0.75px;}""", """""" - ), + ) ) private val eyes: List = listOf( @@ -243,7 +245,7 @@ private val eyes: List = listOf( Part( """.cls-29-2{fill:#fff;}.cls-29-2,.cls-29-4{fill-opacity:0.4;}.cls-29-3{fill:none;}.cls-29-3,.cls-29-4{stroke:#000;stroke-linecap:round;stroke-linejoin:round;}.cls-29-4{stroke-width:0.75px;}""", """""" - ), + ) ) private val faces: List = listOf( @@ -286,7 +288,7 @@ private val faces: List = listOf( Part( """.cls-39-2{fill:#fff;fill-opacity:0.4;}.cls-39-3{fill:none;}.cls-39-3,.cls-39-4{stroke:#000;stroke-miterlimit:10;}.cls-39-4{fill-opacity:0.2;stroke-width:0.75px;}""", """""" - ), + ) ) private val mouths: List = listOf( @@ -329,5 +331,5 @@ private val mouths: List = listOf( Part( """.cls-49-2{fill:none;}.cls-49-2,.cls-49-3,.cls-49-5{stroke:#000;}.cls-49-2,.cls-49-3{stroke-linecap:round;stroke-linejoin:round;}.cls-49-3{opacity:0.1;}.cls-49-4{opacity:0.4;}.cls-49-5{fill-opacity:0.4;stroke-miterlimit:10;stroke-width:0.75px;}""", """""" - ), + ) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 55e2c376b..991c8e8dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -1,257 +1,256 @@ -package com.vitorpamplona.amethyst.ui.navigation - -import android.util.Log -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -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.CircleShape -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.ScaffoldState -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.runtime.Composable -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.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavHostController -import coil.Coil -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.RoboHashCache -import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.service.NostrAccountDataSource -import com.vitorpamplona.amethyst.service.NostrChannelDataSource -import com.vitorpamplona.amethyst.service.NostrChatroomDataSource -import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource -import com.vitorpamplona.amethyst.service.NostrGlobalDataSource -import com.vitorpamplona.amethyst.service.NostrHomeDataSource -import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource -import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource -import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource -import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource -import com.vitorpamplona.amethyst.service.NostrThreadDataSource -import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource -import com.vitorpamplona.amethyst.service.relays.Client -import com.vitorpamplona.amethyst.service.relays.RelayPool -import com.vitorpamplona.amethyst.ui.actions.NewRelayListView -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy -import com.vitorpamplona.amethyst.ui.components.ResizeImage -import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import kotlinx.coroutines.launch - -@Composable -fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { - when (currentRoute(navController)) { - // Route.Profile.route -> TopBarWithBackButton(navController) - else -> MainTopBar(scaffoldState, accountViewModel) - } -} - -@Composable -fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - - val accountUserState by account.userProfile().live().metadata.observeAsState() - val accountUser = accountUserState?.user ?: return - - val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } - val connectedRelaysLiveData by relayViewModel.connectedRelaysLiveData.observeAsState() - val availableRelaysLiveData by relayViewModel.availableRelaysLiveData.observeAsState() - - val coroutineScope = rememberCoroutineScope() - - val context = LocalContext.current - val ctx = LocalContext.current.applicationContext - - var wantsToEditRelays by remember { - mutableStateOf(false) - } - - if (wantsToEditRelays) { - NewRelayListView({ wantsToEditRelays = false }, account) - } - - Column() { - TopAppBar( - elevation = 0.dp, - backgroundColor = Color(0xFFFFFF), - title = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box(Modifier) { - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(start = 0.dp, end = 20.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - IconButton( - onClick = { - Client.allSubscriptions().map { - "$it ${ - Client.getSubscriptionFilters(it) - .joinToString { it.filter.toJson() } - }" - }.forEach { - Log.d("STATE DUMP", it) - } - - NostrAccountDataSource.printCounter() - NostrChannelDataSource.printCounter() - NostrChatroomDataSource.printCounter() - NostrChatroomListDataSource.printCounter() - - NostrGlobalDataSource.printCounter() - NostrHomeDataSource.printCounter() - - NostrSingleEventDataSource.printCounter() - NostrSearchEventOrUserDataSource.printCounter() - NostrSingleChannelDataSource.printCounter() - NostrSingleUserDataSource.printCounter() - NostrThreadDataSource.printCounter() - - NostrUserProfileDataSource.printCounter() - - Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays()) - - val imageLoader = Coil.imageLoader(context) - Log.d("STATE DUMP", "Image Disk Cache ${(imageLoader.diskCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.diskCache?.maxSize ?: 0) / (1024 * 1024)} MB") - Log.d("STATE DUMP", "Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.memoryCache?.maxSize ?: 0) / (1024 * 1024)} MB") - - Log.d("STATE DUMP", "Notes: " + LocalCache.notes.filter { it.value.event != null }.size + "/" + LocalCache.notes.size) - Log.d("STATE DUMP", "Users: " + LocalCache.users.filter { it.value.info?.latestMetadata != null }.size + "/" + LocalCache.users.size) - } - ) { - Icon( - painter = painterResource(R.drawable.amethyst), - null, - modifier = Modifier.size(40.dp), - tint = Color.Unspecified - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), - horizontalAlignment = Alignment.End - - ) { - Row( - modifier = Modifier.fillMaxHeight(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - "${connectedRelaysLiveData ?: "--"}/${availableRelaysLiveData ?: "--"}", - color = if (connectedRelaysLiveData == 0) Color.Red else MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - style = MaterialTheme.typography.subtitle1, - modifier = Modifier.clickable( - onClick = { - wantsToEditRelays = true - } - ) - ) - } - } - } - } - }, - navigationIcon = { - IconButton( - onClick = { - coroutineScope.launch { - scaffoldState.drawerState.open() - } - }, - modifier = Modifier - ) { - AsyncImageProxy( - model = ResizeImage(accountUser.profilePicture(), 34.dp), - placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), - contentDescription = stringResource(id = R.string.profile_image), - modifier = Modifier - .width(34.dp) - .height(34.dp) - .clip(shape = CircleShape) - ) - } - }, - actions = { - IconButton( - onClick = { wantsToEditRelays = true }, - modifier = Modifier - ) { - Icon( - painter = painterResource(R.drawable.ic_trends), - null, - modifier = Modifier.size(24.dp), - tint = Color.Unspecified - ) - } - } - ) - Divider(thickness = 0.25.dp) - } -} - -@Composable -fun TopBarWithBackButton(navController: NavHostController) { - Column() { - TopAppBar( - elevation = 0.dp, - backgroundColor = Color(0xFFFFFF), - title = {}, - navigationIcon = { - IconButton( - onClick = { - navController.popBackStack() - }, - modifier = Modifier - ) { - Icon( - imageVector = Icons.Filled.ArrowBack, - null, - modifier = Modifier.size(28.dp), - tint = MaterialTheme.colors.primary - ) - } - }, - actions = {} - ) - Divider(thickness = 0.25.dp) - } -} +package com.vitorpamplona.amethyst.ui.navigation + +import android.util.Log +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +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.CircleShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ScaffoldState +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.runtime.Composable +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.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import coil.Coil +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.NostrAccountDataSource +import com.vitorpamplona.amethyst.service.NostrChannelDataSource +import com.vitorpamplona.amethyst.service.NostrChatroomDataSource +import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource +import com.vitorpamplona.amethyst.service.NostrGlobalDataSource +import com.vitorpamplona.amethyst.service.NostrHomeDataSource +import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource +import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource +import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource +import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource +import com.vitorpamplona.amethyst.service.NostrThreadDataSource +import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource +import com.vitorpamplona.amethyst.service.relays.Client +import com.vitorpamplona.amethyst.service.relays.RelayPool +import com.vitorpamplona.amethyst.ui.actions.NewRelayListView +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy +import com.vitorpamplona.amethyst.ui.components.ResizeImage +import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlinx.coroutines.launch + +@Composable +fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { + when (currentRoute(navController)) { + // Route.Profile.route -> TopBarWithBackButton(navController) + else -> MainTopBar(scaffoldState, accountViewModel) + } +} + +@Composable +fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = accountState?.account ?: return + + val accountUserState by account.userProfile().live().metadata.observeAsState() + val accountUser = accountUserState?.user ?: return + + val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } + val connectedRelaysLiveData by relayViewModel.connectedRelaysLiveData.observeAsState() + val availableRelaysLiveData by relayViewModel.availableRelaysLiveData.observeAsState() + + val coroutineScope = rememberCoroutineScope() + + val context = LocalContext.current + val ctx = LocalContext.current.applicationContext + + var wantsToEditRelays by remember { + mutableStateOf(false) + } + + if (wantsToEditRelays) { + NewRelayListView({ wantsToEditRelays = false }, account) + } + + Column() { + TopAppBar( + elevation = 0.dp, + backgroundColor = Color(0xFFFFFF), + title = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box(Modifier) { + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(start = 0.dp, end = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + IconButton( + onClick = { + Client.allSubscriptions().map { + "$it ${ + Client.getSubscriptionFilters(it) + .joinToString { it.filter.toJson() } + }" + }.forEach { + Log.d("STATE DUMP", it) + } + + NostrAccountDataSource.printCounter() + NostrChannelDataSource.printCounter() + NostrChatroomDataSource.printCounter() + NostrChatroomListDataSource.printCounter() + + NostrGlobalDataSource.printCounter() + NostrHomeDataSource.printCounter() + + NostrSingleEventDataSource.printCounter() + NostrSearchEventOrUserDataSource.printCounter() + NostrSingleChannelDataSource.printCounter() + NostrSingleUserDataSource.printCounter() + NostrThreadDataSource.printCounter() + + NostrUserProfileDataSource.printCounter() + + Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays()) + + val imageLoader = Coil.imageLoader(context) + Log.d("STATE DUMP", "Image Disk Cache ${(imageLoader.diskCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.diskCache?.maxSize ?: 0) / (1024 * 1024)} MB") + Log.d("STATE DUMP", "Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.memoryCache?.maxSize ?: 0) / (1024 * 1024)} MB") + + Log.d("STATE DUMP", "Notes: " + LocalCache.notes.filter { it.value.event != null }.size + "/" + LocalCache.notes.size) + Log.d("STATE DUMP", "Users: " + LocalCache.users.filter { it.value.info?.latestMetadata != null }.size + "/" + LocalCache.users.size) + } + ) { + Icon( + painter = painterResource(R.drawable.amethyst), + null, + modifier = Modifier.size(40.dp), + tint = Color.Unspecified + ) + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(), + horizontalAlignment = Alignment.End + + ) { + Row( + modifier = Modifier.fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "${connectedRelaysLiveData ?: "--"}/${availableRelaysLiveData ?: "--"}", + color = if (connectedRelaysLiveData == 0) Color.Red else MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.clickable( + onClick = { + wantsToEditRelays = true + } + ) + ) + } + } + } + } + }, + navigationIcon = { + IconButton( + onClick = { + coroutineScope.launch { + scaffoldState.drawerState.open() + } + }, + modifier = Modifier + ) { + AsyncUserImageProxy( + pubkeyHex = accountUser.pubkeyHex, + model = ResizeImage(accountUser.profilePicture(), 34.dp), +// placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), +// fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), +// error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), + contentDescription = stringResource(id = R.string.profile_image), + modifier = Modifier + .width(34.dp) + .height(34.dp) + .clip(shape = CircleShape) + ) + } + }, + actions = { + IconButton( + onClick = { wantsToEditRelays = true }, + modifier = Modifier + ) { + Icon( + painter = painterResource(R.drawable.ic_trends), + null, + modifier = Modifier.size(24.dp), + tint = Color.Unspecified + ) + } + } + ) + Divider(thickness = 0.25.dp) + } +} + +@Composable +fun TopBarWithBackButton(navController: NavHostController) { + Column() { + TopAppBar( + elevation = 0.dp, + backgroundColor = Color(0xFFFFFF), + title = {}, + navigationIcon = { + IconButton( + onClick = { + navController.popBackStack() + }, + modifier = Modifier + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + null, + modifier = Modifier.size(28.dp), + tint = MaterialTheme.colors.primary + ) + } + }, + actions = {} + ) + Divider(thickness = 0.25.dp) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 09c1aac42..b6ad544ef 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -35,7 +35,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.painter.BitmapPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -48,10 +47,9 @@ import androidx.navigation.NavHostController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -137,12 +135,13 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol } Column(modifier = modifier) { - AsyncImageProxy( + AsyncUserImageProxy( + pubkeyHex = accountUser.pubkeyHex, model = ResizeImage(accountUser.profilePicture(), 100.dp), contentDescription = stringResource(id = R.string.profile_image), - placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), +// placeholder = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), +// fallback = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), +// error = BitmapPainter(RoboHashCache.get(ctx, accountUser.pubkeyHex)), modifier = Modifier .width(100.dp) .height(100.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 25cae5b8d..b00a1d192 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -60,7 +60,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -195,11 +195,12 @@ fun ChatroomMessageCompose( horizontalArrangement = alignment, modifier = Modifier.padding(top = 5.dp) ) { - AsyncImageProxy( + AsyncUserImageProxy( + pubkeyHex = author.pubkeyHex, model = ResizeImage(author.profilePicture(), 25.dp), - placeholder = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), +// placeholder = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), +// fallback = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), +// error = BitmapPainter(RoboHashCache.get(context, author.pubkeyHex)), contentDescription = stringResource(id = R.string.profile_image), modifier = Modifier .width(25.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 1b836af46..078389a62 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer @@ -217,11 +217,12 @@ fun NoteCompose( .height(30.dp) .align(Alignment.BottomEnd) ) { - AsyncImageProxy( + AsyncUserImageProxy( + pubkeyHex = channel.idHex, model = ResizeImage(channel.profilePicture(), 30.dp), - placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)), - fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)), - error = BitmapPainter(RoboHashCache.get(context, channel.idHex)), +// placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)), +// fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)), +// error = BitmapPainter(RoboHashCache.get(context, channel.idHex)), contentDescription = stringResource(R.string.group_picture), modifier = Modifier .width(30.dp) @@ -723,12 +724,10 @@ fun UserPicture( .width(size) .height(size) ) { - AsyncImageProxy( + AsyncUserImageProxy( + pubkeyHex = user.pubkeyHex, model = ResizeImage(user.profilePicture(), size), contentDescription = stringResource(id = R.string.profile_image), - placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), modifier = pictureModifier .fillMaxSize(1f) .clip(shape = CircleShape) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt index 936d3f005..2884b931d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt @@ -1,157 +1,153 @@ -package com.vitorpamplona.amethyst.ui.navigation - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -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.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.RoboHashCache -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.ui.actions.CloseButton -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy -import com.vitorpamplona.amethyst.ui.components.ResizeImage -import com.vitorpamplona.amethyst.ui.qrcode.QrCodeScanner - -@Composable -fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) { - var presenting by remember { mutableStateOf(true) } - - val ctx = LocalContext.current.applicationContext - - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false) - ) { - Surface(modifier = Modifier.fillMaxSize()) { - Column( - modifier = Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - CloseButton(onCancel = onClose) - } - - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 10.dp), - verticalArrangement = Arrangement.SpaceBetween - ) { - if (presenting) { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp, vertical = 10.dp) - ) { - } - - Column(modifier = Modifier.fillMaxWidth()) { - Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { - AsyncImageProxy( - model = ResizeImage(user.profilePicture(), 100.dp), - placeholder = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(ctx, user.pubkeyHex)), - contentDescription = stringResource(R.string.profile_image), - modifier = Modifier - .width(100.dp) - .height(100.dp) - .clip(shape = CircleShape) - .border(3.dp, MaterialTheme.colors.background, CircleShape) - .background(MaterialTheme.colors.background) - ) - } - Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { - Text( - user.bestDisplayName() ?: "", - modifier = Modifier.padding(top = 7.dp), - fontWeight = FontWeight.Bold, - fontSize = 18.sp - ) - } - Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { - Text(" @${user.bestUsername()}", color = Color.LightGray) - } - - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 35.dp, vertical = 10.dp) - ) { - QrCodeDrawer("nostr:${user.pubkeyNpub()}") - } - } - - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 30.dp, vertical = 10.dp) - ) { - Button( - onClick = { presenting = false }, - shape = RoundedCornerShape(35.dp), - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults - .buttonColors( - backgroundColor = MaterialTheme.colors.primary - ) - ) { - Text(text = stringResource(R.string.scan_qr)) - } - } - } else { - QrCodeScanner { - if (it.isNullOrEmpty()) { - presenting = true - } else { - onScan(it) - } - } - } - } - } - } - } -} +package com.vitorpamplona.amethyst.ui.navigation + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +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.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.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.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.components.ResizeImage +import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy +import com.vitorpamplona.amethyst.ui.qrcode.QrCodeScanner + +@Composable +fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) { + var presenting by remember { mutableStateOf(true) } + + val ctx = LocalContext.current.applicationContext + + Dialog( + onDismissRequest = onClose, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Surface(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CloseButton(onCancel = onClose) + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 10.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + if (presenting) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp, vertical = 10.dp) + ) { + } + + Column(modifier = Modifier.fillMaxWidth()) { + Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { + RobohashAsyncImageProxy( + robot = user.pubkeyHex, + model = ResizeImage(user.profilePicture(), 100.dp), + contentDescription = stringResource(R.string.profile_image), + modifier = Modifier + .width(100.dp) + .height(100.dp) + .clip(shape = CircleShape) + .border(3.dp, MaterialTheme.colors.background, CircleShape) + .background(MaterialTheme.colors.background) + ) + } + Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { + Text( + user.bestDisplayName() ?: "", + modifier = Modifier.padding(top = 7.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + } + Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { + Text(" @${user.bestUsername()}", color = Color.LightGray) + } + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 35.dp, vertical = 10.dp) + ) { + QrCodeDrawer("nostr:${user.pubkeyNpub()}") + } + } + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp, vertical = 10.dp) + ) { + Button( + onClick = { presenting = false }, + shape = RoundedCornerShape(35.dp), + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults + .buttonColors( + backgroundColor = MaterialTheme.colors.primary + ) + ) { + Text(text = stringResource(R.string.scan_qr)) + } + } + } else { + QrCodeScanner { + if (it.isNullOrEmpty()) { + presenting = true + } else { + onScan(it) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 496666f7e..0dd85989b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -43,7 +43,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.painter.BitmapPainter import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner @@ -61,14 +60,13 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.RoboHashCache import com.vitorpamplona.amethyst.model.Account 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.PostButton -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route @@ -228,11 +226,9 @@ fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavCont Column() { Column(modifier = Modifier.padding(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - AsyncImageProxy( + AsyncUserImageProxy( + pubkeyHex = channel.idHex, model = ResizeImage(channel.profilePicture(), 35.dp), - placeholder = BitmapPainter(RoboHashCache.get(context, channel.idHex)), - fallback = BitmapPainter(RoboHashCache.get(context, channel.idHex)), - error = BitmapPainter(RoboHashCache.get(context, channel.idHex)), contentDescription = context.getString(R.string.profile_image), modifier = Modifier .width(35.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index c4736e760..6d1775651 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -1,245 +1,244 @@ -package com.vitorpamplona.amethyst.ui.screen.loggedIn - -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -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.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.LocalTextStyle -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Cancel -import androidx.compose.runtime.Composable -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.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.BitmapPainter -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 -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.text.style.TextDirection -import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.RoboHashCache -import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.NostrChatroomDataSource -import com.vitorpamplona.amethyst.ui.actions.PostButton -import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy -import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status -import com.vitorpamplona.amethyst.ui.components.ResizeImage -import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter -import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose -import com.vitorpamplona.amethyst.ui.note.UsernameDisplay -import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView -import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel - -@Composable -fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account - - if (account != null && userId != null) { - val newPost = remember { mutableStateOf(TextFieldValue("")) } - val replyTo = remember { mutableStateOf(null) } - - ChatroomFeedFilter.loadMessagesBetween(account, userId) - NostrChatroomDataSource.loadMessagesBetween(account, userId) - - val feedViewModel: NostrChatRoomFeedViewModel = viewModel() - val lifeCycleOwner = LocalLifecycleOwner.current - - LaunchedEffect(userId) { - feedViewModel.refresh() - } - - DisposableEffect(userId) { - val observer = LifecycleEventObserver { source, event -> - if (event == Lifecycle.Event.ON_RESUME) { - println("Private Message Start") - NostrChatroomDataSource.start() - feedViewModel.refresh() - } - if (event == Lifecycle.Event.ON_PAUSE) { - println("Private Message Stop") - NostrChatroomDataSource.stop() - } - } - - lifeCycleOwner.lifecycle.addObserver(observer) - onDispose { - lifeCycleOwner.lifecycle.removeObserver(observer) - } - } - - Column(Modifier.fillMaxHeight()) { - NostrChatroomDataSource.withUser?.let { - ChatroomHeader( - it, - accountViewModel = accountViewModel, - navController = navController - ) - } - - Column( - modifier = Modifier - .fillMaxHeight() - .padding(vertical = 0.dp) - .weight(1f, true) - ) { - ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/$userId") { - replyTo.value = it - } - } - - Spacer(modifier = Modifier.height(10.dp)) - - Row(Modifier.padding(horizontal = 10.dp).animateContentSize(), verticalAlignment = Alignment.CenterVertically) { - val replyingNote = replyTo.value - if (replyingNote != null) { - Column(Modifier.weight(1f)) { - ChatroomMessageCompose( - baseNote = replyingNote, - null, - innerQuote = true, - accountViewModel = accountViewModel, - navController = navController, - onWantsToReply = { - replyTo.value = it - } - ) - } - - Column(Modifier.padding(end = 10.dp)) { - IconButton( - modifier = Modifier.size(30.dp), - onClick = { replyTo.value = null } - ) { - Icon( - imageVector = Icons.Default.Cancel, - null, - modifier = Modifier.padding(end = 5.dp).size(30.dp), - tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - } - } - } - } - - // LAST ROW - Row( - modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - TextField( - value = newPost.value, - onValueChange = { newPost.value = it }, - keyboardOptions = KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.Sentences - ), - modifier = Modifier.weight(1f, true), - shape = RoundedCornerShape(25.dp), - placeholder = { - Text( - text = stringResource(id = R.string.reply_here), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - }, - textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), - trailingIcon = { - PostButton( - onPost = { - account.sendPrivateMeesage(newPost.value.text, userId, replyTo.value) - newPost.value = TextFieldValue("") - replyTo.value = null - feedViewModel.refresh() // Don't wait a full second before updating - }, - newPost.value.text.isNotBlank(), - modifier = Modifier.padding(end = 10.dp) - ) - }, - colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ) - ) - } - } - } -} - -@Composable -fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { - val ctx = LocalContext.current.applicationContext - - Column( - modifier = Modifier.clickable( - onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } - ) - ) { - Column(modifier = Modifier.padding(12.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - val authorState by baseUser.live().metadata.observeAsState() - val author = authorState?.user!! - - AsyncImageProxy( - model = ResizeImage(author.profilePicture(), 35.dp), - placeholder = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), - fallback = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), - error = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), - contentDescription = stringResource(id = R.string.profile_image), - modifier = Modifier - .width(35.dp) - .height(35.dp) - .clip(shape = CircleShape) - ) - - Column(modifier = Modifier.padding(start = 10.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - UsernameDisplay(baseUser) - } - - Row(verticalAlignment = Alignment.CenterVertically) { - ObserveDisplayNip05Status(baseUser) - } - } - } - } - - Divider( - modifier = Modifier.padding(start = 12.dp, end = 12.dp), - thickness = 0.25.dp - ) - } -} +package com.vitorpamplona.amethyst.ui.screen.loggedIn + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +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.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cancel +import androidx.compose.runtime.Composable +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.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 +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +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.PostButton +import com.vitorpamplona.amethyst.ui.components.AsyncUserImageProxy +import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status +import com.vitorpamplona.amethyst.ui.components.ResizeImage +import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter +import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose +import com.vitorpamplona.amethyst.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView +import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel + +@Composable +fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = accountState?.account + + if (account != null && userId != null) { + val newPost = remember { mutableStateOf(TextFieldValue("")) } + val replyTo = remember { mutableStateOf(null) } + + ChatroomFeedFilter.loadMessagesBetween(account, userId) + NostrChatroomDataSource.loadMessagesBetween(account, userId) + + val feedViewModel: NostrChatRoomFeedViewModel = viewModel() + val lifeCycleOwner = LocalLifecycleOwner.current + + LaunchedEffect(userId) { + feedViewModel.refresh() + } + + DisposableEffect(userId) { + val observer = LifecycleEventObserver { source, event -> + if (event == Lifecycle.Event.ON_RESUME) { + println("Private Message Start") + NostrChatroomDataSource.start() + feedViewModel.refresh() + } + if (event == Lifecycle.Event.ON_PAUSE) { + println("Private Message Stop") + NostrChatroomDataSource.stop() + } + } + + lifeCycleOwner.lifecycle.addObserver(observer) + onDispose { + lifeCycleOwner.lifecycle.removeObserver(observer) + } + } + + Column(Modifier.fillMaxHeight()) { + NostrChatroomDataSource.withUser?.let { + ChatroomHeader( + it, + accountViewModel = accountViewModel, + navController = navController + ) + } + + Column( + modifier = Modifier + .fillMaxHeight() + .padding(vertical = 0.dp) + .weight(1f, true) + ) { + ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/$userId") { + replyTo.value = it + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + Row(Modifier.padding(horizontal = 10.dp).animateContentSize(), verticalAlignment = Alignment.CenterVertically) { + val replyingNote = replyTo.value + if (replyingNote != null) { + Column(Modifier.weight(1f)) { + ChatroomMessageCompose( + baseNote = replyingNote, + null, + innerQuote = true, + accountViewModel = accountViewModel, + navController = navController, + onWantsToReply = { + replyTo.value = it + } + ) + } + + Column(Modifier.padding(end = 10.dp)) { + IconButton( + modifier = Modifier.size(30.dp), + onClick = { replyTo.value = null } + ) { + Icon( + imageVector = Icons.Default.Cancel, + null, + modifier = Modifier.padding(end = 5.dp).size(30.dp), + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + } + } + } + } + + // LAST ROW + Row( + modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + TextField( + value = newPost.value, + onValueChange = { newPost.value = it }, + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences + ), + modifier = Modifier.weight(1f, true), + shape = RoundedCornerShape(25.dp), + placeholder = { + Text( + text = stringResource(id = R.string.reply_here), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + }, + textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), + trailingIcon = { + PostButton( + onPost = { + account.sendPrivateMeesage(newPost.value.text, userId, replyTo.value) + newPost.value = TextFieldValue("") + replyTo.value = null + feedViewModel.refresh() // Don't wait a full second before updating + }, + newPost.value.text.isNotBlank(), + modifier = Modifier.padding(end = 10.dp) + ) + }, + colors = TextFieldDefaults.textFieldColors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ) + ) + } + } + } +} + +@Composable +fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { + val ctx = LocalContext.current.applicationContext + + Column( + modifier = Modifier.clickable( + onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } + ) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + val authorState by baseUser.live().metadata.observeAsState() + val author = authorState?.user!! + + AsyncUserImageProxy( + pubkeyHex = author.pubkeyHex, + model = ResizeImage(author.profilePicture(), 35.dp), +// placeholder = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), +// fallback = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), +// error = BitmapPainter(RoboHashCache.get(ctx, author.pubkeyHex)), + contentDescription = stringResource(id = R.string.profile_image), + modifier = Modifier + .width(35.dp) + .height(35.dp) + .clip(shape = CircleShape) + ) + + Column(modifier = Modifier.padding(start = 10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + UsernameDisplay(baseUser) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + ObserveDisplayNip05Status(baseUser) + } + } + } + } + + Divider( + modifier = Modifier.padding(start = 12.dp, end = 12.dp), + thickness = 0.25.dp + ) + } +}