Merge branch 'JetpackDuba:main' into main
This commit is contained in:
commit
7f22c833dc
@ -4,18 +4,11 @@ package app
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.material.*
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
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.graphics.Color
|
|
||||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -26,7 +19,6 @@ import androidx.compose.ui.window.Window
|
|||||||
import androidx.compose.ui.window.WindowPlacement
|
import androidx.compose.ui.window.WindowPlacement
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import app.di.DaggerAppComponent
|
import app.di.DaggerAppComponent
|
||||||
import app.theme.AppTheme
|
import app.theme.AppTheme
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
@ -76,7 +68,7 @@ class App {
|
|||||||
) {
|
) {
|
||||||
var showSettingsDialog by remember { mutableStateOf(false) }
|
var showSettingsDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
AppTheme(theme = theme) {
|
AppTheme(selectedTheme = theme) {
|
||||||
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
|
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
|
||||||
AppTabs(
|
AppTabs(
|
||||||
onOpenSettings = {
|
onOpenSettings = {
|
||||||
@ -127,13 +119,8 @@ class App {
|
|||||||
) {
|
) {
|
||||||
val tabs by tabsFlow.collectAsState()
|
val tabs by tabsFlow.collectAsState()
|
||||||
val tabsInformationList = tabs.sortedBy { it.key }
|
val tabsInformationList = tabs.sortedBy { it.key }
|
||||||
|
|
||||||
println("Tabs count ${tabs.count()}")
|
|
||||||
|
|
||||||
val selectedTabKey = remember { mutableStateOf(0) }
|
val selectedTabKey = remember { mutableStateOf(0) }
|
||||||
|
|
||||||
println("Selected tab key: ${selectedTabKey.value}")
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.background(MaterialTheme.colors.background)
|
modifier = Modifier.background(MaterialTheme.colors.background)
|
||||||
) {
|
) {
|
||||||
@ -214,7 +201,7 @@ class App {
|
|||||||
painter = painterResource("settings.svg"),
|
painter = painterResource("settings.svg"),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
tint = MaterialTheme.colors.primary,
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,11 @@ private const val PREFERENCES_NAME = "GitnuroConfig"
|
|||||||
private const val PREF_LATEST_REPOSITORIES_TABS_OPENED = "latestRepositoriesTabsOpened"
|
private const val PREF_LATEST_REPOSITORIES_TABS_OPENED = "latestRepositoriesTabsOpened"
|
||||||
private const val PREF_LAST_OPENED_REPOSITORIES_PATH = "lastOpenedRepositoriesList"
|
private const val PREF_LAST_OPENED_REPOSITORIES_PATH = "lastOpenedRepositoriesList"
|
||||||
private const val PREF_THEME = "theme"
|
private const val PREF_THEME = "theme"
|
||||||
|
private const val PREF_COMMITS_LIMIT = "commitsLimit"
|
||||||
|
private const val PREF_COMMITS_LIMIT_ENABLED = "commitsLimitEnabled"
|
||||||
|
|
||||||
|
private const val DEFAULT_COMMITS_LIMIT = 1000
|
||||||
|
private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppPreferences @Inject constructor() {
|
class AppPreferences @Inject constructor() {
|
||||||
@ -20,6 +25,12 @@ class AppPreferences @Inject constructor() {
|
|||||||
private val _themeState = MutableStateFlow(theme)
|
private val _themeState = MutableStateFlow(theme)
|
||||||
val themeState: StateFlow<Themes> = _themeState
|
val themeState: StateFlow<Themes> = _themeState
|
||||||
|
|
||||||
|
private val _commitsLimitEnabledFlow = MutableStateFlow(true)
|
||||||
|
val commitsLimitEnabledFlow: StateFlow<Boolean> = _commitsLimitEnabledFlow
|
||||||
|
|
||||||
|
private val _commitsLimitFlow = MutableStateFlow(commitsLimit)
|
||||||
|
val commitsLimitFlow: StateFlow<Int> = _commitsLimitFlow
|
||||||
|
|
||||||
var latestTabsOpened: String
|
var latestTabsOpened: String
|
||||||
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
|
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -35,10 +46,33 @@ class AppPreferences @Inject constructor() {
|
|||||||
var theme: Themes
|
var theme: Themes
|
||||||
get() {
|
get() {
|
||||||
val lastTheme = preferences.get(PREF_THEME, Themes.DARK.toString())
|
val lastTheme = preferences.get(PREF_THEME, Themes.DARK.toString())
|
||||||
return Themes.valueOf(lastTheme)
|
return try {
|
||||||
|
Themes.valueOf(lastTheme)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
Themes.DARK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
preferences.put(PREF_THEME, value.toString())
|
preferences.put(PREF_THEME, value.toString())
|
||||||
_themeState.value = value
|
_themeState.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commitsLimitEnabled: Boolean
|
||||||
|
get() {
|
||||||
|
return preferences.getBoolean(PREF_COMMITS_LIMIT_ENABLED, DEFAULT_COMMITS_LIMIT_ENABLED)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
preferences.putBoolean(PREF_COMMITS_LIMIT_ENABLED, value)
|
||||||
|
_commitsLimitEnabledFlow.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var commitsLimit: Int
|
||||||
|
get() {
|
||||||
|
return preferences.getInt(PREF_COMMITS_LIMIT, DEFAULT_COMMITS_LIMIT)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
preferences.putInt(PREF_COMMITS_LIMIT, value)
|
||||||
|
_commitsLimitFlow.value = value
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package app.di
|
package app.di
|
||||||
|
|
||||||
import app.App
|
import app.App
|
||||||
|
import app.AppPreferences
|
||||||
import app.AppStateManager
|
import app.AppStateManager
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -10,4 +11,6 @@ import javax.inject.Singleton
|
|||||||
interface AppComponent {
|
interface AppComponent {
|
||||||
fun inject(main: App)
|
fun inject(main: App)
|
||||||
fun appStateManager(): AppStateManager
|
fun appStateManager(): AppStateManager
|
||||||
|
|
||||||
|
fun appPreferences(): AppPreferences
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package app.di
|
package app.di
|
||||||
|
|
||||||
|
import app.AppPreferences
|
||||||
import app.di.modules.NetworkModule
|
import app.di.modules.NetworkModule
|
||||||
import app.ui.components.TabInformation
|
import app.ui.components.TabInformation
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
|
@ -52,12 +52,19 @@ class FileChangesWatcher @Inject constructor() {
|
|||||||
while (watchService.take().also { key = it } != null) {
|
while (watchService.take().also { key = it } != null) {
|
||||||
val events = key.pollEvents()
|
val events = key.pollEvents()
|
||||||
|
|
||||||
println("Polled events on dir ${keys[key]}")
|
|
||||||
|
|
||||||
val dir = keys[key] ?: return@withContext
|
val dir = keys[key] ?: return@withContext
|
||||||
|
|
||||||
val hasGitDirectoryChanged = dir.startsWith("$pathStr$systemSeparator.git$systemSeparator")
|
val hasGitDirectoryChanged = dir.startsWith("$pathStr$systemSeparator.git$systemSeparator")
|
||||||
|
|
||||||
|
if(events.count() == 1) {
|
||||||
|
val fileChanged = events.first().context().toString()
|
||||||
|
val fullPathOfFileChanged = "$pathStr$systemSeparator.git$systemSeparator$fileChanged"
|
||||||
|
|
||||||
|
// Ignore COMMIT_EDITMSG changes
|
||||||
|
if(isGitMessageFile(pathStr, fullPathOfFileChanged))
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
println("Has git dir changed: $hasGitDirectoryChanged")
|
println("Has git dir changed: $hasGitDirectoryChanged")
|
||||||
|
|
||||||
_changesNotifier.emit(hasGitDirectoryChanged)
|
_changesNotifier.emit(hasGitDirectoryChanged)
|
||||||
@ -86,4 +93,10 @@ class FileChangesWatcher @Inject constructor() {
|
|||||||
key.reset()
|
key.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isGitMessageFile(repoPath: String, fullPathOfFileChanged: String): Boolean {
|
||||||
|
val gitDir = "$repoPath$systemSeparator.git${systemSeparator}"
|
||||||
|
return fullPathOfFileChanged == "${gitDir}COMMIT_EDITMSG" ||
|
||||||
|
fullPathOfFileChanged == "${gitDir}MERGE_MSG"
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,12 +14,12 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
|
|
||||||
class LogManager @Inject constructor() {
|
class LogManager @Inject constructor() {
|
||||||
suspend fun loadLog(git: Git, currentBranch: Ref?, hasUncommitedChanges: Boolean) = withContext(Dispatchers.IO) {
|
suspend fun loadLog(git: Git, currentBranch: Ref?, hasUncommitedChanges: Boolean, commitsLimit: Int) = withContext(Dispatchers.IO) {
|
||||||
val commitList = GraphCommitList()
|
val commitList = GraphCommitList()
|
||||||
val repositoryState = git.repository.repositoryState
|
val repositoryState = git.repository.repositoryState
|
||||||
println("Repository state ${repositoryState.description}")
|
println("Repository state ${repositoryState.description}")
|
||||||
if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing
|
if (currentBranch != null || repositoryState.isRebasing) { // Current branch is null when there is no log (new repo) or rebasing
|
||||||
val logList = git.log().setMaxCount(2).call().toList()
|
val logList = git.log().setMaxCount(1).call().toList()
|
||||||
|
|
||||||
val walk = GraphWalk(git.repository)
|
val walk = GraphWalk(git.repository)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ class LogManager @Inject constructor() {
|
|||||||
commitList.addUncommitedChangesGraphCommit(logList.first())
|
commitList.addUncommitedChangesGraphCommit(logList.first())
|
||||||
|
|
||||||
commitList.source(walk)
|
commitList.source(walk)
|
||||||
commitList.fillTo(1000) // TODO: Limited commits to show to 1000, add a setting to let the user adjust this
|
commitList.fillTo(commitsLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureActive()
|
ensureActive()
|
||||||
|
@ -7,10 +7,14 @@ import org.eclipse.jgit.revwalk.RevCommit
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StashManager @Inject constructor() {
|
class StashManager @Inject constructor() {
|
||||||
suspend fun stash(git: Git) = withContext(Dispatchers.IO) {
|
suspend fun stash(git: Git, message: String?) = withContext(Dispatchers.IO) {
|
||||||
git
|
git
|
||||||
.stashCreate()
|
.stashCreate()
|
||||||
.setIncludeUntracked(true)
|
.setIncludeUntracked(true)
|
||||||
|
.apply {
|
||||||
|
if (message != null)
|
||||||
|
setWorkingDirectoryMessage(message)
|
||||||
|
}
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,15 +39,10 @@ class StatusManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
suspend fun stage(git: Git, statusEntry: StatusEntry) = withContext(Dispatchers.IO) {
|
||||||
if (statusEntry.statusType == StatusType.REMOVED) {
|
git.add()
|
||||||
git.rm()
|
.addFilepattern(statusEntry.filePath)
|
||||||
.addFilepattern(statusEntry.filePath)
|
.setUpdate(statusEntry.statusType == StatusType.REMOVED)
|
||||||
.call()
|
.call()
|
||||||
} else {
|
|
||||||
git.add()
|
|
||||||
.addFilepattern(statusEntry.filePath)
|
|
||||||
.call()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
|
||||||
@ -160,7 +155,7 @@ class StatusManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
splitted = splitted.mapIndexed { index, line ->
|
splitted = splitted.mapIndexed { index, line ->
|
||||||
val lineWithBreak = line +lineDelimiter.orEmpty()
|
val lineWithBreak = line + lineDelimiter.orEmpty()
|
||||||
|
|
||||||
if (index == splitted.count() - 1 && !content.endsWith(lineWithBreak)) {
|
if (index == splitted.count() - 1 && !content.endsWith(lineWithBreak)) {
|
||||||
line
|
line
|
||||||
@ -238,6 +233,12 @@ class StatusManager @Inject constructor(
|
|||||||
git
|
git
|
||||||
.add()
|
.add()
|
||||||
.addFilepattern(".")
|
.addFilepattern(".")
|
||||||
|
.setUpdate(true) // Modified and deleted files
|
||||||
|
.call()
|
||||||
|
git
|
||||||
|
.add()
|
||||||
|
.addFilepattern(".")
|
||||||
|
.setUpdate(false) // For newly added files
|
||||||
.call()
|
.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,38 +2,75 @@ package app.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val primaryLight = Color(0xFF9FD1FF)
|
val lightTheme = ColorsScheme(
|
||||||
val primary = Color(0xFF0070D8)
|
primary = Color(0xFF0070D8),
|
||||||
val primaryDark = Color(0xFF014F97)
|
primaryVariant = Color(0xFF0070D8),
|
||||||
val onPrimary = Color(0xFFFFFFFFF)
|
onPrimary = Color(0xFFFFFFFFF),
|
||||||
val secondaryLight = Color(0xFF9c27b0)
|
secondary = Color(0xFF9c27b0),
|
||||||
val secondaryDark = Color(0xFFe9c754)
|
primaryText = Color(0xFF212934),
|
||||||
val mainText = Color(0xFF212934)
|
secondaryText = Color(0xFF595858),
|
||||||
val mainTextDark = Color(0xFFFFFFFF)
|
error = Color(0xFFc93838),
|
||||||
val secondaryText = Color(0xFF595858)
|
onError = Color(0xFFFFFFFF),
|
||||||
val secondaryTextDark = Color(0xFFCCCBCB)
|
background = Color(0xFFFFFFFF),
|
||||||
val borderColorLight = Color(0xFF989898)
|
backgroundSelected = Color(0xC0cee1f2),
|
||||||
val borderColorDark = Color(0xFF989898)
|
surface = Color(0xFFe9ecf7),
|
||||||
val errorColor = Color(0xFFc93838)
|
headerBackground = Color(0xFFF4F6FA),
|
||||||
val onErrorColor = Color(0xFFFFFFFF)
|
borderColor = Color(0xFF989898),
|
||||||
|
graphHeaderBackground = Color(0xFFF4F6FA),
|
||||||
|
addFile = Color(0xFF32A852),
|
||||||
|
deletedFile = Color(0xFFc93838),
|
||||||
|
modifiedFile = Color(0xFF0070D8),
|
||||||
|
conflictingFile = Color(0xFFFFB638),
|
||||||
|
dialogOverlay = Color(0xAA000000),
|
||||||
|
normalScrollbar = Color(0xFFCCCCCC),
|
||||||
|
hoverScrollbar = Color(0xFF0070D8),
|
||||||
|
)
|
||||||
|
|
||||||
val backgroundColorLight = Color(0xFFFFFFFF)
|
|
||||||
val backgroundColorSelectedLight = Color(0xFFcee1f2)
|
|
||||||
val backgroundColorDark = Color(0xFF0E1621)
|
|
||||||
val backgroundColorSelectedDark = Color(0xFF2f3640)
|
|
||||||
val surfaceColorLight = Color(0xFFe9ecf7)
|
|
||||||
val surfaceColorDark = Color(0xFF182533)
|
|
||||||
val headerBackgroundLight = Color(0xFFF4F6FA)
|
|
||||||
val graphHeaderBackgroundDark = Color(0xFF303132)
|
|
||||||
val headerBackgroundDark = Color(0xFF0a2b4a)
|
|
||||||
|
|
||||||
val addFileLight = Color(0xFF32A852)
|
val darkBlueTheme = ColorsScheme(
|
||||||
val deleteFileLight = errorColor
|
primary = Color(0xFF014F97),
|
||||||
val modifyFileLight = primary
|
primaryVariant = Color(0xFF9FD1FF),
|
||||||
val conflictFileLight = Color(0xFFFFB638)
|
onPrimary = Color(0xFFFFFFFFF),
|
||||||
|
secondary = Color(0xFFe9c754),
|
||||||
|
primaryText = Color(0xFFFFFFFF),
|
||||||
|
secondaryText = Color(0xFFCCCBCB),
|
||||||
|
error = Color(0xFFc93838),
|
||||||
|
onError = Color(0xFFFFFFFF),
|
||||||
|
background = Color(0xFF0E1621),
|
||||||
|
backgroundSelected = Color(0xFF2f3640),
|
||||||
|
surface = Color(0xFF182533),
|
||||||
|
headerBackground = Color(0xFF0a335c),
|
||||||
|
borderColor = Color(0xFF989898),
|
||||||
|
graphHeaderBackground = Color(0xFF303132),
|
||||||
|
addFile = Color(0xFF32A852),
|
||||||
|
deletedFile = Color(0xFFc93838),
|
||||||
|
modifiedFile = Color(0xFF0070D8),
|
||||||
|
conflictingFile = Color(0xFFFFB638),
|
||||||
|
dialogOverlay = Color(0xAA000000),
|
||||||
|
normalScrollbar = Color(0xFFCCCCCC),
|
||||||
|
hoverScrollbar = Color(0xFF888888)
|
||||||
|
)
|
||||||
|
|
||||||
val dialogBackgroundColor = Color(0xAA000000)
|
val darkGrayTheme = ColorsScheme(
|
||||||
val unhoverScrollbarColorLight = Color.LightGray
|
primary = Color(0xFF014F97),
|
||||||
val unhoverScrollbarColorDark = Color.Gray
|
primaryVariant = Color(0xFFCDEAFF),
|
||||||
val hoverScrollbarColorLight = primary
|
onPrimary = Color(0xFFFFFFFFF),
|
||||||
val hoverScrollbarColorDark = Color.LightGray
|
secondary = Color(0xFFe9c754),
|
||||||
|
primaryText = Color(0xFFFFFFFF),
|
||||||
|
secondaryText = Color(0xFFCCCBCB),
|
||||||
|
error = Color(0xFFc93838),
|
||||||
|
onError = Color(0xFFFFFFFF),
|
||||||
|
background = Color(0xFF16181F),
|
||||||
|
backgroundSelected = Color(0xFF32373e),
|
||||||
|
surface = Color(0xFF212731),
|
||||||
|
headerBackground = Color(0xFF21303d),
|
||||||
|
borderColor = Color(0xFF989898),
|
||||||
|
graphHeaderBackground = Color(0xFF303132),
|
||||||
|
addFile = Color(0xFF32A852),
|
||||||
|
deletedFile = Color(0xFFc93838),
|
||||||
|
modifiedFile = Color(0xFF0070D8),
|
||||||
|
conflictingFile = Color(0xFFFFB638),
|
||||||
|
dialogOverlay = Color(0xAA000000),
|
||||||
|
normalScrollbar = Color(0xFFCCCCCC),
|
||||||
|
hoverScrollbar = Color(0xFF888888)
|
||||||
|
)
|
47
src/main/kotlin/app/theme/ColorsScheme.kt
Normal file
47
src/main/kotlin/app/theme/ColorsScheme.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package app.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.Colors
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
data class ColorsScheme(
|
||||||
|
val primary: Color,
|
||||||
|
val primaryVariant: Color,
|
||||||
|
val onPrimary: Color,
|
||||||
|
val secondary: Color,
|
||||||
|
val primaryText: Color,
|
||||||
|
val secondaryText: Color,
|
||||||
|
val error: Color,
|
||||||
|
val onError: Color,
|
||||||
|
val background: Color,
|
||||||
|
val backgroundSelected: Color,
|
||||||
|
val surface: Color,
|
||||||
|
val headerBackground: Color,
|
||||||
|
val onHeader: Color = primaryText,
|
||||||
|
val borderColor: Color,
|
||||||
|
val graphHeaderBackground: Color,
|
||||||
|
val addFile: Color,
|
||||||
|
val deletedFile: Color,
|
||||||
|
val modifiedFile: Color,
|
||||||
|
val conflictingFile: Color,
|
||||||
|
val dialogOverlay: Color,
|
||||||
|
val normalScrollbar: Color,
|
||||||
|
val hoverScrollbar: Color,
|
||||||
|
) {
|
||||||
|
fun toComposeColors(): Colors {
|
||||||
|
return Colors(
|
||||||
|
primary = this.primary,
|
||||||
|
primaryVariant = this.primaryVariant,
|
||||||
|
secondary = this.secondary,
|
||||||
|
secondaryVariant = this.secondary,
|
||||||
|
background = this.background,
|
||||||
|
surface = this.surface,
|
||||||
|
error = this.error,
|
||||||
|
onPrimary = this.onPrimary,
|
||||||
|
onSecondary = this.onPrimary,
|
||||||
|
onBackground = this.primaryText,
|
||||||
|
onSurface = this.primaryText,
|
||||||
|
onError = this.onError,
|
||||||
|
isLight = true, // todo what is this used for? Hardcoded value for now
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
29
src/main/kotlin/app/theme/ComponentsColors.kt
Normal file
29
src/main/kotlin/app/theme/ComponentsColors.kt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package app.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun textFieldColors() = TextFieldDefaults.textFieldColors(
|
||||||
|
cursorColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
focusedIndicatorColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
focusedLabelColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
backgroundColor = MaterialTheme.colors.background,
|
||||||
|
textColor = MaterialTheme.colors.primaryTextColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun outlinedTextFieldColors() = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
cursorColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
focusedBorderColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
focusedLabelColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
backgroundColor = MaterialTheme.colors.background,
|
||||||
|
textColor = MaterialTheme.colors.primaryTextColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun textButtonColors() = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = MaterialTheme.colors.primaryVariant
|
||||||
|
)
|
@ -1,44 +1,27 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package app.theme
|
package app.theme
|
||||||
|
|
||||||
import androidx.compose.material.Colors
|
import androidx.compose.material.Colors
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.darkColors
|
|
||||||
import androidx.compose.material.lightColors
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import app.DropDownOption
|
import app.DropDownOption
|
||||||
|
|
||||||
private val DarkColorPalette = darkColors(
|
private var appTheme: ColorsScheme = darkGrayTheme
|
||||||
primary = primaryLight,
|
|
||||||
primaryVariant = primaryDark,
|
|
||||||
secondary = secondaryDark,
|
|
||||||
surface = surfaceColorDark,
|
|
||||||
background = backgroundColorDark,
|
|
||||||
error = errorColor,
|
|
||||||
onError = onErrorColor,
|
|
||||||
onPrimary = onPrimary,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val LightColorPalette = lightColors(
|
|
||||||
primary = primary,
|
|
||||||
primaryVariant = primaryDark,
|
|
||||||
secondary = secondaryLight,
|
|
||||||
background = backgroundColorLight,
|
|
||||||
surface = surfaceColorLight,
|
|
||||||
error = errorColor,
|
|
||||||
onError = onErrorColor,
|
|
||||||
onPrimary = onPrimary,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppTheme(theme: Themes = Themes.LIGHT, content: @Composable() () -> Unit) {
|
fun AppTheme(selectedTheme: Themes = Themes.DARK, content: @Composable() () -> Unit) {
|
||||||
val colors = when (theme) {
|
val theme = when (selectedTheme) {
|
||||||
Themes.LIGHT -> LightColorPalette
|
Themes.LIGHT -> lightTheme
|
||||||
Themes.DARK -> DarkColorPalette
|
Themes.DARK -> darkBlueTheme
|
||||||
|
Themes.DARK_GRAY -> darkGrayTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appTheme = theme
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colors = colors,
|
colors = theme.toComposeColors(),
|
||||||
content = content,
|
content = content,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
)
|
)
|
||||||
@ -46,99 +29,71 @@ fun AppTheme(theme: Themes = Themes.LIGHT, content: @Composable() () -> Unit) {
|
|||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.backgroundSelected: Color
|
val Colors.backgroundSelected: Color
|
||||||
get() = if (isLight) backgroundColorSelectedLight else backgroundColorSelectedDark
|
get() = appTheme.backgroundSelected
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.primaryTextColor: Color
|
val Colors.primaryTextColor: Color
|
||||||
get() = if (isLight) mainText else mainTextDark
|
get() = appTheme.primaryText
|
||||||
|
|
||||||
@get:Composable
|
|
||||||
val Colors.halfPrimary: Color
|
|
||||||
get() = primary.copy(alpha = 0.8f)
|
|
||||||
|
|
||||||
@get:Composable
|
|
||||||
val Colors.inversePrimaryTextColor: Color
|
|
||||||
get() = if (isLight) mainTextDark else mainText
|
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.secondaryTextColor: Color
|
val Colors.secondaryTextColor: Color
|
||||||
get() = if (isLight) secondaryText else secondaryTextDark
|
get() = appTheme.secondaryText
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.borderColor: Color
|
val Colors.borderColor: Color
|
||||||
get() = if (isLight)
|
get() = appTheme.borderColor
|
||||||
borderColorLight
|
|
||||||
else
|
|
||||||
borderColorDark
|
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.headerBackground: Color
|
val Colors.headerBackground: Color
|
||||||
get() {
|
get() = appTheme.headerBackground
|
||||||
return if (isLight)
|
|
||||||
headerBackgroundLight
|
|
||||||
else
|
|
||||||
headerBackgroundDark
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.graphHeaderBackground: Color
|
val Colors.graphHeaderBackground: Color
|
||||||
get() {
|
get() = appTheme.graphHeaderBackground
|
||||||
return if (isLight)
|
|
||||||
headerBackgroundLight
|
|
||||||
else
|
|
||||||
graphHeaderBackgroundDark
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.addFile: Color
|
val Colors.addFile: Color
|
||||||
get() = addFileLight
|
get() = appTheme.addFile
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.deleteFile: Color
|
val Colors.deleteFile: Color
|
||||||
get() = deleteFileLight
|
get() = appTheme.deletedFile
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.modifyFile: Color
|
val Colors.modifyFile: Color
|
||||||
get() = modifyFileLight
|
get() = appTheme.modifiedFile
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.conflictFile: Color
|
val Colors.conflictFile: Color
|
||||||
get() = conflictFileLight
|
get() = appTheme.conflictingFile
|
||||||
|
|
||||||
@get:Composable
|
@get:Composable
|
||||||
val Colors.headerText: Color
|
val Colors.headerText: Color
|
||||||
get() = if (isLight) primary else mainTextDark
|
get() = appTheme.onHeader
|
||||||
|
|
||||||
|
|
||||||
val Colors.tabColorActive: Color
|
|
||||||
get() = if (isLight) surfaceColorLight else surfaceColorDark
|
|
||||||
|
|
||||||
|
|
||||||
val Colors.tabColorInactive: Color
|
|
||||||
get() = if (isLight) backgroundColorLight else backgroundColorDark
|
|
||||||
|
|
||||||
val Colors.stageButton: Color
|
val Colors.stageButton: Color
|
||||||
get() = if (isLight) primary else primaryDark
|
get() = appTheme.primary
|
||||||
|
|
||||||
val Colors.unstageButton: Color
|
val Colors.unstageButton: Color
|
||||||
get() = error
|
get() = appTheme.error
|
||||||
|
|
||||||
val Colors.abortButton: Color
|
val Colors.abortButton: Color
|
||||||
get() = error
|
get() = appTheme.error
|
||||||
|
|
||||||
val Colors.confirmationButton: Color
|
val Colors.scrollbarNormal: Color
|
||||||
get() = if (isLight) primary else primaryDark
|
get() = appTheme.normalScrollbar
|
||||||
|
|
||||||
val Colors.scrollbarUnhover: Color
|
|
||||||
get() = if (isLight) unhoverScrollbarColorLight else unhoverScrollbarColorDark
|
|
||||||
|
|
||||||
val Colors.scrollbarHover: Color
|
val Colors.scrollbarHover: Color
|
||||||
get() = if (isLight) hoverScrollbarColorLight else hoverScrollbarColorDark
|
get() = appTheme.hoverScrollbar
|
||||||
|
|
||||||
|
val Colors.dialogOverlay: Color
|
||||||
|
get() = appTheme.dialogOverlay
|
||||||
|
|
||||||
|
|
||||||
enum class Themes(val displayName: String) : DropDownOption {
|
enum class Themes(val displayName: String) : DropDownOption {
|
||||||
LIGHT("Light"),
|
LIGHT("Light"),
|
||||||
DARK("Dark");
|
DARK("Dark"),
|
||||||
|
DARK_GRAY("Dark gray");
|
||||||
|
|
||||||
override val optionName: String
|
override val optionName: String
|
||||||
get() = displayName
|
get() = displayName
|
||||||
@ -147,4 +102,5 @@ enum class Themes(val displayName: String) : DropDownOption {
|
|||||||
val themesList = listOf(
|
val themesList = listOf(
|
||||||
Themes.LIGHT,
|
Themes.LIGHT,
|
||||||
Themes.DARK,
|
Themes.DARK,
|
||||||
|
Themes.DARK_GRAY,
|
||||||
)
|
)
|
@ -61,7 +61,8 @@ fun AppTab(
|
|||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(linearProgressAlpha)
|
.alpha(linearProgressAlpha),
|
||||||
|
color = MaterialTheme.colors.primaryVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
CredentialsDialog(tabViewModel)
|
CredentialsDialog(tabViewModel)
|
||||||
|
@ -4,6 +4,7 @@ package app.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.selection.DisableSelection
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@ -11,10 +12,17 @@ import androidx.compose.material.IconButton
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -24,6 +32,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import app.extensions.handMouseClickable
|
import app.extensions.handMouseClickable
|
||||||
import app.extensions.lineAt
|
import app.extensions.lineAt
|
||||||
import app.extensions.toStringWithSpaces
|
import app.extensions.toStringWithSpaces
|
||||||
|
import app.theme.headerBackground
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
@ -37,7 +46,25 @@ fun Blame(
|
|||||||
onSelectCommit: (RevCommit) -> Unit,
|
onSelectCommit: (RevCommit) -> Unit,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusable()
|
||||||
|
.onPreviewKeyEvent {
|
||||||
|
if (it.key == Key.Escape) {
|
||||||
|
onClose()
|
||||||
|
true
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
},
|
||||||
|
) {
|
||||||
Header(filePath, onClose = onClose)
|
Header(filePath, onClose = onClose)
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
ScrollableLazyColumn(
|
ScrollableLazyColumn(
|
||||||
@ -130,7 +157,7 @@ fun MinimizedBlame(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.height(52.dp)
|
||||||
.background(MaterialTheme.colors.surface),
|
.background(MaterialTheme.colors.surface),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@ -181,9 +208,9 @@ private fun Header(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.height(40.dp)
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
.background(MaterialTheme.colors.headerBackground)
|
||||||
.background(MaterialTheme.colors.surface),
|
.padding(start = 8.dp, end = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -114,7 +114,7 @@ private fun BranchLineEntry(
|
|||||||
painter = painterResource("location.svg"),
|
painter = painterResource("location.svg"),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.padding(horizontal = 4.dp),
|
modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
tint = MaterialTheme.colors.primary,
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ fun CommitChanges(
|
|||||||
|
|
||||||
when (val commitChangesStatus = commitChangesStatusState.value) {
|
when (val commitChangesStatus = commitChangesStatusState.value) {
|
||||||
CommitChangesStatus.Loading -> {
|
CommitChangesStatus.Loading -> {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.primaryVariant)
|
||||||
}
|
}
|
||||||
is CommitChangesStatus.Loaded -> {
|
is CommitChangesStatus.Loaded -> {
|
||||||
CommitChangesView(
|
CommitChangesView(
|
||||||
@ -75,7 +75,7 @@ fun CommitChangesView(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp),
|
.padding(end = 8.dp),
|
||||||
) {
|
) {
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
@ -100,7 +100,7 @@ fun CommitChangesView(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f, fill = true)
|
.weight(1f, fill = true)
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
.padding(end = 8.dp, top = 8.dp, bottom = 8.dp)
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -4,6 +4,7 @@ package app.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@ -13,14 +14,17 @@ import androidx.compose.material.IconButton
|
|||||||
import androidx.compose.material.LinearProgressIndicator
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.res.loadImageBitmap
|
import androidx.compose.ui.res.loadImageBitmap
|
||||||
@ -38,6 +42,7 @@ import app.git.diff.DiffResult
|
|||||||
import app.git.diff.Hunk
|
import app.git.diff.Hunk
|
||||||
import app.git.diff.Line
|
import app.git.diff.Line
|
||||||
import app.git.diff.LineType
|
import app.git.diff.LineType
|
||||||
|
import app.theme.headerBackground
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.theme.stageButton
|
import app.theme.stageButton
|
||||||
import app.theme.unstageButton
|
import app.theme.unstageButton
|
||||||
@ -58,11 +63,25 @@ fun Diff(
|
|||||||
) {
|
) {
|
||||||
val diffResultState = diffViewModel.diffResult.collectAsState()
|
val diffResultState = diffViewModel.diffResult.collectAsState()
|
||||||
val viewDiffResult = diffResultState.value ?: return
|
val viewDiffResult = diffResultState.value ?: return
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusable()
|
||||||
|
.onPreviewKeyEvent {
|
||||||
|
if (it.key == Key.Escape) {
|
||||||
|
onCloseDiffView()
|
||||||
|
true
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
when (viewDiffResult) {
|
when (viewDiffResult) {
|
||||||
ViewDiffResult.DiffNotFound -> { onCloseDiffView() }
|
ViewDiffResult.DiffNotFound -> { onCloseDiffView() }
|
||||||
@ -97,7 +116,7 @@ fun Diff(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewDiffResult.Loading, ViewDiffResult.None -> {
|
ViewDiffResult.Loading, ViewDiffResult.None -> {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.primaryVariant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,9 +322,9 @@ fun DiffHeader(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.height(40.dp)
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
.background(MaterialTheme.colors.headerBackground)
|
||||||
.background(MaterialTheme.colors.surface),
|
.padding(start = 8.dp, end = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
val filePath = if (diffEntry.newPath != "/dev/null")
|
val filePath = if (diffEntry.newPath != "/dev/null")
|
||||||
@ -322,7 +341,7 @@ fun DiffHeader(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
if(diffEntryType is DiffEntryType.UncommitedDiff) {
|
if (diffEntryType is DiffEntryType.UncommitedDiff) {
|
||||||
val buttonText: String
|
val buttonText: String
|
||||||
val color: Color
|
val color: Color
|
||||||
|
|
||||||
@ -404,7 +423,10 @@ fun DiffLine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = line.text.replace("\t", " "), // this replace is a workaround until this issue gets fixed https://github.com/JetBrains/compose-jb/issues/615
|
text = line.text.replace(
|
||||||
|
"\t",
|
||||||
|
" "
|
||||||
|
), // TODO this replace is a workaround until this issue gets fixed https://github.com/JetBrains/compose-jb/issues/615
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp)
|
.padding(start = 8.dp)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
|
@ -4,20 +4,24 @@ package app.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -27,6 +31,7 @@ import app.extensions.handMouseClickable
|
|||||||
import app.extensions.toSmartSystemString
|
import app.extensions.toSmartSystemString
|
||||||
import app.extensions.toSystemDateTimeString
|
import app.extensions.toSystemDateTimeString
|
||||||
import app.git.diff.DiffResult
|
import app.git.diff.DiffResult
|
||||||
|
import app.theme.headerBackground
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.theme.secondaryTextColor
|
import app.theme.secondaryTextColor
|
||||||
import app.ui.components.AvatarImage
|
import app.ui.components.AvatarImage
|
||||||
@ -45,9 +50,24 @@ fun FileHistory(
|
|||||||
) {
|
) {
|
||||||
val historyState by historyViewModel.historyState.collectAsState()
|
val historyState by historyViewModel.historyState.collectAsState()
|
||||||
|
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusable()
|
||||||
|
.onKeyEvent {
|
||||||
|
if (it.key == Key.Escape) {
|
||||||
|
onClose()
|
||||||
|
true
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Header(filePath = historyState.filePath, onClose = onClose)
|
Header(filePath = historyState.filePath, onClose = onClose)
|
||||||
|
|
||||||
@ -67,9 +87,9 @@ private fun Header(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.height(40.dp)
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 8.dp)
|
.background(MaterialTheme.colors.headerBackground)
|
||||||
.background(MaterialTheme.colors.surface),
|
.padding(start = 8.dp, end = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -32,6 +32,7 @@ fun Menu(
|
|||||||
menuViewModel: MenuViewModel,
|
menuViewModel: MenuViewModel,
|
||||||
onRepositoryOpen: () -> Unit,
|
onRepositoryOpen: () -> Unit,
|
||||||
onCreateBranch: () -> Unit,
|
onCreateBranch: () -> Unit,
|
||||||
|
onStashWithMessage: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var showAdditionalOptionsDropDownMenu by remember { mutableStateOf(false) }
|
var showAdditionalOptionsDropDownMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ fun Menu(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
ExtendedMenuButton(
|
ExtendedMenuButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
title = "Pull",
|
title = "Pull",
|
||||||
icon = painterResource("download.svg"),
|
icon = painterResource("download.svg"),
|
||||||
onClick = { menuViewModel.pull() },
|
onClick = { menuViewModel.pull() },
|
||||||
@ -80,7 +82,7 @@ fun Menu(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
||||||
MenuButton(
|
MenuButton(
|
||||||
title = "Branch",
|
title = "Branch",
|
||||||
@ -90,12 +92,16 @@ fun Menu(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
||||||
MenuButton(
|
ExtendedMenuButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
title = "Stash",
|
title = "Stash",
|
||||||
icon = painterResource("stash.svg"),
|
icon = painterResource("stash.svg"),
|
||||||
onClick = { menuViewModel.stash() },
|
onClick = { menuViewModel.stash() },
|
||||||
|
extendedListItems = stashContextMenuItems(
|
||||||
|
onStashWithMessage = onStashWithMessage
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
MenuButton(
|
MenuButton(
|
||||||
@ -146,16 +152,15 @@ fun MenuButton(
|
|||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val iconColor = if (enabled) {
|
val iconColor = if (enabled) {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primaryVariant
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colors.secondaryVariant
|
MaterialTheme.colors.secondaryVariant //todo this color isn't specified anywhere
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(horizontal = 2.dp)
|
|
||||||
.handMouseClickable { if (enabled) onClick() }
|
.handMouseClickable { if (enabled) onClick() }
|
||||||
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(3.dp))
|
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(4.dp))
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@ -189,46 +194,42 @@ fun ExtendedMenuButton(
|
|||||||
extendedListItems: List<DropDownContentData>,
|
extendedListItems: List<DropDownContentData>,
|
||||||
) {
|
) {
|
||||||
val iconColor = if (enabled) {
|
val iconColor = if (enabled) {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primaryVariant
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colors.secondaryVariant
|
MaterialTheme.colors.secondaryVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
var showDropDownMenu by remember { mutableStateOf(false) }
|
var showDropDownMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
|
Row(modifier = modifier.height(IntrinsicSize.Min)) {
|
||||||
Box(
|
Row(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.handMouseClickable { if (enabled) onClick() }
|
.handMouseClickable { if (enabled) onClick() }
|
||||||
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(topStart = 3.dp, bottomStart = 3.dp))
|
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(topStart = 4.dp, bottomStart = 4.dp))
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Row(
|
Image(
|
||||||
horizontalArrangement = Arrangement.Center,
|
painter = icon,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
contentDescription = title,
|
||||||
) {
|
modifier = Modifier
|
||||||
Image(
|
.padding(horizontal = 4.dp)
|
||||||
painter = icon,
|
.size(24.dp),
|
||||||
contentDescription = title,
|
colorFilter = ColorFilter.tint(iconColor),
|
||||||
modifier = Modifier
|
)
|
||||||
.padding(horizontal = 4.dp)
|
Text(
|
||||||
.size(24.dp),
|
text = title,
|
||||||
colorFilter = ColorFilter.tint(iconColor),
|
fontSize = 12.sp,
|
||||||
)
|
color = MaterialTheme.colors.primaryTextColor
|
||||||
Text(
|
)
|
||||||
text = title,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = MaterialTheme.colors.primaryTextColor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp)
|
|
||||||
.width(20.dp)
|
.width(20.dp)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(topEnd = 3.dp, bottomEnd = 3.dp))
|
.border(ButtonDefaults.outlinedBorder, RoundedCornerShape(topEnd = 4.dp, bottomEnd = 4.dp))
|
||||||
.handMouseClickable {
|
.handMouseClickable {
|
||||||
showDropDownMenu = true
|
showDropDownMenu = true
|
||||||
},
|
},
|
||||||
@ -263,7 +264,7 @@ fun IconMenuButton(
|
|||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val iconColor = if (enabled) {
|
val iconColor = if (enabled) {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primaryVariant
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colors.secondaryVariant
|
MaterialTheme.colors.secondaryVariant
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.viewmodels.RebaseInteractiveState
|
import app.viewmodels.RebaseInteractiveState
|
||||||
@ -88,7 +90,8 @@ fun RebaseStateLoaded(
|
|||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
onCancel()
|
onCancel()
|
||||||
}
|
},
|
||||||
|
colors = textButtonColors(),
|
||||||
) {
|
) {
|
||||||
Text("Cancel")
|
Text("Cancel")
|
||||||
}
|
}
|
||||||
@ -139,7 +142,7 @@ fun RebaseCommit(
|
|||||||
newMessage = it
|
newMessage = it
|
||||||
onMessageChanged(it)
|
onMessageChanged(it)
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
colors = outlinedTextFieldColors(),
|
||||||
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package app.ui
|
package app.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@ -9,12 +10,17 @@ import androidx.compose.material.MaterialTheme
|
|||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.input.pointer.PointerIcon
|
||||||
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
import app.theme.borderColor
|
import app.theme.borderColor
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.ui.dialogs.NewBranchDialog
|
import app.ui.dialogs.NewBranchDialog
|
||||||
import app.ui.dialogs.RebaseInteractive
|
import app.ui.dialogs.RebaseInteractive
|
||||||
|
import app.ui.dialogs.StashWithMessageDialog
|
||||||
import app.ui.log.Log
|
import app.ui.log.Log
|
||||||
import app.viewmodels.BlameState
|
import app.viewmodels.BlameState
|
||||||
import app.viewmodels.TabViewModel
|
import app.viewmodels.TabViewModel
|
||||||
@ -23,7 +29,9 @@ import org.eclipse.jgit.lib.RepositoryState
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
|
||||||
import org.jetbrains.compose.splitpane.HorizontalSplitPane
|
import org.jetbrains.compose.splitpane.HorizontalSplitPane
|
||||||
|
import org.jetbrains.compose.splitpane.SplitterScope
|
||||||
import org.jetbrains.compose.splitpane.rememberSplitPaneState
|
import org.jetbrains.compose.splitpane.rememberSplitPaneState
|
||||||
|
import java.awt.Cursor
|
||||||
|
|
||||||
|
|
||||||
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
|
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
|
||||||
@ -36,6 +44,7 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
val showHistory by tabViewModel.showHistory.collectAsState()
|
val showHistory by tabViewModel.showHistory.collectAsState()
|
||||||
|
|
||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
|
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showNewBranchDialog) {
|
if (showNewBranchDialog) {
|
||||||
NewBranchDialog(
|
NewBranchDialog(
|
||||||
@ -47,6 +56,16 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
showNewBranchDialog = false
|
showNewBranchDialog = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else if (showStashWithMessageDialog) {
|
||||||
|
StashWithMessageDialog(
|
||||||
|
onReject = {
|
||||||
|
showStashWithMessageDialog = false
|
||||||
|
},
|
||||||
|
onAccept = { stashMessage ->
|
||||||
|
tabViewModel.menuViewModel.stashWithMessage(stashMessage)
|
||||||
|
showStashWithMessageDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@ -65,7 +84,8 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
onRepositoryOpen = {
|
onRepositoryOpen = {
|
||||||
openRepositoryDialog(tabViewModel = tabViewModel)
|
openRepositoryDialog(tabViewModel = tabViewModel)
|
||||||
},
|
},
|
||||||
onCreateBranch = { showNewBranchDialog = true }
|
onCreateBranch = { showNewBranchDialog = true },
|
||||||
|
onStashWithMessage = { showStashWithMessageDialog = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState, blameState, showHistory)
|
RepoContent(tabViewModel, diffSelected, selectedItem, repositoryState, blameState, showHistory)
|
||||||
@ -83,10 +103,10 @@ fun RepoContent(
|
|||||||
blameState: BlameState,
|
blameState: BlameState,
|
||||||
showHistory: Boolean,
|
showHistory: Boolean,
|
||||||
) {
|
) {
|
||||||
if(showHistory) {
|
if (showHistory) {
|
||||||
val historyViewModel = tabViewModel.historyViewModel
|
val historyViewModel = tabViewModel.historyViewModel
|
||||||
|
|
||||||
if(historyViewModel != null) {
|
if (historyViewModel != null) {
|
||||||
FileHistory(
|
FileHistory(
|
||||||
historyViewModel = historyViewModel,
|
historyViewModel = historyViewModel,
|
||||||
onClose = {
|
onClose = {
|
||||||
@ -117,11 +137,9 @@ fun MainContentView(
|
|||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
HorizontalSplitPane {
|
HorizontalSplitPane {
|
||||||
first(minSize = 200.dp) {
|
first(minSize = 250.dp) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(min = 300.dp)
|
|
||||||
.weight(0.15f)
|
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
) {
|
) {
|
||||||
Branches(
|
Branches(
|
||||||
@ -139,6 +157,10 @@ fun MainContentView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitter {
|
||||||
|
this.repositorySplitter()
|
||||||
|
}
|
||||||
|
|
||||||
second {
|
second {
|
||||||
HorizontalSplitPane(
|
HorizontalSplitPane(
|
||||||
splitPaneState = rememberSplitPaneState(0.9f)
|
splitPaneState = rememberSplitPaneState(0.9f)
|
||||||
@ -147,11 +169,6 @@ fun MainContentView(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.border(
|
|
||||||
width = 2.dp,
|
|
||||||
color = MaterialTheme.colors.borderColor,
|
|
||||||
shape = RoundedCornerShape(4.dp)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
if (blameState is BlameState.Loaded && !blameState.isMinimized) {
|
||||||
Blame(
|
Blame(
|
||||||
@ -191,6 +208,10 @@ fun MainContentView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitter {
|
||||||
|
this.repositorySplitter()
|
||||||
|
}
|
||||||
|
|
||||||
second(minSize = 300.dp) {
|
second(minSize = 300.dp) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -246,6 +267,27 @@ fun MainContentView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SplitterScope.repositorySplitter() {
|
||||||
|
visiblePart {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.width(8.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(Color.Transparent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.markAsHandle()
|
||||||
|
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
|
||||||
|
.background(Color.Transparent)
|
||||||
|
.width(8.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class SelectedItem {
|
sealed class SelectedItem {
|
||||||
object None : SelectedItem()
|
object None : SelectedItem()
|
||||||
object UncommitedChanges : SelectedItem()
|
object UncommitedChanges : SelectedItem()
|
||||||
|
@ -40,6 +40,7 @@ import app.ui.components.SecondaryButton
|
|||||||
import app.ui.context_menu.*
|
import app.ui.context_menu.*
|
||||||
import app.viewmodels.StageStatus
|
import app.viewmodels.StageStatus
|
||||||
import app.viewmodels.StatusViewModel
|
import app.viewmodels.StatusViewModel
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -53,7 +54,7 @@ fun UncommitedChanges(
|
|||||||
onHistoryFile: (String) -> Unit,
|
onHistoryFile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
val stageStatusState = statusViewModel.stageStatus.collectAsState()
|
||||||
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage) }
|
var commitMessage by remember { mutableStateOf(statusViewModel.savedCommitMessage.message) }
|
||||||
|
|
||||||
val stageStatus = stageStatusState.value
|
val stageStatus = stageStatusState.value
|
||||||
val staged: List<StatusEntry>
|
val staged: List<StatusEntry>
|
||||||
@ -70,31 +71,36 @@ fun UncommitedChanges(
|
|||||||
val doCommit = { amend: Boolean ->
|
val doCommit = { amend: Boolean ->
|
||||||
statusViewModel.commit(commitMessage, amend)
|
statusViewModel.commit(commitMessage, amend)
|
||||||
onStagedDiffEntrySelected(null)
|
onStagedDiffEntrySelected(null)
|
||||||
statusViewModel.savedCommitMessage = ""
|
|
||||||
commitMessage = ""
|
commitMessage = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val canCommit = commitMessage.isNotEmpty() && staged.isNotEmpty()
|
val canCommit = commitMessage.isNotEmpty() && staged.isNotEmpty()
|
||||||
val canAmend = (commitMessage.isNotEmpty() || staged.isNotEmpty()) && statusViewModel.hasPreviousCommits
|
val canAmend = (commitMessage.isNotEmpty() || staged.isNotEmpty()) && statusViewModel.hasPreviousCommits
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
statusViewModel.commitMessageChangesFlow.collect { newCommitMessage ->
|
||||||
|
commitMessage = newCommitMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = stageStatus is StageStatus.Loading,
|
visible = stageStatus is StageStatus.Loading,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.primaryVariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
EntriesList(
|
EntriesList(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp, end = 8.dp, bottom = 4.dp)
|
.padding(end = 8.dp, bottom = 4.dp)
|
||||||
.weight(5f)
|
.weight(5f)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
title = "Staged",
|
title = "Staged",
|
||||||
allActionTitle = "Unstage all",
|
allActionTitle = "Unstage all",
|
||||||
actionTitle = "Unstage",
|
actionTitle = "Unstage",
|
||||||
selectedEntryType = if(selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
selectedEntryType = if (selectedEntryType is DiffEntryType.StagedDiff) selectedEntryType else null,
|
||||||
actionColor = MaterialTheme.colors.unstageButton,
|
actionColor = MaterialTheme.colors.unstageButton,
|
||||||
statusEntries = staged,
|
statusEntries = staged,
|
||||||
onDiffEntrySelected = onStagedDiffEntrySelected,
|
onDiffEntrySelected = onStagedDiffEntrySelected,
|
||||||
@ -117,12 +123,12 @@ fun UncommitedChanges(
|
|||||||
|
|
||||||
EntriesList(
|
EntriesList(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 4.dp)
|
.padding(end = 8.dp, top = 8.dp)
|
||||||
.weight(5f)
|
.weight(5f)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
title = "Unstaged",
|
title = "Unstaged",
|
||||||
actionTitle = "Stage",
|
actionTitle = "Stage",
|
||||||
selectedEntryType = if(selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
selectedEntryType = if (selectedEntryType is DiffEntryType.UnstagedDiff) selectedEntryType else null,
|
||||||
actionColor = MaterialTheme.colors.stageButton,
|
actionColor = MaterialTheme.colors.stageButton,
|
||||||
statusEntries = unstaged,
|
statusEntries = unstaged,
|
||||||
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
onDiffEntrySelected = onUnstagedDiffEntrySelected,
|
||||||
@ -149,7 +155,7 @@ fun UncommitedChanges(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(top = 8.dp, bottom = 8.dp, end = 8.dp)
|
||||||
.run {
|
.run {
|
||||||
// When rebasing, we don't need a fixed size as we don't show the message TextField
|
// When rebasing, we don't need a fixed size as we don't show the message TextField
|
||||||
if (!repositoryState.isRebasing) {
|
if (!repositoryState.isRebasing) {
|
||||||
@ -175,23 +181,30 @@ fun UncommitedChanges(
|
|||||||
value = commitMessage,
|
value = commitMessage,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
commitMessage = it
|
commitMessage = it
|
||||||
statusViewModel.savedCommitMessage = it
|
|
||||||
|
statusViewModel.updateCommitMessage(it)
|
||||||
},
|
},
|
||||||
label = { Text("Write your commit message here", fontSize = 14.sp) },
|
label = { Text("Write your commit message here", fontSize = 14.sp) },
|
||||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
colors = textFieldColors(),
|
||||||
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
)
|
)
|
||||||
|
|
||||||
when {
|
when {
|
||||||
repositoryState.isMerging -> MergeButtons(
|
repositoryState.isMerging -> MergeButtons(
|
||||||
haveConflictsBeenSolved = unstaged.isEmpty(),
|
haveConflictsBeenSolved = unstaged.isEmpty(),
|
||||||
onAbort = { statusViewModel.abortMerge() },
|
onAbort = {
|
||||||
|
statusViewModel.abortMerge()
|
||||||
|
statusViewModel.updateCommitMessage("")
|
||||||
|
},
|
||||||
onMerge = { doCommit(false) }
|
onMerge = { doCommit(false) }
|
||||||
)
|
)
|
||||||
repositoryState.isRebasing -> RebasingButtons(
|
repositoryState.isRebasing -> RebasingButtons(
|
||||||
canContinue = staged.isNotEmpty() || unstaged.isNotEmpty(),
|
canContinue = staged.isNotEmpty() || unstaged.isNotEmpty(),
|
||||||
haveConflictsBeenSolved = unstaged.isEmpty(),
|
haveConflictsBeenSolved = unstaged.isEmpty(),
|
||||||
onAbort = { statusViewModel.abortRebase() },
|
onAbort = {
|
||||||
|
statusViewModel.abortRebase()
|
||||||
|
statusViewModel.updateCommitMessage("")
|
||||||
|
},
|
||||||
onContinue = { statusViewModel.continueRebase() },
|
onContinue = { statusViewModel.continueRebase() },
|
||||||
onSkip = { statusViewModel.skipRebase() },
|
onSkip = { statusViewModel.skipRebase() },
|
||||||
)
|
)
|
||||||
@ -237,7 +250,7 @@ fun UncommitedChangesButtons(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
.clip(MaterialTheme.shapes.small.copy(topStart = CornerSize(0.dp), bottomStart = CornerSize(0.dp)))
|
.clip(MaterialTheme.shapes.small.copy(topStart = CornerSize(0.dp), bottomStart = CornerSize(0.dp)))
|
||||||
.background(MaterialTheme.colors.confirmationButton)
|
.background(MaterialTheme.colors.primary)
|
||||||
.handMouseClickable { showDropDownMenu = true }
|
.handMouseClickable { showDropDownMenu = true }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -367,7 +380,7 @@ fun ConfirmationButton(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
backgroundColor = MaterialTheme.colors.confirmationButton,
|
backgroundColor = MaterialTheme.colors.primary,
|
||||||
contentColor = Color.White
|
contentColor = Color.White
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -502,7 +515,7 @@ private fun FileEntry(
|
|||||||
tint = statusEntry.iconColor,
|
tint = statusEntry.iconColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
if(statusEntry.parentDirectoryPath.isNotEmpty()) {
|
if (statusEntry.parentDirectoryPath.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = statusEntry.parentDirectoryPath,
|
text = statusEntry.parentDirectoryPath,
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
|
@ -26,6 +26,7 @@ import app.extensions.dirPath
|
|||||||
import app.extensions.openUrlInBrowser
|
import app.extensions.openUrlInBrowser
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.theme.secondaryTextColor
|
import app.theme.secondaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.dialogs.AppInfoDialog
|
import app.ui.dialogs.AppInfoDialog
|
||||||
import app.ui.dialogs.CloneDialog
|
import app.ui.dialogs.CloneDialog
|
||||||
import app.updates.Update
|
import app.updates.Update
|
||||||
@ -225,13 +226,13 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
tabViewModel.openRepository(repo)
|
tabViewModel.openRepository(repo)
|
||||||
}
|
},
|
||||||
|
colors = textButtonColors(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = repoDirName,
|
text = repoDirName,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
color = MaterialTheme.colors.primary,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,12 +271,13 @@ fun ButtonTile(
|
|||||||
.size(24.dp),
|
.size(24.dp),
|
||||||
painter = painter,
|
painter = painter,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryVariant),
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
color = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,12 +288,13 @@ fun IconTextButton(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
painter: Painter,
|
painter: Painter,
|
||||||
iconColor: Color = MaterialTheme.colors.primary,
|
iconColor: Color = MaterialTheme.colors.primaryVariant,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier.size(width = 280.dp, height = 40.dp)
|
modifier = modifier.size(width = 280.dp, height = 40.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
@ -21,7 +21,7 @@ fun PrimaryButton(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
backgroundColor = MaterialTheme.colors.primaryVariant,
|
backgroundColor = MaterialTheme.colors.primary,
|
||||||
contentColor = textColor
|
contentColor = textColor
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
@ -28,8 +28,6 @@ import app.di.AppComponent
|
|||||||
import app.di.DaggerTabComponent
|
import app.di.DaggerTabComponent
|
||||||
import app.extensions.handMouseClickable
|
import app.extensions.handMouseClickable
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.theme.tabColorActive
|
|
||||||
import app.theme.tabColorInactive
|
|
||||||
import app.viewmodels.TabViewModel
|
import app.viewmodels.TabViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
@ -44,9 +42,7 @@ fun RepositoriesTabPanel(
|
|||||||
onTabClosed: (Int) -> Unit,
|
onTabClosed: (Int) -> Unit,
|
||||||
newTabContent: (key: Int) -> TabInformation,
|
newTabContent: (key: Int) -> TabInformation,
|
||||||
) {
|
) {
|
||||||
var tabsIdentifier by remember {
|
var tabsIdentifier by remember { mutableStateOf(tabs.count()) }
|
||||||
mutableStateOf(tabs.count())
|
|
||||||
}
|
|
||||||
|
|
||||||
TabPanel(
|
TabPanel(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -123,7 +119,7 @@ fun TabPanel(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colors.primary
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,9 +134,9 @@ fun Tab(title: MutableState<String>, selected: Boolean, onClick: () -> Unit, onC
|
|||||||
0.dp
|
0.dp
|
||||||
Box {
|
Box {
|
||||||
val backgroundColor = if (selected)
|
val backgroundColor = if (selected)
|
||||||
MaterialTheme.colors.tabColorActive
|
MaterialTheme.colors.surface
|
||||||
else
|
else
|
||||||
MaterialTheme.colors.tabColorInactive
|
MaterialTheme.colors.background
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -16,7 +16,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.theme.scrollbarHover
|
import app.theme.scrollbarHover
|
||||||
import app.theme.scrollbarUnhover
|
import app.theme.scrollbarNormal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScrollableLazyColumn(
|
fun ScrollableLazyColumn(
|
||||||
@ -35,9 +35,9 @@ fun ScrollableLazyColumn(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterEnd)
|
.align(Alignment.CenterEnd)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(end = 4.dp),
|
.padding(end = 2.dp),
|
||||||
style = LocalScrollbarStyle.current.copy(
|
style = LocalScrollbarStyle.current.copy(
|
||||||
unhoverColor = MaterialTheme.colors.scrollbarUnhover,
|
unhoverColor = MaterialTheme.colors.scrollbarNormal,
|
||||||
hoverColor = MaterialTheme.colors.scrollbarHover,
|
hoverColor = MaterialTheme.colors.scrollbarHover,
|
||||||
),
|
),
|
||||||
adapter = rememberScrollbarAdapter(
|
adapter = rememberScrollbarAdapter(
|
||||||
|
@ -47,7 +47,7 @@ fun SideMenuSubentry(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.size(16.dp),
|
.size(16.dp),
|
||||||
tint = MaterialTheme.colors.primary,
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
@ -26,7 +26,7 @@ fun TextLink(
|
|||||||
val textColor = if (isHovered == colorsInverted) {
|
val textColor = if (isHovered == colorsInverted) {
|
||||||
MaterialTheme.colors.primaryTextColor
|
MaterialTheme.colors.primaryTextColor
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colors.primary
|
MaterialTheme.colors.primaryVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
15
src/main/kotlin/app/ui/context_menu/StashContextMenu.kt
Normal file
15
src/main/kotlin/app/ui/context_menu/StashContextMenu.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package app.ui.context_menu
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
fun stashContextMenuItems(
|
||||||
|
onStashWithMessage: () -> Unit,
|
||||||
|
): List<DropDownContentData> {
|
||||||
|
return mutableListOf(
|
||||||
|
DropDownContentData(
|
||||||
|
label = "Stash with message",
|
||||||
|
onClick = onStashWithMessage,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
@ -14,6 +14,7 @@ import app.AppConstants
|
|||||||
import app.AppConstants.openSourceProjects
|
import app.AppConstants.openSourceProjects
|
||||||
import app.Project
|
import app.Project
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.ScrollableLazyColumn
|
import app.ui.components.ScrollableLazyColumn
|
||||||
import app.ui.components.TextLink
|
import app.ui.components.TextLink
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ import app.ui.components.TextLink
|
|||||||
fun AppInfoDialog(
|
fun AppInfoDialog(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onClose) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(600.dp)
|
.width(600.dp)
|
||||||
@ -64,7 +65,8 @@ fun AppInfoDialog(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 16.dp, end = 8.dp)
|
.padding(top = 16.dp, end = 8.dp)
|
||||||
.align(Alignment.End),
|
.align(Alignment.End),
|
||||||
onClick = onClose
|
onClick = onClose,
|
||||||
|
colors = textButtonColors(),
|
||||||
) {
|
) {
|
||||||
Text("Close")
|
Text("Close")
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,9 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import app.git.CloneStatus
|
import app.git.CloneStatus
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
import app.viewmodels.CloneViewModel
|
import app.viewmodels.CloneViewModel
|
||||||
import openDirectoryDialog
|
import openDirectoryDialog
|
||||||
@ -33,7 +35,7 @@ fun CloneDialog(
|
|||||||
val cloneStatus = cloneViewModel.cloneStatus.collectAsState()
|
val cloneStatus = cloneViewModel.cloneStatus.collectAsState()
|
||||||
val cloneStatusValue = cloneStatus.value
|
val cloneStatusValue = cloneStatus.value
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onClose) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(400.dp)
|
.width(400.dp)
|
||||||
@ -106,6 +108,7 @@ private fun CloneInput(
|
|||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
value = url,
|
value = url,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
cloneViewModel.resetStateIfError()
|
cloneViewModel.resetStateIfError()
|
||||||
url = it
|
url = it
|
||||||
@ -131,6 +134,7 @@ private fun CloneInput(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
label = { Text("Directory") },
|
label = { Text("Directory") },
|
||||||
value = directory,
|
value = directory,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
cloneViewModel.resetStateIfError()
|
cloneViewModel.resetStateIfError()
|
||||||
directory = it
|
directory = it
|
||||||
@ -190,6 +194,7 @@ private fun CloneInput(
|
|||||||
previous = cloneButtonFocusRequester
|
previous = cloneButtonFocusRequester
|
||||||
next = urlFocusRequester
|
next = urlFocusRequester
|
||||||
},
|
},
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
@ -248,6 +253,7 @@ private fun Cloning(cloneViewModel: CloneViewModel, cloneStatusValue: CloneStatu
|
|||||||
end = 8.dp
|
end = 8.dp
|
||||||
)
|
)
|
||||||
.align(Alignment.End),
|
.align(Alignment.End),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
cloneViewModel.cancelClone()
|
cloneViewModel.cancelClone()
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package app.ui.dialogs
|
package app.ui.dialogs
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
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.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Clear
|
import androidx.compose.material.icons.filled.Clear
|
||||||
@ -19,9 +17,7 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.extensions.handMouseClickable
|
import app.extensions.handMouseClickable
|
||||||
import app.theme.borderColor
|
import app.theme.*
|
||||||
import app.theme.primaryTextColor
|
|
||||||
import app.theme.secondaryTextColor
|
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
import app.viewmodels.RemotesViewModel
|
import app.viewmodels.RemotesViewModel
|
||||||
import org.eclipse.jgit.transport.RemoteConfig
|
import org.eclipse.jgit.transport.RemoteConfig
|
||||||
@ -70,6 +66,7 @@ fun EditRemotesDialog(
|
|||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
paddingVertical = 8.dp,
|
paddingVertical = 8.dp,
|
||||||
paddingHorizontal = 16.dp,
|
paddingHorizontal = 16.dp,
|
||||||
|
onCloseRequested = onDismiss
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -104,11 +101,6 @@ fun EditRemotesDialog(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 8.dp)
|
.padding(bottom = 8.dp)
|
||||||
.border(
|
|
||||||
width = 1.dp,
|
|
||||||
shape = RoundedCornerShape(5.dp),
|
|
||||||
color = MaterialTheme.colors.borderColor,
|
|
||||||
)
|
|
||||||
.background(MaterialTheme.colors.surface)
|
.background(MaterialTheme.colors.surface)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@ -230,6 +222,7 @@ fun EditRemotesDialog(
|
|||||||
},
|
},
|
||||||
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
@ -251,6 +244,7 @@ fun EditRemotesDialog(
|
|||||||
},
|
},
|
||||||
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
@ -271,6 +265,7 @@ fun EditRemotesDialog(
|
|||||||
},
|
},
|
||||||
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(color = MaterialTheme.colors.primaryTextColor),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
@ -289,6 +284,7 @@ fun EditRemotesDialog(
|
|||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
enabled = remoteChanged,
|
enabled = remoteChanged,
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
remotesEditorData = remotesEditorData.copy(
|
remotesEditorData = remotesEditorData.copy(
|
||||||
selectedRemote = selectedRemote.copy(
|
selectedRemote = selectedRemote.copy(
|
||||||
|
@ -1,25 +1,37 @@
|
|||||||
|
@file:OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
|
||||||
package app.ui.dialogs
|
package app.ui.dialogs
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupPositionProvider
|
import androidx.compose.ui.window.PopupPositionProvider
|
||||||
import app.theme.dialogBackgroundColor
|
import app.theme.dialogOverlay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MaterialDialog(
|
fun MaterialDialog(
|
||||||
alignment: Alignment = Alignment.Center,
|
alignment: Alignment = Alignment.Center,
|
||||||
paddingHorizontal: Dp = 16.dp,
|
paddingHorizontal: Dp = 16.dp,
|
||||||
paddingVertical: Dp = 16.dp,
|
paddingVertical: Dp = 16.dp,
|
||||||
|
onCloseRequested: () -> Unit = {},
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
Popup(
|
Popup(
|
||||||
@ -33,10 +45,26 @@ fun MaterialDialog(
|
|||||||
): IntOffset = IntOffset.Zero
|
): IntOffset = IntOffset.Zero
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(dialogBackgroundColor),
|
.background(MaterialTheme.colors.dialogOverlay)
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.focusable()
|
||||||
|
.onPreviewKeyEvent {
|
||||||
|
if (it.key == Key.Escape) {
|
||||||
|
onCloseRequested()
|
||||||
|
true
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
},
|
||||||
contentAlignment = alignment,
|
contentAlignment = alignment,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.ui.input.pointer.isPrimaryPressed
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ -28,7 +29,7 @@ fun MergeDialog(
|
|||||||
) {
|
) {
|
||||||
var fastForwardCheck by remember { mutableStateOf(fastForward) }
|
var fastForwardCheck by remember { mutableStateOf(fastForward) }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -89,6 +90,7 @@ fun MergeDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@ -28,7 +30,7 @@ fun NewBranchDialog(
|
|||||||
val branchFieldFocusRequester = remember { FocusRequester() }
|
val branchFieldFocusRequester = remember { FocusRequester() }
|
||||||
val buttonFieldFocusRequester = remember { FocusRequester() }
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -42,7 +44,7 @@ fun NewBranchDialog(
|
|||||||
}
|
}
|
||||||
.width(300.dp)
|
.width(300.dp)
|
||||||
.onPreviewKeyEvent {
|
.onPreviewKeyEvent {
|
||||||
if (it.key == Key.Enter) {
|
if (it.key == Key.Enter && branchField.isNotBlank()) {
|
||||||
onAccept(branchField)
|
onAccept(branchField)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@ -53,6 +55,7 @@ fun NewBranchDialog(
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
label = { Text("New branch name", fontSize = 14.sp) },
|
label = { Text("New branch name", fontSize = 14.sp) },
|
||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
branchField = it
|
branchField = it
|
||||||
},
|
},
|
||||||
@ -64,6 +67,7 @@ fun NewBranchDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
@ -75,7 +79,7 @@ fun NewBranchDialog(
|
|||||||
this.previous = branchFieldFocusRequester
|
this.previous = branchFieldFocusRequester
|
||||||
this.next = branchFieldFocusRequester
|
this.next = branchFieldFocusRequester
|
||||||
},
|
},
|
||||||
enabled = branchField.isNotEmpty(),
|
enabled = branchField.isNotBlank(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onAccept(branchField)
|
onAccept(branchField)
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,9 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@ -28,7 +30,7 @@ fun NewTagDialog(
|
|||||||
val tagFieldFocusRequester = remember { FocusRequester() }
|
val tagFieldFocusRequester = remember { FocusRequester() }
|
||||||
val buttonFieldFocusRequester = remember { FocusRequester() }
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -42,7 +44,7 @@ fun NewTagDialog(
|
|||||||
}
|
}
|
||||||
.width(300.dp)
|
.width(300.dp)
|
||||||
.onPreviewKeyEvent {
|
.onPreviewKeyEvent {
|
||||||
if (it.key == Key.Enter) {
|
if (it.key == Key.Enter && tagField.isBlank()) {
|
||||||
onAccept(tagField)
|
onAccept(tagField)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@ -53,6 +55,7 @@ fun NewTagDialog(
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
label = { Text("New tag name", fontSize = 14.sp) },
|
label = { Text("New tag name", fontSize = 14.sp) },
|
||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
tagField = it
|
tagField = it
|
||||||
},
|
},
|
||||||
@ -64,6 +67,7 @@ fun NewTagDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
@ -75,7 +79,7 @@ fun NewTagDialog(
|
|||||||
this.previous = tagFieldFocusRequester
|
this.previous = tagFieldFocusRequester
|
||||||
this.next = tagFieldFocusRequester
|
this.next = tagFieldFocusRequester
|
||||||
},
|
},
|
||||||
enabled = tagField.isNotEmpty(),
|
enabled = tagField.isBlank(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onAccept(tagField)
|
onAccept(tagField)
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,7 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ fun PasswordDialog(
|
|||||||
val passwordFieldFocusRequester = remember { FocusRequester() }
|
val passwordFieldFocusRequester = remember { FocusRequester() }
|
||||||
val buttonFieldFocusRequester = remember { FocusRequester() }
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -61,6 +62,7 @@ fun PasswordDialog(
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
label = { Text("Password", fontSize = 14.sp) },
|
label = { Text("Password", fontSize = 14.sp) },
|
||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
passwordField = it
|
passwordField = it
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ -25,7 +26,7 @@ fun RebaseDialog(
|
|||||||
onReject: () -> Unit,
|
onReject: () -> Unit,
|
||||||
onAccept: () -> Unit
|
onAccept: () -> Unit
|
||||||
) {
|
) {
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -67,6 +68,7 @@ fun RebaseDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.git.ResetType
|
import app.git.ResetType
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -26,7 +27,7 @@ fun ResetBranchDialog(
|
|||||||
) {
|
) {
|
||||||
var resetType by remember { mutableStateOf(ResetType.MIXED) }
|
var resetType by remember { mutableStateOf(ResetType.MIXED) }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -60,6 +61,7 @@ fun ResetBranchDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
package app.ui.dialogs
|
package app.ui.dialogs
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import app.AppPreferences
|
import app.AppPreferences
|
||||||
import app.DropDownOption
|
import app.DropDownOption
|
||||||
import app.theme.Themes
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.theme.themesList
|
import app.theme.themesList
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDialog(
|
fun SettingsDialog(
|
||||||
@ -18,16 +25,30 @@ fun SettingsDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val currentTheme by appPreferences.themeState.collectAsState()
|
val currentTheme by appPreferences.themeState.collectAsState()
|
||||||
|
val commitsLimitEnabled by appPreferences.commitsLimitEnabledFlow.collectAsState()
|
||||||
|
var commitsLimit by remember { mutableStateOf(appPreferences.commitsLimit) }
|
||||||
|
|
||||||
MaterialDialog {
|
MaterialDialog(
|
||||||
Column(modifier = Modifier.width(500.dp)) {
|
onCloseRequested = {
|
||||||
|
savePendingSettings(
|
||||||
|
appPreferences = appPreferences,
|
||||||
|
commitsLimit = commitsLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.width(720.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Settings",
|
text = "Settings",
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp, start = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingDropDown(
|
SettingDropDown(
|
||||||
title = "Theme",
|
title = "Theme",
|
||||||
|
subtitle = "Select the UI theme between light and dark mode",
|
||||||
dropDownOptions = themesList,
|
dropDownOptions = themesList,
|
||||||
currentOption = currentTheme,
|
currentOption = currentTheme,
|
||||||
onOptionSelected = { theme ->
|
onOptionSelected = { theme ->
|
||||||
@ -35,11 +56,38 @@ fun SettingsDialog(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SettingToogle(
|
||||||
|
title = "Limit log commits",
|
||||||
|
subtitle = "Turning off this may affect the performance",
|
||||||
|
value = commitsLimitEnabled,
|
||||||
|
onValueChanged = { value ->
|
||||||
|
appPreferences.commitsLimitEnabled = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingIntInput(
|
||||||
|
title = "Max commits",
|
||||||
|
subtitle = "Increasing this value may affect the performance",
|
||||||
|
value = commitsLimit,
|
||||||
|
enabled = commitsLimitEnabled,
|
||||||
|
onValueChanged = { value ->
|
||||||
|
commitsLimit = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp)
|
.padding(end = 8.dp)
|
||||||
.align(Alignment.End),
|
.align(Alignment.End),
|
||||||
onClick = onDismiss
|
colors = textButtonColors(),
|
||||||
|
onClick = {
|
||||||
|
savePendingSettings(
|
||||||
|
appPreferences = appPreferences,
|
||||||
|
commitsLimit = commitsLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Text("Close")
|
Text("Close")
|
||||||
}
|
}
|
||||||
@ -47,9 +95,19 @@ fun SettingsDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun savePendingSettings(
|
||||||
|
appPreferences: AppPreferences,
|
||||||
|
commitsLimit: Int,
|
||||||
|
) {
|
||||||
|
if (appPreferences.commitsLimit != commitsLimit) {
|
||||||
|
appPreferences.commitsLimit = commitsLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T: DropDownOption> SettingDropDown(
|
fun <T : DropDownOption> SettingDropDown(
|
||||||
title: String,
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
dropDownOptions: List<T>,
|
dropDownOptions: List<T>,
|
||||||
onOptionSelected: (T) -> Unit,
|
onOptionSelected: (T) -> Unit,
|
||||||
currentOption: T,
|
currentOption: T,
|
||||||
@ -59,16 +117,28 @@ fun <T: DropDownOption> SettingDropDown(
|
|||||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(verticalArrangement = Arrangement.Center) {
|
||||||
text = title,
|
Text(
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
text = title,
|
||||||
)
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Box {
|
Box {
|
||||||
OutlinedButton(onClick = { showThemeDropdown = true }) {
|
OutlinedButton(onClick = { showThemeDropdown = true }) {
|
||||||
Text(
|
Text(
|
||||||
currentOption.optionName,
|
text = currentOption.optionName,
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 14.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,46 +163,111 @@ fun <T: DropDownOption> SettingDropDown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T: DropDownOption> SettingTextInput(
|
fun SettingToogle(
|
||||||
title: String,
|
title: String,
|
||||||
dropDownOptions: List<T>,
|
subtitle: String,
|
||||||
onOptionSelected: (T) -> Unit,
|
value: Boolean,
|
||||||
currentOption: T,
|
onValueChanged: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
var showThemeDropdown by remember { mutableStateOf(false) }
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = title,
|
verticalArrangement = Arrangement.Center
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
) {
|
||||||
)
|
Text(
|
||||||
Spacer(modifier = Modifier.width(300.dp))
|
text = title,
|
||||||
Box {
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
OutlinedButton(onClick = { showThemeDropdown = true }) {
|
fontSize = 16.sp,
|
||||||
Text(
|
)
|
||||||
currentOption.optionName,
|
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = showThemeDropdown,
|
|
||||||
onDismissRequest = { showThemeDropdown = false },
|
|
||||||
) {
|
|
||||||
for (dropDownOption in dropDownOptions) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
showThemeDropdown = false
|
|
||||||
onOptionSelected(dropDownOption)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(dropDownOption.optionName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Switch(value, onCheckedChange = onValueChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingIntInput(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
value: Int,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onValueChanged: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
var text by remember {
|
||||||
|
mutableStateOf(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
var isError by remember { mutableStateOf(false) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = text,
|
||||||
|
modifier = Modifier.width(136.dp),
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
||||||
|
isError = isError,
|
||||||
|
enabled = enabled,
|
||||||
|
onValueChange = {
|
||||||
|
val textFiltered = it.filter { c -> c.isDigit() }
|
||||||
|
if (textFiltered.isEmpty() || isValidInt(textFiltered)) {
|
||||||
|
isError = false
|
||||||
|
|
||||||
|
val newValue = textFiltered.toIntOrNull() ?: 0
|
||||||
|
text = newValue.toString()
|
||||||
|
onValueChanged(newValue)
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
isError = true
|
||||||
|
delay(500) // Show an error
|
||||||
|
isError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
|
maxLines = 1,
|
||||||
|
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.End),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidInt(value: String): Boolean {
|
||||||
|
return try {
|
||||||
|
value.toInt()
|
||||||
|
true
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
94
src/main/kotlin/app/ui/dialogs/StashWithMessageDialog.kt
Normal file
94
src/main/kotlin/app/ui/dialogs/StashWithMessageDialog.kt
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package app.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusOrder
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
fun StashWithMessageDialog(
|
||||||
|
onReject: () -> Unit,
|
||||||
|
onAccept: (stashMessage: String) -> Unit
|
||||||
|
) {
|
||||||
|
var textField by remember { mutableStateOf("") }
|
||||||
|
val textFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
MaterialDialog(onCloseRequested = onReject) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.background),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.focusOrder(textFieldFocusRequester) {
|
||||||
|
this.next = buttonFieldFocusRequester
|
||||||
|
}
|
||||||
|
.width(300.dp)
|
||||||
|
.onPreviewKeyEvent {
|
||||||
|
if (it.key == Key.Enter && textField.isNotBlank()) {
|
||||||
|
onAccept(textField)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = textField,
|
||||||
|
label = { Text("New stash message", fontSize = 14.sp) },
|
||||||
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
|
onValueChange = {
|
||||||
|
textField = it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
|
onClick = {
|
||||||
|
onReject()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier.focusOrder(buttonFieldFocusRequester) {
|
||||||
|
this.previous = textFieldFocusRequester
|
||||||
|
this.next = textFieldFocusRequester
|
||||||
|
},
|
||||||
|
enabled = textField.isNotBlank(),
|
||||||
|
onClick = {
|
||||||
|
onAccept(textField)
|
||||||
|
},
|
||||||
|
text = "Stash"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
textFieldFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,9 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
import app.ui.components.PrimaryButton
|
import app.ui.components.PrimaryButton
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@ -33,7 +35,9 @@ fun UserPasswordDialog(
|
|||||||
val acceptDialog = {
|
val acceptDialog = {
|
||||||
onAccept(userField, passwordField)
|
onAccept(userField, passwordField)
|
||||||
}
|
}
|
||||||
MaterialDialog {
|
MaterialDialog(
|
||||||
|
onCloseRequested = onReject
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
@ -64,6 +68,7 @@ fun UserPasswordDialog(
|
|||||||
},
|
},
|
||||||
value = userField,
|
value = userField,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
label = { Text("User", fontSize = 14.sp) },
|
label = { Text("User", fontSize = 14.sp) },
|
||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
@ -89,6 +94,7 @@ fun UserPasswordDialog(
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
label = { Text("Password", fontSize = 14.sp) },
|
label = { Text("Password", fontSize = 14.sp) },
|
||||||
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
passwordField = it
|
passwordField = it
|
||||||
},
|
},
|
||||||
@ -102,6 +108,7 @@ fun UserPasswordDialog(
|
|||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onReject()
|
onReject()
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import androidx.compose.ui.input.key.*
|
|||||||
import androidx.compose.ui.input.pointer.PointerIcon
|
import androidx.compose.ui.input.pointer.PointerIcon
|
||||||
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
import androidx.compose.ui.input.pointer.PointerIconDefaults
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
@ -156,6 +157,7 @@ fun Log(
|
|||||||
graphWidth = graphWidth,
|
graphWidth = graphWidth,
|
||||||
scrollState = verticalScrollState,
|
scrollState = verticalScrollState,
|
||||||
hasUncommitedChanges = hasUncommitedChanges,
|
hasUncommitedChanges = hasUncommitedChanges,
|
||||||
|
commitsLimit = logStatus.commitsLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The commits' messages list overlaps with the graph list to catch all the click events but leaves
|
// The commits' messages list overlaps with the graph list to catch all the click events but leaves
|
||||||
@ -171,9 +173,11 @@ fun Log(
|
|||||||
commitList = commitList,
|
commitList = commitList,
|
||||||
logViewModel = logViewModel,
|
logViewModel = logViewModel,
|
||||||
graphWidth = graphWidth,
|
graphWidth = graphWidth,
|
||||||
|
commitsLimit = logStatus.commitsLimit,
|
||||||
onShowLogDialog = { dialog ->
|
onShowLogDialog = { dialog ->
|
||||||
logViewModel.showDialog(dialog)
|
logViewModel.showDialog(dialog)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
DividerLog(
|
DividerLog(
|
||||||
modifier = Modifier.draggable(
|
modifier = Modifier.draggable(
|
||||||
@ -189,7 +193,7 @@ fun Log(
|
|||||||
HorizontalScrollbar(
|
HorizontalScrollbar(
|
||||||
modifier = Modifier.align(Alignment.BottomStart).width(graphWidth)
|
modifier = Modifier.align(Alignment.BottomStart).width(graphWidth)
|
||||||
.padding(start = 4.dp, bottom = 4.dp), style = LocalScrollbarStyle.current.copy(
|
.padding(start = 4.dp, bottom = 4.dp), style = LocalScrollbarStyle.current.copy(
|
||||||
unhoverColor = MaterialTheme.colors.scrollbarUnhover,
|
unhoverColor = MaterialTheme.colors.scrollbarNormal,
|
||||||
hoverColor = MaterialTheme.colors.scrollbarHover,
|
hoverColor = MaterialTheme.colors.scrollbarHover,
|
||||||
), adapter = rememberScrollbarAdapter(horizontalScrollState)
|
), adapter = rememberScrollbarAdapter(horizontalScrollState)
|
||||||
)
|
)
|
||||||
@ -226,8 +230,7 @@ fun SearchFilter(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(64.dp)
|
.height(64.dp),
|
||||||
.background(MaterialTheme.colors.graphHeaderBackground),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
@ -260,7 +263,7 @@ fun SearchFilter(
|
|||||||
label = {
|
label = {
|
||||||
Text("Search by message, author name or commit ID")
|
Text("Search by message, author name or commit ID")
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.background),
|
colors = textFieldColors(),
|
||||||
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
textStyle = TextStyle.Default.copy(fontSize = 14.sp, color = MaterialTheme.colors.primaryTextColor),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
Row(
|
Row(
|
||||||
@ -320,6 +323,7 @@ fun MessagesList(
|
|||||||
selectedItem: SelectedItem,
|
selectedItem: SelectedItem,
|
||||||
commitList: GraphCommitList,
|
commitList: GraphCommitList,
|
||||||
logViewModel: LogViewModel,
|
logViewModel: LogViewModel,
|
||||||
|
commitsLimit: Int,
|
||||||
onShowLogDialog: (LogDialog) -> Unit,
|
onShowLogDialog: (LogDialog) -> Unit,
|
||||||
graphWidth: Dp,
|
graphWidth: Dp,
|
||||||
) {
|
) {
|
||||||
@ -354,6 +358,25 @@ fun MessagesList(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (commitsLimit >= 0 && commitsLimit <= commitList.count()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = graphWidth + 24.dp)
|
||||||
|
.height(40.dp),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "The commits list has been limited to $commitsLimit. Access the settings to change it.",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Box(modifier = Modifier.height(20.dp))
|
Box(modifier = Modifier.height(20.dp))
|
||||||
}
|
}
|
||||||
@ -369,6 +392,7 @@ fun GraphList(
|
|||||||
hasUncommitedChanges: Boolean,
|
hasUncommitedChanges: Boolean,
|
||||||
selectedCommit: RevCommit?,
|
selectedCommit: RevCommit?,
|
||||||
selectedItem: SelectedItem,
|
selectedItem: SelectedItem,
|
||||||
|
commitsLimit: Int,
|
||||||
) {
|
) {
|
||||||
val maxLinePosition = if (commitList.isNotEmpty())
|
val maxLinePosition = if (commitList.isNotEmpty())
|
||||||
commitList.maxLine
|
commitList.maxLine
|
||||||
@ -429,6 +453,16 @@ fun GraphList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spacing when the commits limit is present
|
||||||
|
if (commitsLimit >= 0 && commitsLimit <= commitList.count()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(40.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Box(modifier = Modifier.height(20.dp))
|
Box(modifier = Modifier.height(20.dp))
|
||||||
}
|
}
|
||||||
@ -496,11 +530,14 @@ fun GraphHeader(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().height(48.dp).background(MaterialTheme.colors.graphHeaderBackground),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
.background(MaterialTheme.colors.headerBackground),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.width(graphWidth).padding(start = 8.dp),
|
modifier = Modifier.width(graphWidth).padding(start = 16.dp),
|
||||||
text = "Graph",
|
text = "Graph",
|
||||||
color = MaterialTheme.colors.headerText,
|
color = MaterialTheme.colors.headerText,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
@ -516,7 +553,9 @@ fun GraphHeader(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 8.dp).weight(1f),
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
.weight(1f),
|
||||||
text = "Message",
|
text = "Message",
|
||||||
color = MaterialTheme.colors.headerText,
|
color = MaterialTheme.colors.headerText,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
@ -551,11 +590,9 @@ fun UncommitedChangesLine(
|
|||||||
modifier = Modifier.height(40.dp)
|
modifier = Modifier.height(40.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onUncommitedChangesSelected() }
|
.clickable { onUncommitedChangesSelected() }
|
||||||
.padding(
|
.padding(start = graphWidth)
|
||||||
start = graphWidth + DIVIDER_WIDTH.dp,
|
.backgroundIf(isSelected, MaterialTheme.colors.backgroundSelected)
|
||||||
end = 4.dp,
|
.padding(DIVIDER_WIDTH.dp),
|
||||||
)
|
|
||||||
.backgroundIf(isSelected, MaterialTheme.colors.backgroundSelected),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
val text = when {
|
val text = when {
|
||||||
@ -800,7 +837,10 @@ fun DividerLog(modifier: Modifier, graphWidth: Dp) {
|
|||||||
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
|
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxHeight().width(1.dp).background(color = MaterialTheme.colors.primary)
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(1.dp)
|
||||||
|
.background(color = MaterialTheme.colors.primaryVariant)
|
||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -823,6 +863,10 @@ fun CommitsGraphLine(
|
|||||||
val passingLanes = plotCommit.passingLanes
|
val passingLanes = plotCommit.passingLanes
|
||||||
val forkingOffLanes = plotCommit.forkingOffLanes
|
val forkingOffLanes = plotCommit.forkingOffLanes
|
||||||
val mergingLanes = plotCommit.mergingLanes
|
val mergingLanes = plotCommit.mergingLanes
|
||||||
|
val density = LocalDensity.current.density
|
||||||
|
val laneWidthWithDensity = remember(density) {
|
||||||
|
LANE_WIDTH * density
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -837,40 +881,40 @@ fun CommitsGraphLine(
|
|||||||
if (plotCommit.childCount > 0) {
|
if (plotCommit.childCount > 0) {
|
||||||
drawLine(
|
drawLine(
|
||||||
color = colors[itemPosition % colors.size],
|
color = colors[itemPosition % colors.size],
|
||||||
start = Offset(30f * (itemPosition + 1), this.center.y),
|
start = Offset( laneWidthWithDensity * (itemPosition + 1), this.center.y),
|
||||||
end = Offset(30f * (itemPosition + 1), 0f),
|
end = Offset( laneWidthWithDensity * (itemPosition + 1), 0f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
forkingOffLanes.forEach { plotLane ->
|
forkingOffLanes.forEach { plotLane ->
|
||||||
drawLine(
|
drawLine(
|
||||||
color = colors[plotLane.position % colors.size],
|
color = colors[plotLane.position % colors.size],
|
||||||
start = Offset(30f * (itemPosition + 1), this.center.y),
|
start = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
|
||||||
end = Offset(30f * (plotLane.position + 1), 0f),
|
end = Offset(laneWidthWithDensity * (plotLane.position + 1), 0f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mergingLanes.forEach { plotLane ->
|
mergingLanes.forEach { plotLane ->
|
||||||
drawLine(
|
drawLine(
|
||||||
color = colors[plotLane.position % colors.size],
|
color = colors[plotLane.position % colors.size],
|
||||||
start = Offset(30f * (plotLane.position + 1), this.size.height),
|
start = Offset(laneWidthWithDensity * (plotLane.position + 1), this.size.height),
|
||||||
end = Offset(30f * (itemPosition + 1), this.center.y),
|
end = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plotCommit.parentCount > 0) {
|
if (plotCommit.parentCount > 0) {
|
||||||
drawLine(
|
drawLine(
|
||||||
color = colors[itemPosition % colors.size],
|
color = colors[itemPosition % colors.size],
|
||||||
start = Offset(30f * (itemPosition + 1), this.center.y),
|
start = Offset(laneWidthWithDensity * (itemPosition + 1), this.center.y),
|
||||||
end = Offset(30f * (itemPosition + 1), this.size.height),
|
end = Offset(laneWidthWithDensity * (itemPosition + 1), this.size.height),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
passingLanes.forEach { plotLane ->
|
passingLanes.forEach { plotLane ->
|
||||||
drawLine(
|
drawLine(
|
||||||
color = colors[plotLane.position % colors.size],
|
color = colors[plotLane.position % colors.size],
|
||||||
start = Offset(30f * (plotLane.position + 1), 0f),
|
start = Offset(laneWidthWithDensity * (plotLane.position + 1), 0f),
|
||||||
end = Offset(30f * (plotLane.position + 1), this.size.height),
|
end = Offset(laneWidthWithDensity * (plotLane.position + 1), this.size.height),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -907,6 +951,11 @@ fun UncommitedChangesGraphNode(
|
|||||||
hasPreviousCommits: Boolean,
|
hasPreviousCommits: Boolean,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
) {
|
) {
|
||||||
|
val density = LocalDensity.current.density
|
||||||
|
|
||||||
|
val laneWidthWithDensity = remember(density) {
|
||||||
|
LANE_WIDTH * density
|
||||||
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.backgroundIf(isSelected, MaterialTheme.colors.backgroundSelected)
|
.backgroundIf(isSelected, MaterialTheme.colors.backgroundSelected)
|
||||||
@ -918,14 +967,14 @@ fun UncommitedChangesGraphNode(
|
|||||||
|
|
||||||
if (hasPreviousCommits) drawLine(
|
if (hasPreviousCommits) drawLine(
|
||||||
color = colors[0],
|
color = colors[0],
|
||||||
start = Offset(30f, this.center.y),
|
start = Offset(laneWidthWithDensity, this.center.y),
|
||||||
end = Offset(30f, this.size.height),
|
end = Offset(laneWidthWithDensity, this.size.height),
|
||||||
)
|
)
|
||||||
|
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = colors[0],
|
color = colors[0],
|
||||||
radius = 15f,
|
radius = 15f,
|
||||||
center = Offset(30f, this.center.y),
|
center = Offset(laneWidthWithDensity, this.center.y),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -969,7 +1018,7 @@ fun BranchChip(
|
|||||||
painter = painterResource("location.svg"),
|
painter = painterResource("location.svg"),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.padding(end = 6.dp),
|
modifier = Modifier.padding(end = 6.dp),
|
||||||
tint = MaterialTheme.colors.primary,
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1042,7 +1091,7 @@ fun RefChip(
|
|||||||
modifier = Modifier.padding(6.dp).size(14.dp),
|
modifier = Modifier.padding(6.dp).size(14.dp),
|
||||||
painter = painterResource(icon),
|
painter = painterResource(icon),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colors.inversePrimaryTextColor,
|
tint = MaterialTheme.colors.background,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import app.AppPreferences
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.git.graph.GraphCommitList
|
import app.git.graph.GraphCommitList
|
||||||
import app.git.graph.GraphNode
|
import app.git.graph.GraphNode
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
import app.ui.log.LogDialog
|
import app.ui.log.LogDialog
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Ref
|
import org.eclipse.jgit.lib.Ref
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
@ -36,6 +38,7 @@ class LogViewModel @Inject constructor(
|
|||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
private val remoteOperationsManager: RemoteOperationsManager,
|
private val remoteOperationsManager: RemoteOperationsManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
|
private val appPreferences: AppPreferences,
|
||||||
) {
|
) {
|
||||||
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
||||||
|
|
||||||
@ -57,9 +60,24 @@ class LogViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
private val _logSearchFilterResults = MutableStateFlow<LogSearch>(LogSearch.NotSearching)
|
private val _logSearchFilterResults = MutableStateFlow<LogSearch>(LogSearch.NotSearching)
|
||||||
val logSearchFilterResults: StateFlow<LogSearch> = _logSearchFilterResults
|
val logSearchFilterResults: StateFlow<LogSearch> = _logSearchFilterResults
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
appPreferences.commitsLimitEnabledFlow.collect {
|
||||||
|
tabState.refreshData(RefreshType.ONLY_LOG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
appPreferences.commitsLimitFlow.collect {
|
||||||
|
tabState.refreshData(RefreshType.ONLY_LOG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadLog(git: Git) {
|
private suspend fun loadLog(git: Git) {
|
||||||
_logStatus.value = LogStatus.Loading
|
_logStatus.value = LogStatus.Loading
|
||||||
|
|
||||||
@ -70,9 +88,19 @@ class LogViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val hasUncommitedChanges = statusSummary.total > 0
|
val hasUncommitedChanges = statusSummary.total > 0
|
||||||
val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges)
|
val commitsLimit = if(appPreferences.commitsLimitEnabled) {
|
||||||
|
appPreferences.commitsLimit
|
||||||
|
} else
|
||||||
|
Int.MAX_VALUE
|
||||||
|
|
||||||
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary)
|
val commitsLimitDisplayed = if(appPreferences.commitsLimitEnabled) {
|
||||||
|
appPreferences.commitsLimit
|
||||||
|
} else
|
||||||
|
-1
|
||||||
|
|
||||||
|
val log = logManager.loadLog(git, currentBranch, hasUncommitedChanges, commitsLimit)
|
||||||
|
|
||||||
|
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch, statusSummary, commitsLimitDisplayed)
|
||||||
|
|
||||||
// Remove search filter if the log has been updated
|
// Remove search filter if the log has been updated
|
||||||
_logSearchFilterResults.value = LogSearch.NotSearching
|
_logSearchFilterResults.value = LogSearch.NotSearching
|
||||||
@ -182,6 +210,7 @@ class LogViewModel @Inject constructor(
|
|||||||
plotCommitList = previousLogStatusValue.plotCommitList,
|
plotCommitList = previousLogStatusValue.plotCommitList,
|
||||||
currentBranch = currentBranch,
|
currentBranch = currentBranch,
|
||||||
statusSummary = statsSummary,
|
statusSummary = statsSummary,
|
||||||
|
commitsLimit = previousLogStatusValue.commitsLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
_logStatus.value = newLogStatusValue
|
_logStatus.value = newLogStatusValue
|
||||||
@ -329,6 +358,7 @@ sealed class LogStatus {
|
|||||||
val plotCommitList: GraphCommitList,
|
val plotCommitList: GraphCommitList,
|
||||||
val currentBranch: Ref?,
|
val currentBranch: Ref?,
|
||||||
val statusSummary: StatusSummary,
|
val statusSummary: StatusSummary,
|
||||||
|
val commitsLimit: Int,
|
||||||
) : LogStatus()
|
) : LogStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,14 @@ class MenuViewModel @Inject constructor(
|
|||||||
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||||
) { git ->
|
) { git ->
|
||||||
statusManager.stageUntrackedFiles(git)
|
statusManager.stageUntrackedFiles(git)
|
||||||
stashManager.stash(git)
|
stashManager.stash(git, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stashWithMessage(message: String) = tabState.safeProcessing(
|
||||||
|
refreshType = RefreshType.UNCOMMITED_CHANGES_AND_LOG,
|
||||||
|
) { git ->
|
||||||
|
statusManager.stageUntrackedFiles(git)
|
||||||
|
stashManager.stash(git, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun popStash() = tabState.safeProcessing(
|
fun popStash() = tabState.safeProcessing(
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.extensions.isMerging
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StatusViewModel @Inject constructor(
|
class StatusViewModel @Inject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val statusManager: StatusManager,
|
private val statusManager: StatusManager,
|
||||||
private val branchesManager: BranchesManager,
|
|
||||||
private val repositoryManager: RepositoryManager,
|
|
||||||
private val rebaseManager: RebaseManager,
|
private val rebaseManager: RebaseManager,
|
||||||
private val mergeManager: MergeManager,
|
private val mergeManager: MergeManager,
|
||||||
private val logManager: LogManager,
|
private val logManager: LogManager,
|
||||||
@ -21,11 +23,30 @@ class StatusViewModel @Inject constructor(
|
|||||||
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf()))
|
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf()))
|
||||||
val stageStatus: StateFlow<StageStatus> = _stageStatus
|
val stageStatus: StateFlow<StageStatus> = _stageStatus
|
||||||
|
|
||||||
var savedCommitMessage: String = ""
|
var savedCommitMessage = CommitMessage("", MessageType.NORMAL)
|
||||||
|
|
||||||
var hasPreviousCommits = true // When false, disable "amend previous commit"
|
var hasPreviousCommits = true // When false, disable "amend previous commit"
|
||||||
|
|
||||||
private var lastUncommitedChangesState = false
|
private var lastUncommitedChangesState = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the UI that the commit message has been changed by the view model
|
||||||
|
*/
|
||||||
|
private val _commitMessageChangesFlow = MutableSharedFlow<String>()
|
||||||
|
val commitMessageChangesFlow: SharedFlow<String> = _commitMessageChangesFlow
|
||||||
|
|
||||||
|
private fun persistMessage() = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
val messageToPersist = savedCommitMessage.message.ifBlank { null }
|
||||||
|
|
||||||
|
if (git.repository.repositoryState.isMerging) {
|
||||||
|
git.repository.writeMergeCommitMsg(messageToPersist)
|
||||||
|
} else if (git.repository.repositoryState == RepositoryState.SAFE) {
|
||||||
|
git.repository.writeCommitEditMsg(messageToPersist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
fun stage(statusEntry: StatusEntry) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
) { git ->
|
) { git ->
|
||||||
@ -66,6 +87,21 @@ class StatusViewModel @Inject constructor(
|
|||||||
private suspend fun loadStatus(git: Git) {
|
private suspend fun loadStatus(git: Git) {
|
||||||
val previousStatus = _stageStatus.value
|
val previousStatus = _stageStatus.value
|
||||||
|
|
||||||
|
val requiredMessageType = if (git.repository.repositoryState == RepositoryState.MERGING) {
|
||||||
|
MessageType.MERGE
|
||||||
|
} else {
|
||||||
|
MessageType.NORMAL
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredMessageType != savedCommitMessage.messageType) {
|
||||||
|
savedCommitMessage = CommitMessage(messageByRepoState(git), requiredMessageType)
|
||||||
|
_commitMessageChangesFlow.emit(savedCommitMessage.message)
|
||||||
|
|
||||||
|
} else if (savedCommitMessage.message.isEmpty()) {
|
||||||
|
savedCommitMessage = savedCommitMessage.copy(message = messageByRepoState(git))
|
||||||
|
_commitMessageChangesFlow.emit(savedCommitMessage.message)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_stageStatus.value = StageStatus.Loading
|
_stageStatus.value = StageStatus.Loading
|
||||||
val status = statusManager.getStatus(git)
|
val status = statusManager.getStatus(git)
|
||||||
@ -79,6 +115,17 @@ class StatusViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun messageByRepoState(git: Git): String {
|
||||||
|
val message: String? = if (git.repository.repositoryState == RepositoryState.MERGING) {
|
||||||
|
git.repository.readMergeCommitMsg()
|
||||||
|
} else {
|
||||||
|
git.repository.readCommitEditMsg()
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO this replace is a workaround until this issue gets fixed https://github.com/JetBrains/compose-jb/issues/615
|
||||||
|
return message.orEmpty().replace("\t", " ")
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
|
private suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
|
||||||
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
|
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
|
||||||
}
|
}
|
||||||
@ -92,6 +139,7 @@ class StatusViewModel @Inject constructor(
|
|||||||
message
|
message
|
||||||
|
|
||||||
statusManager.commit(git, commitMessage, amend)
|
statusManager.commit(git, commitMessage, amend)
|
||||||
|
updateCommitMessage("")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
|
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
|
||||||
@ -148,6 +196,11 @@ class StatusViewModel @Inject constructor(
|
|||||||
|
|
||||||
fileToDelete.delete()
|
fileToDelete.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateCommitMessage(message: String) {
|
||||||
|
savedCommitMessage = savedCommitMessage.copy(message = message)
|
||||||
|
persistMessage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class StageStatus {
|
sealed class StageStatus {
|
||||||
@ -155,3 +208,9 @@ sealed class StageStatus {
|
|||||||
data class Loaded(val staged: List<StatusEntry>, val unstaged: List<StatusEntry>) : StageStatus()
|
data class Loaded(val staged: List<StatusEntry>, val unstaged: List<StatusEntry>) : StageStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CommitMessage(val message: String, val messageType: MessageType)
|
||||||
|
|
||||||
|
enum class MessageType {
|
||||||
|
NORMAL,
|
||||||
|
MERGE;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user