Improved welcome/new tab page

This commit is contained in:
Abdelilah El Aissaoui 2021-10-16 03:39:59 +02:00
parent cc2a7e180f
commit cd592292d1
9 changed files with 222 additions and 45 deletions

View File

@ -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

View File

@ -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)
} }
} }

View File

@ -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)
}
} }
} }

View File

@ -8,3 +8,24 @@ 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
}

View File

@ -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()

View File

@ -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()

View File

@ -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,
)
}
}
}

View 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

View 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