feat: implement avatar animation and enhance image sharing functionality

This commit is contained in:
2026-02-10 00:06:41 +05:00
parent 3c37a3b0e5
commit bbaa04cda5
10 changed files with 523 additions and 168 deletions

View File

@@ -17,6 +17,7 @@ import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -26,6 +27,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -34,7 +37,6 @@ import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -160,12 +162,13 @@ private fun calculateAverageColor(bitmap: android.graphics.Bitmap): Color {
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Composable
fun OtherProfileScreen(
user: SearchUser,
isDarkTheme: Boolean,
onBack: () -> Unit,
onSwipeBackEnabledChanged: (Boolean) -> Unit = {},
avatarRepository: AvatarRepository? = null,
currentUserPublicKey: String = "",
currentUserPrivateKey: String = "",
@@ -173,9 +176,19 @@ fun OtherProfileScreen(
) {
var isBlocked by remember { mutableStateOf(false) }
var showAvatarMenu by remember { mutableStateOf(false) }
var selectedTab by rememberSaveable { mutableStateOf(OtherProfileTab.MEDIA) }
var showImageViewer by remember { mutableStateOf(false) }
var imageViewerInitialIndex by remember { mutableIntStateOf(0) }
val tabs = remember { OtherProfileTab.entries }
val pagerState = rememberPagerState(initialPage = 0, pageCount = { tabs.size })
val selectedTab =
tabs.getOrElse(pagerState.currentPage.coerceIn(0, tabs.lastIndex)) {
OtherProfileTab.MEDIA
}
val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp
val sharedPagerMinHeight = (screenHeightDp * 0.45f).coerceAtLeast(240.dp)
LaunchedEffect(selectedTab) {
onSwipeBackEnabledChanged(selectedTab == OtherProfileTab.MEDIA)
}
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val avatarColors = getAvatarColor(user.publicKey, isDarkTheme)
@@ -535,62 +548,78 @@ fun OtherProfileScreen(
OtherProfileSharedTabs(
selectedTab = selectedTab,
onTabSelected = { selectedTab = it },
onTabSelected = { tab ->
val targetPage = tab.ordinal
if (pagerState.currentPage != targetPage) {
coroutineScope.launch {
pagerState.animateScrollToPage(targetPage)
}
}
},
isDarkTheme = isDarkTheme
)
Spacer(modifier = Modifier.height(10.dp))
OtherProfileSharedTabContent(
selectedTab = selectedTab,
sharedContent = sharedContent,
isDarkTheme = isDarkTheme,
accountPublicKey = activeAccountPublicKey,
accountPrivateKey = activeAccountPrivateKey,
onMediaClick = { index ->
imageViewerInitialIndex = index
showImageViewer = true
},
onFileClick = { file ->
val opened = openSharedFile(context, file)
if (!opened) {
Toast.makeText(
context,
"File is not available on this device",
Toast.LENGTH_SHORT
)
.show()
}
},
onLinkClick = { link ->
val normalizedLink =
if (link.startsWith("http://", ignoreCase = true) ||
link.startsWith("https://", ignoreCase = true)
) {
link
} else {
"https://$link"
}
val opened =
runCatching {
context.startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(normalizedLink)
)
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxWidth().heightIn(min = sharedPagerMinHeight),
beyondBoundsPageCount = 0,
verticalAlignment = Alignment.Top
) { page ->
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopStart) {
OtherProfileSharedTabContent(
selectedTab = tabs[page],
sharedContent = sharedContent,
isDarkTheme = isDarkTheme,
accountPublicKey = activeAccountPublicKey,
accountPrivateKey = activeAccountPrivateKey,
onMediaClick = { index ->
imageViewerInitialIndex = index
showImageViewer = true
},
onFileClick = { file ->
val opened = openSharedFile(context, file)
if (!opened) {
Toast.makeText(
context,
"File is not available on this device",
Toast.LENGTH_SHORT
)
.show()
}
},
onLinkClick = { link ->
val normalizedLink =
if (link.startsWith("http://", ignoreCase = true) ||
link.startsWith("https://", ignoreCase = true)
) {
link
} else {
"https://$link"
}
.isSuccess
if (!opened) {
Toast.makeText(
context,
"Unable to open this link",
Toast.LENGTH_SHORT
)
.show()
}
}
)
val opened =
runCatching {
context.startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(normalizedLink)
)
)
}
.isSuccess
if (!opened) {
Toast.makeText(
context,
"Unable to open this link",
Toast.LENGTH_SHORT
)
.show()
}
}
)
}
}
Spacer(modifier = Modifier.height(32.dp))
}