FriendTrackerManagerRoot.kt (12362B) - raw
1 package me.rhunk.snapenhance.ui.manager.pages.tracker 2 3 import androidx.compose.foundation.BorderStroke 4 import androidx.compose.foundation.ExperimentalFoundationApi 5 import androidx.compose.foundation.background 6 import androidx.compose.foundation.border 7 import androidx.compose.foundation.clickable 8 import androidx.compose.foundation.layout.* 9 import androidx.compose.foundation.lazy.LazyColumn 10 import androidx.compose.foundation.lazy.items 11 import androidx.compose.foundation.pager.HorizontalPager 12 import androidx.compose.foundation.pager.rememberPagerState 13 import androidx.compose.foundation.shape.CircleShape 14 import androidx.compose.foundation.shape.RoundedCornerShape 15 import androidx.compose.material.icons.Icons 16 import androidx.compose.material.icons.filled.Add 17 import androidx.compose.material.icons.filled.DeleteOutline 18 import androidx.compose.material.icons.filled.SaveAlt 19 import androidx.compose.material3.* 20 import androidx.compose.runtime.Composable 21 import androidx.compose.runtime.getValue 22 import androidx.compose.runtime.mutableIntStateOf 23 import androidx.compose.runtime.rememberCoroutineScope 24 import androidx.compose.runtime.setValue 25 import androidx.compose.ui.Alignment 26 import androidx.compose.ui.Modifier 27 import androidx.compose.ui.draw.clip 28 import androidx.compose.ui.graphics.Color 29 import androidx.compose.ui.text.font.FontWeight 30 import androidx.compose.ui.text.style.TextAlign 31 import androidx.compose.ui.text.style.TextOverflow 32 import androidx.compose.ui.unit.dp 33 import androidx.compose.ui.unit.sp 34 import androidx.navigation.NavBackStackEntry 35 import kotlinx.coroutines.launch 36 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState 37 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList 38 import me.rhunk.snapenhance.common.ui.rememberAsyncUpdateDispatcher 39 import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie 40 import me.rhunk.snapenhance.storage.* 41 import me.rhunk.snapenhance.ui.manager.Routes 42 import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper 43 import me.rhunk.snapenhance.ui.util.coil.BitmojiImage 44 import me.rhunk.snapenhance.ui.util.pagerTabIndicatorOffset 45 46 47 @OptIn(ExperimentalFoundationApi::class) 48 class FriendTrackerManagerRoot : Routes.Route() { 49 enum class FilterType { 50 CONVERSATION, USERNAME, EVENT 51 } 52 53 private val titles = listOf("Logs", "Rules") 54 private var currentPage by mutableIntStateOf(0) 55 private lateinit var logDeleteAction : () -> Unit 56 private lateinit var exportAction : () -> Unit 57 58 private lateinit var activityLauncherHelper: ActivityLauncherHelper 59 60 override val init: () -> Unit = { 61 activityLauncherHelper = ActivityLauncherHelper(context.activity!!) 62 } 63 64 override val floatingActionButton: @Composable () -> Unit = { 65 when (currentPage) { 66 0 -> { 67 Column( 68 verticalArrangement = Arrangement.spacedBy(6.dp), 69 ) { 70 ExtendedFloatingActionButton( 71 icon = { Icon(Icons.Default.SaveAlt, contentDescription = "Export") }, 72 expanded = true, 73 text = { Text("Export") }, 74 onClick = { 75 context.coroutineScope.launch { exportAction() } 76 } 77 ) 78 ExtendedFloatingActionButton( 79 icon = { Icon(Icons.Default.DeleteOutline, contentDescription = "Delete") }, 80 expanded = true, 81 text = { Text("Delete") }, 82 onClick = { 83 context.coroutineScope.launch { logDeleteAction() } 84 } 85 ) 86 } 87 } 88 1 -> { 89 ExtendedFloatingActionButton( 90 icon = { Icon(Icons.Default.Add, contentDescription = "Add Rule") }, 91 expanded = true, 92 text = { Text("Add Rule") }, 93 onClick = { routes.editRule.navigate() } 94 ) 95 } 96 } 97 } 98 99 @Composable 100 private fun ConfigRulesTab() { 101 val updateRules = rememberAsyncUpdateDispatcher() 102 val rules = rememberAsyncMutableStateList(defaultValue = listOf(), updateDispatcher = updateRules) { 103 context.database.getTrackerRulesDesc() 104 } 105 106 Column( 107 modifier = Modifier.fillMaxSize() 108 ) { 109 LazyColumn( 110 modifier = Modifier.weight(1f) 111 ) { 112 item { 113 if (rules.isEmpty()) { 114 Text("No rules found", modifier = Modifier 115 .padding(16.dp) 116 .fillMaxWidth(), textAlign = TextAlign.Center, fontWeight = FontWeight.Light) 117 } 118 } 119 items(rules, key = { it.id }) { rule -> 120 val ruleName by rememberAsyncMutableState(defaultValue = rule.name) { 121 context.database.getTrackerRule(rule.id)?.name ?: "(empty)" 122 } 123 val eventCount by rememberAsyncMutableState(defaultValue = 0) { 124 context.database.getTrackerEvents(rule.id).size 125 } 126 val scopeCount by rememberAsyncMutableState(defaultValue = 0) { 127 context.database.getRuleTrackerScopes(rule.id).size 128 } 129 var enabled by rememberAsyncMutableState(defaultValue = rule.enabled) { 130 context.database.getTrackerRule(rule.id)?.enabled ?: false 131 } 132 133 ElevatedCard( 134 modifier = Modifier 135 .fillMaxWidth() 136 .clickable { 137 routes.editRule.navigate { 138 this["rule_id"] = rule.id.toString() 139 } 140 } 141 .padding(5.dp) 142 ) { 143 Row( 144 modifier = Modifier 145 .fillMaxWidth(), 146 verticalAlignment = Alignment.CenterVertically 147 ) { 148 Column( 149 modifier = Modifier 150 .fillMaxWidth() 151 .padding(16.dp) 152 .weight(1f), 153 verticalArrangement = Arrangement.spacedBy(2.dp) 154 ) { 155 Text(ruleName, fontSize = 20.sp, fontWeight = FontWeight.Bold) 156 Text(buildString { 157 append(eventCount) 158 append(" events") 159 if (scopeCount > 0) { 160 append(", ") 161 append(scopeCount) 162 append(" scopes") 163 } 164 }, fontSize = 13.sp, fontWeight = FontWeight.Light) 165 } 166 167 Row( 168 modifier = Modifier.padding(10.dp), 169 horizontalArrangement = Arrangement.spacedBy(10.dp) 170 ) { 171 val scopesBitmoji = rememberAsyncMutableStateList(defaultValue = emptyList()) { 172 context.database.getRuleTrackerScopes(rule.id, limit = 10).mapNotNull { 173 context.database.getFriendInfo(it.key)?.let { friend -> 174 friend.selfieId to friend.bitmojiId 175 } 176 }.take(3) 177 } 178 179 Row { 180 scopesBitmoji.forEachIndexed { index, friend -> 181 Box( 182 modifier = Modifier 183 .offset(x = (-index * 20).dp + (scopesBitmoji.size * 20).dp - 20.dp) 184 ) { 185 BitmojiImage( 186 size = 50, 187 modifier = Modifier 188 .border( 189 BorderStroke(1.dp, Color.White), 190 CircleShape 191 ) 192 .background(Color.White, CircleShape) 193 .clip(CircleShape), 194 context = context, 195 url = BitmojiSelfie.getBitmojiSelfie(friend.first, friend.second, BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D), 196 ) 197 } 198 } 199 } 200 201 Box(modifier = Modifier 202 .padding(start = 5.dp, end = 5.dp) 203 .height(50.dp) 204 .width(1.dp) 205 .background( 206 color = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f), 207 shape = RoundedCornerShape(5.dp) 208 ) 209 ) 210 211 Switch( 212 checked = enabled, 213 onCheckedChange = { 214 enabled = it 215 context.database.setTrackerRuleState(rule.id, it) 216 } 217 ) 218 } 219 } 220 } 221 } 222 } 223 } 224 } 225 226 227 @OptIn(ExperimentalFoundationApi::class) 228 override val content: @Composable (NavBackStackEntry) -> Unit = { 229 val coroutineScope = rememberCoroutineScope() 230 val pagerState = rememberPagerState { titles.size } 231 currentPage = pagerState.currentPage 232 233 Column { 234 TabRow(selectedTabIndex = pagerState.currentPage, indicator = { tabPositions -> 235 TabRowDefaults.SecondaryIndicator( 236 Modifier.pagerTabIndicatorOffset( 237 pagerState = pagerState, 238 tabPositions = tabPositions 239 ) 240 ) 241 }) { 242 titles.forEachIndexed { index, title -> 243 Tab( 244 selected = pagerState.currentPage == index, 245 onClick = { 246 coroutineScope.launch { 247 pagerState.animateScrollToPage(index) 248 } 249 }, 250 text = { 251 Text( 252 text = title, 253 maxLines = 2, 254 overflow = TextOverflow.Ellipsis 255 ) 256 } 257 ) 258 } 259 } 260 261 HorizontalPager( 262 modifier = Modifier.weight(1f), 263 state = pagerState 264 ) { page -> 265 when (page) { 266 0 -> LogsTab( 267 context = context, 268 activityLauncherHelper = activityLauncherHelper, 269 deleteAction = { logDeleteAction = it }, 270 exportAction = { exportAction = it } 271 ) 272 1 -> ConfigRulesTab() 273 } 274 } 275 } 276 } 277 }