@file:OptIn(ExperimentalComposeUiApi::class) package app import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.zIndex import app.di.DaggerAppComponent import app.theme.AppTheme import app.ui.components.RepositoriesTabPanel import app.ui.components.TabInformation import app.ui.dialogs.SettingsDialog import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject class App { private val appComponent = DaggerAppComponent.create() @Inject lateinit var appStateManager: AppStateManager @Inject lateinit var appPreferences: AppPreferences private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) init { appComponent.inject(this) println("AppStateManagerReference $appStateManager") } private val tabsFlow = MutableStateFlow>(emptyList()) fun start() { appStateManager.loadRepositoriesTabs() loadTabs() application { var isOpen by remember { mutableStateOf(true) } val theme by appPreferences.themeState.collectAsState() if (isOpen) { Window( title = "Gitnuro", onCloseRequest = { isOpen = false }, state = rememberWindowState( placement = WindowPlacement.Maximized, size = DpSize(1280.dp, 720.dp) ), icon = painterResource("logo.svg"), ) { var showSettingsDialog by remember { mutableStateOf(false) } AppTheme(theme = theme) { Box(modifier = Modifier.background(MaterialTheme.colors.background)) { AppTabs( onOpenSettings = { showSettingsDialog = true } ) } if (showSettingsDialog) { SettingsDialog( appPreferences = appPreferences, onDismiss = { showSettingsDialog = false } ) } } } } else { appScope.cancel("Closing app") this.exitApplication() } } } private fun loadTabs() { val repositoriesSavedTabs = appStateManager.openRepositoriesPathsTabs var repoTabs = repositoriesSavedTabs.map { repositoryTab -> newAppTab( key = repositoryTab.key, path = repositoryTab.value ) } if (repoTabs.isEmpty()) { repoTabs = listOf( newAppTab() ) } tabsFlow.value = repoTabs println("After reading prefs, got ${tabsFlow.value.count()} tabs") } @Composable fun AppTabs( onOpenSettings: () -> Unit, ) { val tabs by tabsFlow.collectAsState() val tabsInformationList = tabs.sortedBy { it.key } println("Tabs count ${tabs.count()}") val selectedTabKey = remember { mutableStateOf(0) } println("Selected tab key: ${selectedTabKey.value}") Column( modifier = Modifier.background(MaterialTheme.colors.background) ) { Tabs( tabsInformationList = tabsInformationList, selectedTabKey = selectedTabKey, onOpenSettings = onOpenSettings, onAddedTab = { tabInfo -> addTab(tabInfo) }, onRemoveTab = { key -> removeTab(key) } ) TabsContent(tabsInformationList, selectedTabKey.value) } } private fun removeTab(key: Int) = appScope.launch(Dispatchers.IO) { // Stop any running jobs val tabs = tabsFlow.value val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch tabToRemove.tabViewModel.dispose() // Remove tab from persistent tabs storage appStateManager.repositoryTabRemoved(key) // Remove from tabs flow tabsFlow.value = tabsFlow.value.filter { tab -> tab.key != key } } fun addTab(tabInformation: TabInformation) = appScope.launch(Dispatchers.IO) { tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) } } @Composable fun Tabs( selectedTabKey: MutableState, onOpenSettings: () -> Unit, tabsInformationList: List, onAddedTab: (TabInformation) -> Unit, onRemoveTab: (Int) -> Unit, ) { Row( modifier = Modifier .padding(top = 4.dp, bottom = 0.dp, start = 4.dp, end = 4.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { RepositoriesTabPanel( modifier = Modifier .weight(1f), tabs = tabsInformationList, selectedTabKey = selectedTabKey.value, onTabSelected = { newSelectedTabKey -> println("New selected tab key $newSelectedTabKey") selectedTabKey.value = newSelectedTabKey }, newTabContent = { key -> val newAppTab = newAppTab( key = key ) onAddedTab(newAppTab) newAppTab }, onTabClosed = onRemoveTab ) IconButton( modifier = Modifier .padding(horizontal = 8.dp) .size(24.dp), onClick = onOpenSettings ) { Icon( painter = painterResource("settings.svg"), contentDescription = null, modifier = Modifier.fillMaxSize(), tint = MaterialTheme.colors.primary, ) } } } private fun newAppTab( key: Int = 0, tabName: MutableState = mutableStateOf("New tab"), path: String? = null, ): TabInformation { return TabInformation( tabName = tabName, key = key, path = path, appComponent = appComponent, ) } } @Composable private fun TabsContent(tabs: List, selectedTabKey: Int) { LazyColumn( modifier = Modifier .fillMaxSize(), ) { items(items = tabs, key = { it.key }) { val isItemSelected = it.key == selectedTabKey var tabMod: Modifier = if (!isItemSelected) Modifier.size(0.dp) else Modifier .fillParentMaxSize() tabMod = tabMod.background(MaterialTheme.colors.primary) .alpha(if (isItemSelected) 1f else -1f) .zIndex(if (isItemSelected) 1f else -1f) Box( modifier = tabMod, ) { it.content(it) } } } } @Composable fun LoadingRepository() { Box { } }