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<List<ShimClass>>(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<ShimBackUp>(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<String, MutableSet<String>>()
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<ShimClass?>(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 })
}
}
}
}