SocialRootSection.kt (12962B) - raw
1 package me.rhunk.snapenhance.ui.manager.pages.social 2 3 import androidx.compose.foundation.ExperimentalFoundationApi 4 import androidx.compose.foundation.clickable 5 import androidx.compose.foundation.layout.* 6 import androidx.compose.foundation.lazy.LazyColumn 7 import androidx.compose.foundation.pager.HorizontalPager 8 import androidx.compose.foundation.pager.rememberPagerState 9 import androidx.compose.foundation.shape.RoundedCornerShape 10 import androidx.compose.material.icons.Icons 11 import androidx.compose.material.icons.filled.RemoveRedEye 12 import androidx.compose.material.icons.rounded.Add 13 import androidx.compose.material3.* 14 import androidx.compose.runtime.* 15 import androidx.compose.ui.Alignment 16 import androidx.compose.ui.Modifier 17 import androidx.compose.ui.graphics.vector.ImageVector 18 import androidx.compose.ui.res.vectorResource 19 import androidx.compose.ui.text.font.FontWeight 20 import androidx.compose.ui.text.style.TextAlign 21 import androidx.compose.ui.text.style.TextOverflow 22 import androidx.compose.ui.unit.dp 23 import androidx.compose.ui.unit.sp 24 import androidx.navigation.NavBackStackEntry 25 import kotlinx.coroutines.launch 26 import me.rhunk.snapenhance.R 27 import me.rhunk.snapenhance.common.data.MessagingFriendInfo 28 import me.rhunk.snapenhance.common.data.MessagingGroupInfo 29 import me.rhunk.snapenhance.common.data.SocialScope 30 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState 31 import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie 32 import me.rhunk.snapenhance.storage.* 33 import me.rhunk.snapenhance.ui.manager.Routes 34 import me.rhunk.snapenhance.ui.util.coil.BitmojiImage 35 import me.rhunk.snapenhance.ui.util.pagerTabIndicatorOffset 36 37 class SocialRootSection : Routes.Route() { 38 private var friendList: List<MessagingFriendInfo> by mutableStateOf(emptyList()) 39 private var groupList: List<MessagingGroupInfo> by mutableStateOf(emptyList()) 40 41 private fun updateScopeLists() { 42 context.coroutineScope.launch { 43 friendList = context.database.getFriends(descOrder = true) 44 groupList = context.database.getGroups() 45 } 46 } 47 48 @Composable 49 private fun ScopeList(scope: SocialScope) { 50 val remainingHours = remember { context.config.root.streaksReminder.remainingHours.get() } 51 52 LazyColumn( 53 modifier = Modifier 54 .fillMaxSize(), 55 contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp, start = 8.dp, end = 8.dp), 56 verticalArrangement = Arrangement.spacedBy(8.dp) 57 ) { 58 //check if scope list is empty 59 val listSize = when (scope) { 60 SocialScope.GROUP -> groupList.size 61 SocialScope.FRIEND -> friendList.size 62 } 63 64 if (listSize == 0) { 65 item { 66 Text( 67 text = translation["empty_hint"], modifier = Modifier 68 .fillMaxWidth() 69 .padding(10.dp), textAlign = TextAlign.Center 70 ) 71 } 72 } 73 74 items(listSize) { index -> 75 val id = when (scope) { 76 SocialScope.GROUP -> groupList[index].conversationId 77 SocialScope.FRIEND -> friendList[index].userId 78 } 79 80 ElevatedCard( 81 modifier = Modifier 82 .fillMaxWidth() 83 .height(70.dp), 84 onClick = { 85 routes.manageScope.navigate { 86 put("id", id) 87 put("scope", scope.key) 88 } 89 } 90 ) { 91 Row( 92 modifier = Modifier 93 .padding(10.dp) 94 .fillMaxSize(), 95 verticalAlignment = Alignment.CenterVertically 96 ) { 97 when (scope) { 98 SocialScope.GROUP -> { 99 val group = groupList[index] 100 Column( 101 modifier = Modifier 102 .padding(start = 7.dp) 103 .fillMaxWidth() 104 .weight(1f) 105 ) { 106 Text( 107 text = group.name, 108 maxLines = 1, 109 fontWeight = FontWeight.Bold 110 ) 111 } 112 } 113 114 SocialScope.FRIEND -> { 115 val friend = friendList[index] 116 val streaks by rememberAsyncMutableState(defaultValue = friend.streaks) { 117 context.database.getFriendStreaks(friend.userId) 118 } 119 120 BitmojiImage( 121 context = context, 122 url = BitmojiSelfie.getBitmojiSelfie( 123 friend.selfieId, 124 friend.bitmojiId, 125 BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D 126 ) 127 ) 128 129 Column( 130 modifier = Modifier 131 .padding(start = 7.dp) 132 .fillMaxWidth() 133 .weight(1f) 134 ) { 135 Text( 136 text = friend.displayName ?: friend.mutableUsername, 137 maxLines = 1, 138 fontWeight = FontWeight.Bold 139 ) 140 Text( 141 text = friend.mutableUsername, 142 maxLines = 1, 143 fontSize = 12.sp, 144 fontWeight = FontWeight.Light 145 ) 146 } 147 Row(verticalAlignment = Alignment.CenterVertically) { 148 streaks?.takeIf { it.notify }?.let { streaks -> 149 Icon( 150 imageVector = ImageVector.vectorResource(id = R.drawable.streak_icon), 151 contentDescription = null, 152 modifier = Modifier.height(40.dp), 153 tint = if (streaks.isAboutToExpire(remainingHours)) 154 MaterialTheme.colorScheme.error 155 else MaterialTheme.colorScheme.primary 156 ) 157 Text( 158 text = translation.format( 159 "streaks_expiration_short", 160 "hours" to (((streaks.expirationTimestamp - System.currentTimeMillis()) / 3600000).toInt().takeIf { it > 0 } ?: 0) 161 .toString() 162 ), 163 maxLines = 1, 164 fontWeight = FontWeight.Bold 165 ) 166 } 167 } 168 } 169 } 170 171 FilledIconButton(onClick = { 172 routes.messagingPreview.navigate { 173 put("id", id) 174 put("scope", scope.key) 175 } 176 }) { 177 Icon(imageVector = Icons.Filled.RemoveRedEye, contentDescription = null) 178 } 179 } 180 } 181 } 182 } 183 } 184 185 @OptIn(ExperimentalFoundationApi::class) 186 override val content: @Composable (NavBackStackEntry) -> Unit = { 187 val titles = remember { 188 listOf(translation["friends_tab"], translation["groups_tab"]) 189 } 190 val coroutineScope = rememberCoroutineScope() 191 val pagerState = rememberPagerState { titles.size } 192 var addFriendDialog by remember { mutableStateOf(null as AddFriendDialog?) } 193 194 if (addFriendDialog != null) { 195 addFriendDialog?.Content { 196 addFriendDialog = null 197 } 198 DisposableEffect(Unit) { 199 onDispose { 200 updateScopeLists() 201 } 202 } 203 } 204 205 LaunchedEffect(Unit) { 206 updateScopeLists() 207 } 208 209 Scaffold( 210 floatingActionButton = { 211 FloatingActionButton( 212 onClick = { 213 addFriendDialog = AddFriendDialog( 214 context, 215 AddFriendDialog.Actions( 216 onFriendState = { friend, state -> 217 if (state) { 218 context.bridgeService?.triggerScopeSync(SocialScope.FRIEND, friend.userId) 219 } else { 220 context.database.deleteFriend(friend.userId) 221 } 222 }, 223 onGroupState = { group, state -> 224 if (state) { 225 context.bridgeService?.triggerScopeSync(SocialScope.GROUP, group.conversationId) 226 } else { 227 context.database.deleteGroup(group.conversationId) 228 } 229 }, 230 getFriendState = { friend -> context.database.getFriendInfo(friend.userId) != null }, 231 getGroupState = { group -> context.database.getGroupInfo(group.conversationId) != null } 232 ), 233 pinnedIds = (friendList.map { it.userId } + groupList.map { it.conversationId }).reversed(), 234 ) 235 }, 236 modifier = Modifier.padding(10.dp), 237 containerColor = MaterialTheme.colorScheme.primary, 238 contentColor = MaterialTheme.colorScheme.onPrimary, 239 shape = RoundedCornerShape(16.dp), 240 ) { 241 Icon( 242 imageVector = Icons.Rounded.Add, 243 contentDescription = null 244 ) 245 } 246 } 247 ) { paddingValues -> 248 Column(modifier = Modifier.padding(paddingValues)) { 249 TabRow(selectedTabIndex = pagerState.currentPage, indicator = { tabPositions -> 250 TabRowDefaults.SecondaryIndicator( 251 Modifier.pagerTabIndicatorOffset( 252 pagerState = pagerState, 253 tabPositions = tabPositions 254 ) 255 ) 256 }) { 257 titles.forEachIndexed { index, title -> 258 Tab( 259 selected = pagerState.currentPage == index, 260 onClick = { 261 coroutineScope.launch { 262 pagerState.animateScrollToPage(index) 263 } 264 }, 265 text = { 266 Text( 267 text = title, 268 maxLines = 2, 269 overflow = TextOverflow.Ellipsis 270 ) 271 } 272 ) 273 } 274 } 275 276 HorizontalPager( 277 modifier = Modifier.padding(paddingValues), 278 state = pagerState 279 ) { page -> 280 when (page) { 281 0 -> ScopeList(SocialScope.FRIEND) 282 1 -> ScopeList(SocialScope.GROUP) 283 } 284 } 285 } 286 } 287 } 288 }