Recent repositories now are unlimited, can be removed and searched
This commit is contained in:
parent
ef41e9acf0
commit
127f2158ee
@ -5,6 +5,8 @@ import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@ -20,26 +22,25 @@ class AppStateManager @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
private val _latestOpenedRepositoriesPaths = mutableListOf<String>()
|
private val _latestOpenedRepositoriesPaths = MutableStateFlow<List<String>>(emptyList())
|
||||||
val latestOpenedRepositoriesPaths: List<String>
|
val latestOpenedRepositoriesPaths = _latestOpenedRepositoriesPaths.asStateFlow()
|
||||||
get() = _latestOpenedRepositoriesPaths
|
|
||||||
|
|
||||||
val latestOpenedRepositoryPath: String
|
val latestOpenedRepositoryPath: String
|
||||||
get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: ""
|
get() = _latestOpenedRepositoriesPaths.value.firstOrNull() ?: ""
|
||||||
|
|
||||||
fun repositoryTabChanged(path: String) = appScope.launch(Dispatchers.IO) {
|
fun repositoryTabChanged(path: String) = appScope.launch(Dispatchers.IO) {
|
||||||
mutex.lock()
|
mutex.lock()
|
||||||
try {
|
try {
|
||||||
|
val repoPaths = _latestOpenedRepositoriesPaths.value.toMutableList()
|
||||||
|
|
||||||
// Remove any previously existing path
|
// Remove any previously existing path
|
||||||
_latestOpenedRepositoriesPaths.removeIf { it == path }
|
repoPaths.removeIf { it == path }
|
||||||
|
|
||||||
// Add the latest one to the beginning
|
// Add the latest one to the beginning
|
||||||
_latestOpenedRepositoriesPaths.add(0, path)
|
repoPaths.add(0, path)
|
||||||
|
|
||||||
if (_latestOpenedRepositoriesPaths.count() > 10)
|
appSettingsRepository.latestOpenedRepositoriesPath = Json.encodeToString(repoPaths)
|
||||||
_latestOpenedRepositoriesPaths.removeLast()
|
_latestOpenedRepositoriesPaths.value = repoPaths
|
||||||
|
|
||||||
appSettingsRepository.latestOpenedRepositoriesPath = Json.encodeToString(_latestOpenedRepositoriesPaths)
|
|
||||||
} finally {
|
} finally {
|
||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
}
|
}
|
||||||
@ -49,11 +50,28 @@ class AppStateManager @Inject constructor(
|
|||||||
val repositoriesPathsSaved = appSettingsRepository.latestOpenedRepositoriesPath
|
val repositoriesPathsSaved = appSettingsRepository.latestOpenedRepositoriesPath
|
||||||
if (repositoriesPathsSaved.isNotEmpty()) {
|
if (repositoriesPathsSaved.isNotEmpty()) {
|
||||||
val repositories = Json.decodeFromString<List<String>>(repositoriesPathsSaved)
|
val repositories = Json.decodeFromString<List<String>>(repositoriesPathsSaved)
|
||||||
_latestOpenedRepositoriesPaths.addAll(repositories)
|
val repoPaths = _latestOpenedRepositoriesPaths.value.toMutableList()
|
||||||
|
|
||||||
|
repoPaths.addAll(repositories)
|
||||||
|
|
||||||
|
_latestOpenedRepositoriesPaths.value = repoPaths
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelCoroutines() {
|
fun cancelCoroutines() {
|
||||||
appScope.cancel("Closing app")
|
appScope.cancel("Closing app")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeRepositoryFromRecent(path: String) = appScope.launch {
|
||||||
|
mutex.lock()
|
||||||
|
try {
|
||||||
|
val repoPaths = _latestOpenedRepositoriesPaths.value.toMutableList()
|
||||||
|
repoPaths.removeIf { it == path }
|
||||||
|
|
||||||
|
appSettingsRepository.latestOpenedRepositoriesPath = Json.encodeToString(repoPaths)
|
||||||
|
_latestOpenedRepositoriesPaths.value = repoPaths
|
||||||
|
} finally {
|
||||||
|
mutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,11 +21,13 @@ import com.jetpackduba.gitnuro.git.DiffEntryType
|
|||||||
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
import com.jetpackduba.gitnuro.git.rebase.RebaseInteractiveState
|
||||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||||
|
import com.jetpackduba.gitnuro.models.AuthorInfoSimple
|
||||||
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
||||||
import com.jetpackduba.gitnuro.ui.components.TripleVerticalSplitPanel
|
import com.jetpackduba.gitnuro.ui.components.TripleVerticalSplitPanel
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||||
import com.jetpackduba.gitnuro.ui.diff.Diff
|
import com.jetpackduba.gitnuro.ui.diff.Diff
|
||||||
import com.jetpackduba.gitnuro.ui.log.Log
|
import com.jetpackduba.gitnuro.ui.log.Log
|
||||||
|
import com.jetpackduba.gitnuro.updates.Update
|
||||||
import com.jetpackduba.gitnuro.viewmodels.BlameState
|
import com.jetpackduba.gitnuro.viewmodels.BlameState
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
@ -160,14 +162,26 @@ fun RepositoryOpenPage(
|
|||||||
.background(MaterialTheme.colors.primaryVariant.copy(alpha = 0.2f))
|
.background(MaterialTheme.colors.primaryVariant.copy(alpha = 0.2f))
|
||||||
)
|
)
|
||||||
|
|
||||||
BottomInfoBar(tabViewModel)
|
|
||||||
|
val userInfo by tabViewModel.authorInfoSimple.collectAsState()
|
||||||
|
val newUpdate = tabViewModel.update.collectAsState().value
|
||||||
|
|
||||||
|
BottomInfoBar(
|
||||||
|
userInfo,
|
||||||
|
newUpdate,
|
||||||
|
onOpenUrlInBrowser = { tabViewModel.openUrlInBrowser(it) },
|
||||||
|
onShowAuthorInfoDialog = { tabViewModel.showAuthorInfoDialog() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
private fun BottomInfoBar(
|
||||||
val userInfo by tabViewModel.authorInfoSimple.collectAsState()
|
userInfo: AuthorInfoSimple,
|
||||||
val newUpdate = tabViewModel.hasUpdates.collectAsState().value
|
newUpdate: Update?,
|
||||||
|
onOpenUrlInBrowser: (String) -> Unit,
|
||||||
|
onShowAuthorInfoDialog: () -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -180,7 +194,7 @@ private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.handMouseClickable { tabViewModel.showAuthorInfoDialog() },
|
.handMouseClickable { onShowAuthorInfoDialog() },
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -194,7 +208,7 @@ private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
|||||||
if (newUpdate != null) {
|
if (newUpdate != null) {
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
text = "Update ${newUpdate.appVersion} available",
|
text = "Update ${newUpdate.appVersion} available",
|
||||||
onClick = { tabViewModel.openUrlInBrowser(newUpdate.downloadUrl) },
|
onClick = { onOpenUrlInBrowser(newUpdate.downloadUrl) },
|
||||||
backgroundButton = MaterialTheme.colors.primary,
|
backgroundButton = MaterialTheme.colors.primary,
|
||||||
modifier = Modifier.padding(end = 16.dp)
|
modifier = Modifier.padding(end = 16.dp)
|
||||||
)
|
)
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
package com.jetpackduba.gitnuro.ui
|
package com.jetpackduba.gitnuro.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -14,6 +17,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.BiasAlignment
|
import androidx.compose.ui.BiasAlignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
@ -24,15 +28,14 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.AppConstants
|
import com.jetpackduba.gitnuro.AppConstants
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
import com.jetpackduba.gitnuro.extensions.dirName
|
import com.jetpackduba.gitnuro.extensions.*
|
||||||
import com.jetpackduba.gitnuro.extensions.dirPath
|
import com.jetpackduba.gitnuro.theme.AppTheme
|
||||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
|
||||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
|
||||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
|
||||||
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||||
import com.jetpackduba.gitnuro.theme.textButtonColors
|
import com.jetpackduba.gitnuro.theme.textButtonColors
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||||
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.AppInfoDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.AppInfoDialog
|
||||||
|
import com.jetpackduba.gitnuro.updates.Update
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||||
|
|
||||||
|
|
||||||
@ -42,22 +45,14 @@ fun WelcomePage(
|
|||||||
onShowCloneDialog: () -> Unit,
|
onShowCloneDialog: () -> Unit,
|
||||||
onShowSettings: () -> Unit,
|
onShowSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val appStateManager = tabViewModel.appStateManager
|
val recentlyOpenedRepositories by tabViewModel.appStateManager.latestOpenedRepositoriesPaths.collectAsState()
|
||||||
var showAdditionalInfo by remember { mutableStateOf(false) }
|
val newUpdate by tabViewModel.update.collectAsState()
|
||||||
|
|
||||||
|
WelcomeView(
|
||||||
Column(
|
recentlyOpenedRepositories,
|
||||||
modifier = Modifier
|
newUpdate,
|
||||||
.fillMaxSize()
|
onShowCloneDialog = onShowCloneDialog,
|
||||||
.background(MaterialTheme.colors.surface),
|
onShowSettings = onShowSettings,
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = BiasAlignment.Vertical(-0.5f),
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
HomeButtons(
|
|
||||||
onOpenRepository = {
|
onOpenRepository = {
|
||||||
val repo = tabViewModel.openDirectoryPicker()
|
val repo = tabViewModel.openDirectoryPicker()
|
||||||
|
|
||||||
@ -72,14 +67,91 @@ fun WelcomePage(
|
|||||||
tabViewModel.initLocalRepository(dir)
|
tabViewModel.initLocalRepository(dir)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onOpenKnownRepository = { tabViewModel.openRepository(it) },
|
||||||
|
onOpenUrlInBrowser = { tabViewModel.openUrlInBrowser(it) },
|
||||||
|
onRemoveRepositoryFromRecent = { tabViewModel.removeRepositoryFromRecent(it) }
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun WelcomeViewPreview() {
|
||||||
|
AppTheme(
|
||||||
|
customTheme = null
|
||||||
|
) {
|
||||||
|
val recentRepositories = (0..10).map {
|
||||||
|
"/home/user/sample$it"
|
||||||
|
}
|
||||||
|
WelcomeView(
|
||||||
|
recentlyOpenedRepositories = recentRepositories,
|
||||||
|
newUpdate = null,
|
||||||
|
onShowCloneDialog = {},
|
||||||
|
onShowSettings = {},
|
||||||
|
onOpenRepository = {},
|
||||||
|
onOpenKnownRepository = {},
|
||||||
|
onStartRepository = {},
|
||||||
|
onOpenUrlInBrowser = {},
|
||||||
|
onRemoveRepositoryFromRecent = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WelcomeView(
|
||||||
|
recentlyOpenedRepositories: List<String>,
|
||||||
|
newUpdate: Update?,
|
||||||
|
onShowCloneDialog: () -> Unit,
|
||||||
|
onShowSettings: () -> Unit,
|
||||||
|
onOpenRepository: () -> Unit,
|
||||||
|
onOpenKnownRepository: (String) -> Unit,
|
||||||
|
onStartRepository: () -> Unit,
|
||||||
|
onOpenUrlInBrowser: (String) -> Unit,
|
||||||
|
onRemoveRepositoryFromRecent: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
|
var showAdditionalInfo by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp, BiasAlignment.Vertical(-0.5f)),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = AppConstants.APP_NAME,
|
||||||
|
style = MaterialTheme.typography.h1,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
|
||||||
|
) {
|
||||||
|
HomeButtons(
|
||||||
|
onOpenRepository = onOpenRepository,
|
||||||
|
onStartRepository = onStartRepository,
|
||||||
onShowCloneView = onShowCloneDialog,
|
onShowCloneView = onShowCloneDialog,
|
||||||
onShowAdditionalInfo = { showAdditionalInfo = true },
|
onShowAdditionalInfo = { showAdditionalInfo = true },
|
||||||
onShowSettings = onShowSettings,
|
onShowSettings = onShowSettings,
|
||||||
onOpenUrlInBrowser = { url -> tabViewModel.openUrlInBrowser(url) }
|
onOpenUrlInBrowser = onOpenUrlInBrowser,
|
||||||
)
|
)
|
||||||
|
|
||||||
RecentRepositories(appStateManager, tabViewModel)
|
RecentRepositories(
|
||||||
|
recentlyOpenedRepositories,
|
||||||
|
canRepositoriesBeRemoved = true,
|
||||||
|
onOpenKnownRepository = onOpenKnownRepository,
|
||||||
|
onRemoveRepositoryFromRecent = onRemoveRepositoryFromRecent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(
|
Spacer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(1.dp)
|
.height(1.dp)
|
||||||
@ -87,20 +159,25 @@ fun WelcomePage(
|
|||||||
.background(MaterialTheme.colors.primaryVariant.copy(alpha = 0.2f))
|
.background(MaterialTheme.colors.primaryVariant.copy(alpha = 0.2f))
|
||||||
)
|
)
|
||||||
|
|
||||||
BottomInfoBar(tabViewModel)
|
BottomInfoBar(
|
||||||
|
newUpdate = newUpdate,
|
||||||
|
onOpenUrlInBrowser = onOpenUrlInBrowser,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAdditionalInfo) {
|
if (showAdditionalInfo) {
|
||||||
AppInfoDialog(
|
AppInfoDialog(
|
||||||
onClose = { showAdditionalInfo = false },
|
onClose = { showAdditionalInfo = false },
|
||||||
onOpenUrlInBrowser = { url -> tabViewModel.openUrlInBrowser(url) }
|
onOpenUrlInBrowser = onOpenUrlInBrowser
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
private fun BottomInfoBar(
|
||||||
val newUpdate = tabViewModel.hasUpdates.collectAsState().value
|
newUpdate: Update?,
|
||||||
|
onOpenUrlInBrowser: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -115,7 +192,7 @@ private fun BottomInfoBar(tabViewModel: TabViewModel) {
|
|||||||
if (newUpdate != null) {
|
if (newUpdate != null) {
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
text = "Update ${newUpdate.appVersion} available",
|
text = "Update ${newUpdate.appVersion} available",
|
||||||
onClick = { tabViewModel.openUrlInBrowser(newUpdate.downloadUrl) },
|
onClick = { onOpenUrlInBrowser(newUpdate.downloadUrl) },
|
||||||
backgroundButton = MaterialTheme.colors.primary,
|
backgroundButton = MaterialTheme.colors.primary,
|
||||||
modifier = Modifier.padding(end = 16.dp)
|
modifier = Modifier.padding(end = 16.dp)
|
||||||
)
|
)
|
||||||
@ -138,16 +215,7 @@ fun HomeButtons(
|
|||||||
onShowSettings: () -> Unit,
|
onShowSettings: () -> Unit,
|
||||||
onOpenUrlInBrowser: (String) -> Unit,
|
onOpenUrlInBrowser: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column {
|
||||||
modifier = Modifier.padding(end = 32.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = AppConstants.APP_NAME,
|
|
||||||
style = MaterialTheme.typography.h1,
|
|
||||||
maxLines = 1,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
ButtonTile(
|
ButtonTile(
|
||||||
modifier = Modifier.padding(bottom = 8.dp),
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
title = "Open a repository",
|
title = "Open a repository",
|
||||||
@ -206,19 +274,23 @@ fun HomeButtons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewModel) {
|
fun RecentRepositories(
|
||||||
|
recentlyOpenedRepositories: List<String>,
|
||||||
|
canRepositoriesBeRemoved: Boolean,
|
||||||
|
onRemoveRepositoryFromRecent: (String) -> Unit,
|
||||||
|
onOpenKnownRepository: (String) -> Unit,
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 32.dp),
|
.padding(start = 32.dp)
|
||||||
|
.width(600.dp)
|
||||||
|
.height(400.dp),
|
||||||
) {
|
) {
|
||||||
val latestOpenedRepositoriesPaths = appStateManager.latestOpenedRepositoriesPaths
|
var filter by remember {
|
||||||
Text(
|
mutableStateOf("")
|
||||||
text = "Recent",
|
}
|
||||||
style = MaterialTheme.typography.h3,
|
|
||||||
modifier = Modifier.padding(top = 48.dp, bottom = 4.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (latestOpenedRepositoriesPaths.isEmpty()) {
|
if (recentlyOpenedRepositories.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
"Nothing to see here, open a repository first!",
|
"Nothing to see here, open a repository first!",
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary,
|
color = MaterialTheme.colors.onBackgroundSecondary,
|
||||||
@ -226,47 +298,113 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo
|
|||||||
modifier = Modifier.padding(top = 16.dp)
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LazyColumn {
|
AdjustableOutlinedTextField(
|
||||||
items(items = latestOpenedRepositoriesPaths) { repo ->
|
value = filter,
|
||||||
|
onValueChange = { filter = it },
|
||||||
|
hint = "Search for recent repositories",
|
||||||
|
trailingIcon = {
|
||||||
|
if (filter.isNotEmpty()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { filter = "" },
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.handOnHover(),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(AppIcons.CLOSE),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (filter.isEmpty()) MaterialTheme.colors.onBackgroundSecondary else MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val filteredRepositories = remember(filter, recentlyOpenedRepositories) {
|
||||||
|
if (filter.isBlank()) {
|
||||||
|
recentlyOpenedRepositories
|
||||||
|
} else {
|
||||||
|
recentlyOpenedRepositories.filter { repository ->
|
||||||
|
repository.lowercaseContains(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
Box (modifier = Modifier.padding(top = 4.dp)) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(items = filteredRepositories) { repo ->
|
||||||
val repoDirName = repo.dirName
|
val repoDirName = repo.dirName
|
||||||
val repoDirPath = repo.dirPath
|
val repoDirPath = repo.dirPath
|
||||||
|
val hoverInteraction = remember { MutableInteractionSource() }
|
||||||
|
val isHovered by hoverInteraction.collectIsHoveredAsState()
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.handMouseClickable {
|
.fillMaxWidth()
|
||||||
tabViewModel.openRepository(repo)
|
.hoverable(hoverInteraction)
|
||||||
},
|
.handMouseClickable { onOpenKnownRepository(repo) }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = repoDirName,
|
text = repoDirName,
|
||||||
style = MaterialTheme.typography.body1,
|
style = MaterialTheme.typography.body2,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = MaterialTheme.colors.primaryVariant,
|
color = MaterialTheme.colors.primaryVariant,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.widthIn(max = 600.dp),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = repoDirPath,
|
text = repoDirPath,
|
||||||
style = MaterialTheme.typography.body1,
|
style = MaterialTheme.typography.body2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier,
|
||||||
.padding(start = 4.dp)
|
|
||||||
.widthIn(max = 600.dp),
|
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary
|
color = MaterialTheme.colors.onBackgroundSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val buttonAlpha = if (canRepositoriesBeRemoved && isHovered) {
|
||||||
|
1f
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { onRemoveRepositoryFromRecent(repo) },
|
||||||
|
enabled = canRepositoriesBeRemoved && isHovered,
|
||||||
|
modifier = Modifier.alpha(buttonAlpha)
|
||||||
|
.size(24.dp)
|
||||||
|
.handOnHover(),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(AppIcons.CLOSE),
|
||||||
|
contentDescription = "Remove repository from recent",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colors.onBackgroundSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalScrollbar(
|
||||||
|
rememberScrollbarAdapter(listState),
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package com.jetpackduba.gitnuro.ui.context_menu
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.DropdownMenuItem
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropDownContent(
|
|
||||||
dropDownContentData: DropDownContentData,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
enabled = enabled,
|
|
||||||
onClick = {
|
|
||||||
dropDownContentData.onClick()
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
if (dropDownContentData.icon != null) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(dropDownContentData.icon),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = dropDownContentData.label,
|
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package com.jetpackduba.gitnuro.ui.context_menu
|
|
||||||
|
|
||||||
data class DropDownContentData(
|
|
||||||
val label: String,
|
|
||||||
val icon: String? = null,
|
|
||||||
val onClick: () -> Unit,
|
|
||||||
)
|
|
@ -1,17 +0,0 @@
|
|||||||
package com.jetpackduba.gitnuro.ui.context_menu
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
fun repositoryAdditionalOptionsMenu(
|
|
||||||
onOpenRepositoryOnFileExplorer: () -> Unit,
|
|
||||||
): List<DropDownContentData> {
|
|
||||||
return mutableListOf(
|
|
||||||
DropDownContentData(
|
|
||||||
label = "Open repository folder",
|
|
||||||
icon = AppIcons.SOURCE,
|
|
||||||
onClick = onOpenRepositoryOnFileExplorer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
@ -442,7 +442,7 @@ fun SearchFilter(
|
|||||||
.padding(end = 4.dp),
|
.padding(end = 4.dp),
|
||||||
onClick = { logViewModel.closeSearch() }
|
onClick = { logViewModel.closeSearch() }
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Clear, contentDescription = null)
|
Icon(painterResource(AppIcons.CLOSE), contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,7 +348,7 @@ class TabViewModel @Inject constructor(
|
|||||||
openRepository(repoDir)
|
openRepository(repoDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasUpdates: StateFlow<Update?> = updatesRepository.hasUpdatesFlow()
|
val update: StateFlow<Update?> = updatesRepository.hasUpdatesFlow()
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.stateIn(tabScope, started = SharingStarted.Eagerly, null)
|
.stateIn(tabScope, started = SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
@ -449,6 +449,10 @@ class TabViewModel @Inject constructor(
|
|||||||
fun openUrlInBrowser(url: String) {
|
fun openUrlInBrowser(url: String) {
|
||||||
openUrlInBrowserUseCase(url)
|
openUrlInBrowserUseCase(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeRepositoryFromRecent(repository: String) {
|
||||||
|
appStateManager.removeRepositoryFromRecent(repository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user