Improved welcome/new tab page
This commit is contained in:
parent
cc2a7e180f
commit
cd592292d1
@ -200,11 +200,7 @@ fun App(gitManager: GitManager, repositoryPath: String?, tabName: MutableState<S
|
|||||||
if (isProcessing)
|
if (isProcessing)
|
||||||
Box(modifier = Modifier.fillMaxSize()) //TODO this should block of the mouse/keyboard events while visible
|
Box(modifier = Modifier.fillMaxSize()) //TODO this should block of the mouse/keyboard events while visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -6,22 +6,22 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
private const val PREFERENCES_NAME = "GitnuroConfig"
|
private const val PREFERENCES_NAME = "GitnuroConfig"
|
||||||
|
|
||||||
private const val PREF_LATEST_REPOSITORIES_OPENED = "latestRepositoriesOpened"
|
private const val PREF_LATEST_REPOSITORIES_TABS_OPENED = "latestRepositoriesTabsOpened"
|
||||||
private const val PREF_LAST_OPENED_REPOSITORY_PATH = "lastOpenedRepositoryPath"
|
private const val PREF_LAST_OPENED_REPOSITORIES_PATH = "lastOpenedRepositoriesList"
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppPreferences @Inject constructor() {
|
class AppPreferences @Inject constructor() {
|
||||||
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
|
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
|
||||||
|
|
||||||
var latestTabsOpened: String
|
var latestTabsOpened: String
|
||||||
get() = preferences.get(PREF_LATEST_REPOSITORIES_OPENED, "")
|
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
|
||||||
set(value) {
|
set(value) {
|
||||||
preferences.put(PREF_LATEST_REPOSITORIES_OPENED, value)
|
preferences.put(PREF_LATEST_REPOSITORIES_TABS_OPENED, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var latestOpenedRepositoryPath: String
|
var latestOpenedRepositoriesPath: String
|
||||||
get() = preferences.get(PREF_LAST_OPENED_REPOSITORY_PATH, "")
|
get() = preferences.get(PREF_LAST_OPENED_REPOSITORIES_PATH, "")
|
||||||
set(value) {
|
set(value) {
|
||||||
preferences.put(PREF_LAST_OPENED_REPOSITORY_PATH, value)
|
preferences.put(PREF_LAST_OPENED_REPOSITORIES_PATH, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,36 +11,52 @@ import javax.inject.Singleton
|
|||||||
class AppStateManager @Inject constructor(
|
class AppStateManager @Inject constructor(
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val _openRepositoriesPaths = mutableMapOf<Int, String>()
|
private val _openRepositoriesPaths = mutableMapOf<Int, String>()
|
||||||
val openRepositoriesPathsTabs: Map<Int, String>
|
val openRepositoriesPathsTabs: Map<Int, String>
|
||||||
get() = _openRepositoriesPaths
|
get() = _openRepositoriesPaths
|
||||||
|
|
||||||
|
private val _latestOpenedRepositoriesPaths = mutableListOf<String>()
|
||||||
|
val latestOpenedRepositoriesPaths: List<String>
|
||||||
|
get() = _latestOpenedRepositoriesPaths
|
||||||
|
|
||||||
private val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) // TODO Stop this when closing the app
|
private val appStateScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) // TODO Stop this when closing the app
|
||||||
|
|
||||||
var latestOpenedRepositoryPath: String
|
val latestOpenedRepositoryPath: String
|
||||||
get() = appPreferences.latestOpenedRepositoryPath
|
get() = _latestOpenedRepositoriesPaths.firstOrNull() ?: ""
|
||||||
set(value) {
|
|
||||||
appPreferences.latestOpenedRepositoryPath = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun repositoryTabChanged(key: Int, path: String) {
|
fun repositoryTabChanged(key: Int, path: String) = appStateScope.launch(Dispatchers.IO) {
|
||||||
_openRepositoriesPaths[key] = path
|
// Do not save already saved repos
|
||||||
|
if (!_openRepositoriesPaths.containsValue(path))
|
||||||
|
_openRepositoriesPaths[key] = path
|
||||||
|
|
||||||
|
// Remove any previously existing path
|
||||||
|
_latestOpenedRepositoriesPaths.remove(path)
|
||||||
|
|
||||||
|
// Add the latest one to the beginning
|
||||||
|
_latestOpenedRepositoriesPaths.add(0, path)
|
||||||
|
|
||||||
|
if (_latestOpenedRepositoriesPaths.count() > 5)
|
||||||
|
_latestOpenedRepositoriesPaths.removeLast()
|
||||||
|
|
||||||
updateSavedRepositoryTabs()
|
updateSavedRepositoryTabs()
|
||||||
|
updateLatestRepositoryTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun repositoryTabRemoved(key: Int) {
|
fun repositoryTabRemoved(key: Int) = appStateScope.launch(Dispatchers.IO) {
|
||||||
_openRepositoriesPaths.remove(key)
|
_openRepositoriesPaths.remove(key)
|
||||||
|
|
||||||
updateSavedRepositoryTabs()
|
updateSavedRepositoryTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSavedRepositoryTabs() = appStateScope.launch(Dispatchers.IO) {
|
private suspend fun updateSavedRepositoryTabs() = withContext(Dispatchers.IO) {
|
||||||
val tabsList = _openRepositoriesPaths.map { it.value }
|
val tabsList = _openRepositoriesPaths.map { it.value }
|
||||||
appPreferences.latestTabsOpened = Json.encodeToString(tabsList)
|
appPreferences.latestTabsOpened = Json.encodeToString(tabsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun updateLatestRepositoryTabs() = withContext(Dispatchers.IO) {
|
||||||
|
appPreferences.latestOpenedRepositoriesPath = Json.encodeToString(_latestOpenedRepositoriesPaths)
|
||||||
|
}
|
||||||
|
|
||||||
fun loadRepositoriesTabs() = appStateScope.launch(Dispatchers.IO) {
|
fun loadRepositoriesTabs() = appStateScope.launch(Dispatchers.IO) {
|
||||||
val repositoriesSaved = appPreferences.latestTabsOpened
|
val repositoriesSaved = appPreferences.latestTabsOpened
|
||||||
|
|
||||||
@ -52,5 +68,10 @@ class AppStateManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val repositoriesPathsSaved = appPreferences.latestOpenedRepositoriesPath
|
||||||
|
if(repositoriesPathsSaved.isNotEmpty()) {
|
||||||
|
val repositories = Json.decodeFromString<List<String>>(repositoriesPathsSaved)
|
||||||
|
_latestOpenedRepositoriesPaths.addAll(repositories)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,4 +7,25 @@ val String.md5: String
|
|||||||
get() {
|
get() {
|
||||||
val md = MessageDigest.getInstance("MD5")
|
val md = MessageDigest.getInstance("MD5")
|
||||||
return BigInteger(1, md.digest(this.toByteArray())).toString(16).padStart(32, '0')
|
return BigInteger(1, md.digest(this.toByteArray())).toString(16).padStart(32, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
val String.dirName: String
|
||||||
|
get() {
|
||||||
|
val parts = this.split("/")
|
||||||
|
|
||||||
|
return if(parts.isNotEmpty())
|
||||||
|
parts.last()
|
||||||
|
else
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
val String.dirPath: String
|
||||||
|
get() {
|
||||||
|
val parts = this.split("/").toMutableList()
|
||||||
|
|
||||||
|
return if(parts.count() > 1) {
|
||||||
|
parts.removeLast()
|
||||||
|
parts.joinToString("/")
|
||||||
|
} else
|
||||||
|
this
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ import org.eclipse.jgit.lib.Ref
|
|||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
|
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
|
||||||
import app.AppPreferences
|
|
||||||
import app.AppStateManager
|
import app.AppStateManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -24,7 +23,7 @@ class GitManager @Inject constructor(
|
|||||||
private val branchesManager: BranchesManager,
|
private val branchesManager: BranchesManager,
|
||||||
private val stashManager: StashManager,
|
private val stashManager: StashManager,
|
||||||
private val diffManager: DiffManager,
|
private val diffManager: DiffManager,
|
||||||
private val appStateManager: AppStateManager,
|
val appStateManager: AppStateManager,
|
||||||
) {
|
) {
|
||||||
val repositoryName: String
|
val repositoryName: String
|
||||||
get() = safeGit.repository.directory.parentFile.name
|
get() = safeGit.repository.directory.parentFile.name
|
||||||
@ -63,9 +62,6 @@ class GitManager @Inject constructor(
|
|||||||
val credentialsState: StateFlow<CredentialsState>
|
val credentialsState: StateFlow<CredentialsState>
|
||||||
get() = credentialsStateManager.credentialsState
|
get() = credentialsStateManager.credentialsState
|
||||||
|
|
||||||
val latestOpenedRepositoryPath: String
|
|
||||||
get() = appStateManager.latestOpenedRepositoryPath
|
|
||||||
|
|
||||||
private var git: Git? = null
|
private var git: Git? = null
|
||||||
|
|
||||||
val safeGit: Git
|
val safeGit: Git
|
||||||
@ -109,7 +105,6 @@ class GitManager @Inject constructor(
|
|||||||
git = Git(repository)
|
git = Git(repository)
|
||||||
|
|
||||||
onRepositoryChanged(repository.directory.parent)
|
onRepositoryChanged(repository.directory.parent)
|
||||||
appStateManager.latestOpenedRepositoryPath = directory.absolutePath
|
|
||||||
refreshRepositoryInfo()
|
refreshRepositoryInfo()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
|
@ -2,7 +2,8 @@ import app.git.GitManager
|
|||||||
import javax.swing.JFileChooser
|
import javax.swing.JFileChooser
|
||||||
|
|
||||||
fun openRepositoryDialog(gitManager: GitManager) {
|
fun openRepositoryDialog(gitManager: GitManager) {
|
||||||
val latestDirectoryOpened = gitManager.latestOpenedRepositoryPath
|
val appStateManager = gitManager.appStateManager
|
||||||
|
val latestDirectoryOpened = appStateManager.latestOpenedRepositoryPath
|
||||||
|
|
||||||
val fileChooser = if (latestDirectoryOpened.isEmpty())
|
val fileChooser = if (latestDirectoryOpened.isEmpty())
|
||||||
JFileChooser()
|
JFileChooser()
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package app.ui
|
package app.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -11,56 +14,194 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.git.GitManager
|
import app.git.GitManager
|
||||||
import openRepositoryDialog
|
import openRepositoryDialog
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.BiasAlignment
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.extensions.dirName
|
||||||
|
import app.extensions.dirPath
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.secondaryTextColor
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WelcomePage(gitManager: GitManager) {
|
fun WelcomePage(gitManager: GitManager) {
|
||||||
|
val appStateManager = gitManager.appStateManager
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = BiasAlignment.Vertical(-0.5f),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight(),
|
.padding(end = 32.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Gitnuro",
|
||||||
|
fontSize = 32.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
ButtonTile(
|
ButtonTile(
|
||||||
title = "Open repository",
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
title = "Open a repository",
|
||||||
painter = painterResource("open.svg"),
|
painter = painterResource("open.svg"),
|
||||||
onClick = { openRepositoryDialog(gitManager) }
|
onClick = { openRepositoryDialog(gitManager) }
|
||||||
)
|
)
|
||||||
|
|
||||||
ButtonTile(
|
ButtonTile(
|
||||||
title = "Clone repository",
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
title = "Clone a repository",
|
||||||
painter = painterResource("open.svg"),
|
painter = painterResource("open.svg"),
|
||||||
onClick = {}
|
onClick = { }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ButtonTile(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
title = "Start a local repository",
|
||||||
|
painter = painterResource("open.svg"),
|
||||||
|
onClick = { }
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "About Gitnuro",
|
||||||
|
fontSize = 18.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp, bottom = 8.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
IconTextButton(
|
||||||
|
title = "Source code",
|
||||||
|
painter = painterResource("code.svg"),
|
||||||
|
onClick = {
|
||||||
|
Desktop.getDesktop().browse(URI("https://github.com/aeab13/Gitnuro"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
IconTextButton(
|
||||||
|
title = "Report a bug",
|
||||||
|
painter = painterResource("bug.svg"),
|
||||||
|
onClick = {
|
||||||
|
Desktop.getDesktop().browse(URI("https://github.com/aeab13/Gitnuro/issues"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 32.dp),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Recent",
|
||||||
|
fontSize = 18.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp, bottom = 8.dp),
|
||||||
|
)
|
||||||
|
LazyColumn {
|
||||||
|
items(items = appStateManager.latestOpenedRepositoriesPaths) { repo ->
|
||||||
|
val repoDirName = repo.dirName
|
||||||
|
val repoDirPath = repo.dirPath
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
gitManager.openRepository(repo)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = repoDirName,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = repoDirPath,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
|
color = MaterialTheme.colors.secondaryTextColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ButtonTile(
|
fun ButtonTile(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
painter: Painter,
|
painter: Painter,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.size(width = 200.dp, height = 56.dp)
|
modifier = modifier.size(width = 280.dp, height = 56.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.size(24.dp)
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.padding(end = 8.dp),
|
) {
|
||||||
painter = painter,
|
Image(
|
||||||
contentDescription = null,
|
modifier = Modifier
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
|
.padding(end = 8.dp)
|
||||||
)
|
.size(24.dp),
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
|
||||||
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IconTextButton(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String,
|
||||||
|
painter: Painter,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.size(width = 280.dp, height = 40.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(24.dp),
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1
src/main/resources/bug.svg
Normal file
1
src/main/resources/bug.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"/></svg>
|
After Width: | Height: | Size: 531 B |
1
src/main/resources/code.svg
Normal file
1
src/main/resources/code.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
After Width: | Height: | Size: 249 B |
Loading…
Reference in New Issue
Block a user