package com.vgmlr.shim import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.enableEdgeToEdge import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import java.util.concurrent.TimeUnit import androidx.compose.foundation.background import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.FileOpen import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.DataArray import androidx.compose.material.icons.automirrored.outlined.ExitToApp import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.res.colorResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.text.input.TextFieldValue class MainActivity : ComponentActivity() { private lateinit var db: ShimDatabase private lateinit var themeManager: ShimThemeManager private var shimListState = mutableStateOf>(emptyList()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.setBackgroundDrawableResource(R.color.background_color) db = ShimDatabase(this) themeManager = ShimThemeManager(this) enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT)) val backupRequest = PeriodicWorkRequestBuilder(6, TimeUnit.HOURS).build() WorkManager.getInstance(this).enqueueUniquePeriodicWork( "ShimDailyBackup", ExistingPeriodicWorkPolicy.REPLACE, backupRequest ) setContent { ShimTheme(themeManager) { MainScreen() } } } override fun onResume() { super.onResume() themeManager.refresh() refreshList() } private fun refreshList() { shimListState.value = db.getAllShims() } @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun MainScreen() { val lifecycleOwner = LocalLifecycleOwner.current var searchQuery by remember { mutableStateOf("") } var isSearchExpanded by remember { mutableStateOf(false) } var showDataDialog by remember { mutableStateOf(false) } var showTagsDialog by remember { mutableStateOf(false) } var showVersionDialog by remember { mutableStateOf(false) } var shimText by remember { mutableStateOf("") } var hashtagText by remember { mutableStateOf(TextFieldValue("")) } val allTags = remember(shimListState.value) { shimListState.value.flatMap { it.shimhash.split(" ").filter { tag -> tag.isNotBlank() } }.distinct().sorted() } val tagAssociations = remember(shimListState.value) { val associations = mutableMapOf>() shimListState.value.forEach { shim -> val tags = shim.shimhash.split(" ").filter { it.isNotBlank() } tags.forEach { tag -> val associated = associations.getOrPut(tag) { mutableSetOf() } associated.addAll(tags.filter { it != tag }) } } associations.mapValues { it.value.sorted() } } var selectedShim by remember { mutableStateOf(null) } val clipboardManager = LocalClipboardManager.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { isSearchExpanded = false searchQuery = "" selectedShim = null } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } val exportLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/x-sqlite3")) { uri -> uri?.let { val dbFile = getDatabasePath("shim.db") contentResolver.openOutputStream(it)?.use { output -> dbFile.inputStream().use { input -> input.copyTo(output) } } } } val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { db.importDatabase(this@MainActivity, it) refreshList() } } Box(modifier = Modifier.fillMaxSize()) { Scaffold( topBar = { Column { TopAppBar( title = { Box(Modifier.fillMaxWidth().padding(end = 16.dp), contentAlignment = Alignment.CenterEnd) { Text("shim", fontSize = 18.sp, color = colorResource(id = R.color.title_color)) } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = colorResource(id = R.color.primary_color)), actions = { if (selectedShim == null) { IconButton(onClick = { isSearchExpanded = false searchQuery = "" refreshList() }) { Icon(Icons.Default.Add, contentDescription = "Add", tint = colorResource(id = R.color.title_color)) } IconButton(onClick = { showTagsDialog = true }) { Icon(Icons.Default.Tag, contentDescription = "Tags", tint = colorResource(id = R.color.title_color)) } } else { IconButton(onClick = { clipboardManager.setText(AnnotatedString(selectedShim!!.shimtext)) selectedShim = null }) { Icon(Icons.Default.ContentCopy, contentDescription = "Copy", tint = colorResource(id = R.color.title_color)) } IconButton(onClick = { val text = selectedShim!!.shimtext clipboardManager.setText(AnnotatedString(text)) val intent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, text) } startActivity(Intent.createChooser(intent, "Share shim")) selectedShim = null }) { Icon(Icons.Default.Share, contentDescription = "Share", tint = colorResource(id = R.color.title_color)) } IconButton(onClick = { db.deleteShim(selectedShim!!.id) refreshList() selectedShim = null }) { Icon(Icons.Outlined.Delete, contentDescription = "Delete", tint = colorResource(id = R.color.title_color)) } } Box { var expanded by remember { mutableStateOf(false) } IconButton(onClick = { expanded = true }) { Icon(Icons.Default.MoreVert, contentDescription = "More", tint = colorResource(id = R.color.title_color)) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.background(colorResource(id = R.color.menu_bg_color)).padding(horizontal = 6.dp) ) { DropdownMenuItem( text = { Text("Import", color = colorResource(id = R.color.menu_color)) }, onClick = { importLauncher.launch("*/*") expanded = false }, leadingIcon = { Icon(Icons.Outlined.FileOpen, null, tint = colorResource(id = R.color.menu_color)) } ) DropdownMenuItem( text = { Text("Export", color = colorResource(id = R.color.menu_color)) }, onClick = { exportLauncher.launch("shim${OTCClock.getFileNameTimestamp()}.db") expanded = false }, leadingIcon = { Icon(Icons.AutoMirrored.Outlined.ExitToApp, null, tint = colorResource(id = R.color.menu_color)) } ) HorizontalDivider( modifier = Modifier.padding(vertical = 8.dp), color = colorResource(id = R.color.hr_color).copy(0.4f), thickness = 1.dp ) DropdownMenuItem( text = { Text("Data", color = colorResource(id = R.color.menu_color)) }, onClick = { showDataDialog = true expanded = false }, leadingIcon = { Icon(Icons.Outlined.DataArray, null, tint = colorResource(id = R.color.menu_color)) } ) DropdownMenuItem( text = { Text("Settings", color = colorResource(id = R.color.menu_color)) }, onClick = { startActivity(Intent(this@MainActivity, ShimSettings::class.java)) expanded = false }, leadingIcon = { Icon(Icons.Outlined.Settings, null, tint = colorResource(id = R.color.menu_color)) } ) DropdownMenuItem( text = { Text("Version", color = colorResource(id = R.color.menu_color)) }, onClick = { showVersionDialog = true expanded = false }, leadingIcon = { Icon(Icons.Outlined.History, null, tint = colorResource(id = R.color.menu_color)) } ) } } } ) if (isSearchExpanded) { SearchField( query = searchQuery, onQueryChange = { searchQuery = it shimListState.value = if (it.isEmpty()) db.getAllShims() else db.searchShims(it) }, onClose = { isSearchExpanded = false searchQuery = "" refreshList() } ) } } } ) { padding -> LazyColumn( modifier = Modifier .fillMaxSize() .padding(padding) .background(themeManager.backgroundColor.value) ) { item { Spacer(Modifier.height(0.dp)) } items(shimListState.value) { shim -> ShimItem( shim = shim, themeManager = themeManager, isSelected = selectedShim?.id == shim.id, onHashtagClick = { tag -> searchQuery = tag isSearchExpanded = true shimListState.value = db.searchShims(tag) }, onLongClick = { selectedShim = shim }, onClick = { if (selectedShim?.id == shim.id) { selectedShim = null } } ) } } } if (!isSearchExpanded) { Box( modifier = Modifier .fillMaxSize() .background(Color.Black.copy(alpha = 0.8f)) .clickable { isSearchExpanded = true } ) Box( modifier = Modifier .align(Alignment.BottomCenter) .navigationBarsPadding() .imePadding() ) { InputBar( shimText = shimText, onShimTextChange = { shimText = it }, hashtagText = hashtagText, allTags = allTags, tagAssociations = tagAssociations, onHashtagTextChange = { hashtagText = it }, onAdd = { text, tags -> db.addShim(text, tags.text, OTCClock.getCurrentOTCTime()) shimText = "" hashtagText = TextFieldValue("") refreshList() }, themeManager = themeManager ) } } if (showDataDialog) { DataDialog( shims = shimListState.value, onDismiss = { showDataDialog = false } ) } if (showTagsDialog) { TagsDialog( shims = shimListState.value, onDismiss = { showTagsDialog = false }, onTagClick = { tag -> searchQuery = tag isSearchExpanded = true shimListState.value = db.searchShims(tag) } ) } if (showVersionDialog) { VersionDialog(onDismiss = { showVersionDialog = false }) } } } }