shim/app/src/main/kotlin/com/vgmlr/shim/MainActivity.kt (16.5 kb)
Modified: 02:25:59 66 026 (20 May 026) - 4 Days Ago
Download
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 })
            }
        }
    }
}