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 }