Added basic split diff
This commit is contained in:
parent
34164fb2bc
commit
5659bf8918
@ -26,8 +26,10 @@ import androidx.compose.ui.window.rememberWindowState
|
|||||||
import app.di.DaggerAppComponent
|
import app.di.DaggerAppComponent
|
||||||
import app.extensions.preferenceValue
|
import app.extensions.preferenceValue
|
||||||
import app.extensions.toWindowPlacement
|
import app.extensions.toWindowPlacement
|
||||||
import app.preferences.AppPreferences
|
import app.logging.printLog
|
||||||
|
import app.preferences.AppSettings
|
||||||
import app.theme.AppTheme
|
import app.theme.AppTheme
|
||||||
|
import app.theme.Theme
|
||||||
import app.theme.primaryTextColor
|
import app.theme.primaryTextColor
|
||||||
import app.theme.secondaryTextColor
|
import app.theme.secondaryTextColor
|
||||||
import app.ui.AppTab
|
import app.ui.AppTab
|
||||||
@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val TAG = "App"
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
private val appComponent = DaggerAppComponent.create()
|
private val appComponent = DaggerAppComponent.create()
|
||||||
@ -48,7 +51,7 @@ class App {
|
|||||||
lateinit var appStateManager: AppStateManager
|
lateinit var appStateManager: AppStateManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferences: AppPreferences
|
lateinit var appSettings: AppSettings
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settingsViewModel: SettingsViewModel
|
lateinit var settingsViewModel: SettingsViewModel
|
||||||
@ -60,17 +63,26 @@ class App {
|
|||||||
private val tabsFlow = MutableStateFlow<List<TabInformation>>(emptyList())
|
private val tabsFlow = MutableStateFlow<List<TabInformation>>(emptyList())
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
val windowPlacement = appPreferences.windowPlacement.toWindowPlacement
|
val windowPlacement = appSettings.windowPlacement.toWindowPlacement
|
||||||
|
|
||||||
appStateManager.loadRepositoriesTabs()
|
appStateManager.loadRepositoriesTabs()
|
||||||
appPreferences.loadCustomTheme()
|
|
||||||
|
try {
|
||||||
|
if (appSettings.theme == Theme.CUSTOM) {
|
||||||
|
appSettings.loadCustomTheme()
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
printLog(TAG, "Failed to load custom theme")
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
loadTabs()
|
loadTabs()
|
||||||
|
|
||||||
application {
|
application {
|
||||||
var isOpen by remember { mutableStateOf(true) }
|
var isOpen by remember { mutableStateOf(true) }
|
||||||
val theme by appPreferences.themeState.collectAsState()
|
val theme by appSettings.themeState.collectAsState()
|
||||||
val customTheme by appPreferences.customThemeFlow.collectAsState()
|
val customTheme by appSettings.customThemeFlow.collectAsState()
|
||||||
val scale by appPreferences.scaleUiFlow.collectAsState()
|
val scale by appSettings.scaleUiFlow.collectAsState()
|
||||||
|
|
||||||
val windowState = rememberWindowState(
|
val windowState = rememberWindowState(
|
||||||
placement = windowPlacement,
|
placement = windowPlacement,
|
||||||
@ -78,7 +90,7 @@ class App {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Save window state for next time the Window is started
|
// Save window state for next time the Window is started
|
||||||
appPreferences.windowPlacement = windowState.placement.preferenceValue
|
appSettings.windowPlacement = windowState.placement.preferenceValue
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
Window(
|
Window(
|
||||||
@ -191,6 +203,7 @@ class App {
|
|||||||
tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) }
|
tabsFlow.value = tabsFlow.value.toMutableList().apply { add(tabInformation) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Tabs(
|
fun Tabs(
|
||||||
selectedTabKey: MutableState<Int>,
|
selectedTabKey: MutableState<Int>,
|
||||||
|
@ -31,5 +31,8 @@ object AppConstants {
|
|||||||
private val apache__2_0 = License("Apache 2.0", "https://www.apache.org/licenses/LICENSE-2.0")
|
private val apache__2_0 = License("Apache 2.0", "https://www.apache.org/licenses/LICENSE-2.0")
|
||||||
private val edl = License("EDL", "https://www.eclipse.org/org/documents/edl-v10.php")
|
private val edl = License("EDL", "https://www.eclipse.org/org/documents/edl-v10.php")
|
||||||
|
|
||||||
data class License(val name: String, val url: String)
|
data class License(
|
||||||
|
val name: String,
|
||||||
|
val url: String
|
||||||
|
)
|
||||||
data class Project(val name: String, val url: String, val license: License)
|
data class Project(val name: String, val url: String, val license: License)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import app.preferences.AppPreferences
|
import app.preferences.AppSettings
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppStateManager @Inject constructor(
|
class AppStateManager @Inject constructor(
|
||||||
private val appPreferences: AppPreferences,
|
private val appSettings: AppSettings,
|
||||||
) {
|
) {
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
@ -59,15 +59,15 @@ class AppStateManager @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun updateSavedRepositoryTabs() = withContext(Dispatchers.IO) {
|
private suspend fun updateSavedRepositoryTabs() = withContext(Dispatchers.IO) {
|
||||||
val tabsList = _openRepositoriesPaths.map { it.value }
|
val tabsList = _openRepositoriesPaths.map { it.value }
|
||||||
appPreferences.latestTabsOpened = Json.encodeToString(tabsList)
|
appSettings.latestTabsOpened = Json.encodeToString(tabsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateLatestRepositoryTabs() = withContext(Dispatchers.IO) {
|
private suspend fun updateLatestRepositoryTabs() = withContext(Dispatchers.IO) {
|
||||||
appPreferences.latestOpenedRepositoriesPath = Json.encodeToString(_latestOpenedRepositoriesPaths)
|
appSettings.latestOpenedRepositoriesPath = Json.encodeToString(_latestOpenedRepositoriesPaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadRepositoriesTabs() {
|
fun loadRepositoriesTabs() {
|
||||||
val repositoriesSaved = appPreferences.latestTabsOpened
|
val repositoriesSaved = appSettings.latestTabsOpened
|
||||||
|
|
||||||
if (repositoriesSaved.isNotEmpty()) {
|
if (repositoriesSaved.isNotEmpty()) {
|
||||||
val repositoriesList = Json.decodeFromString<List<String>>(repositoriesSaved)
|
val repositoriesList = Json.decodeFromString<List<String>>(repositoriesSaved)
|
||||||
@ -77,7 +77,7 @@ class AppStateManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val repositoriesPathsSaved = appPreferences.latestOpenedRepositoriesPath
|
val repositoriesPathsSaved = appSettings.latestOpenedRepositoriesPath
|
||||||
if (repositoriesPathsSaved.isNotEmpty()) {
|
if (repositoriesPathsSaved.isNotEmpty()) {
|
||||||
val repositories = Json.decodeFromString<List<String>>(repositoriesPathsSaved)
|
val repositories = Json.decodeFromString<List<String>>(repositoriesPathsSaved)
|
||||||
_latestOpenedRepositoriesPaths.addAll(repositories)
|
_latestOpenedRepositoriesPaths.addAll(repositories)
|
||||||
|
@ -2,7 +2,7 @@ package app.di
|
|||||||
|
|
||||||
import app.App
|
import app.App
|
||||||
import app.AppStateManager
|
import app.AppStateManager
|
||||||
import app.preferences.AppPreferences
|
import app.preferences.AppSettings
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ interface AppComponent {
|
|||||||
fun inject(main: App)
|
fun inject(main: App)
|
||||||
fun appStateManager(): AppStateManager
|
fun appStateManager(): AppStateManager
|
||||||
|
|
||||||
fun appPreferences(): AppPreferences
|
fun appPreferences(): AppSettings
|
||||||
}
|
}
|
@ -118,3 +118,12 @@ val DiffEntry.iconColor: Color
|
|||||||
else -> throw NotImplementedError("Unexpected ChangeType")
|
else -> throw NotImplementedError("Unexpected ChangeType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun DiffEntry.toStatusType(): StatusType = when (this.changeType) {
|
||||||
|
DiffEntry.ChangeType.ADD -> StatusType.ADDED
|
||||||
|
DiffEntry.ChangeType.MODIFY -> StatusType.MODIFIED
|
||||||
|
DiffEntry.ChangeType.DELETE -> StatusType.REMOVED
|
||||||
|
DiffEntry.ChangeType.COPY -> StatusType.ADDED
|
||||||
|
DiffEntry.ChangeType.RENAME -> StatusType.MODIFIED
|
||||||
|
else -> throw NotImplementedError("Unexpected ChangeType")
|
||||||
|
}
|
@ -1,11 +1,25 @@
|
|||||||
package app.git
|
package app.git
|
||||||
|
|
||||||
|
import app.extensions.filePath
|
||||||
|
import app.extensions.toStatusType
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
|
||||||
sealed class DiffEntryType {
|
sealed class DiffEntryType {
|
||||||
class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType()
|
class CommitDiff(val diffEntry: DiffEntry) : DiffEntryType() {
|
||||||
|
override val filePath: String
|
||||||
|
get() = diffEntry.filePath
|
||||||
|
|
||||||
sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType()
|
override val statusType: StatusType
|
||||||
|
get() = diffEntry.toStatusType()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class UncommitedDiff(val statusEntry: StatusEntry) : DiffEntryType() {
|
||||||
|
override val filePath: String
|
||||||
|
get() = statusEntry.filePath
|
||||||
|
|
||||||
|
override val statusType: StatusType
|
||||||
|
get() = statusEntry.statusType
|
||||||
|
}
|
||||||
|
|
||||||
sealed class UnstagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
sealed class UnstagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||||
sealed class StagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
sealed class StagedDiff(statusEntry: StatusEntry) : UncommitedDiff(statusEntry)
|
||||||
@ -23,4 +37,7 @@ sealed class DiffEntryType {
|
|||||||
class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
class SafeStagedDiff(statusEntry: StatusEntry) : StagedDiff(statusEntry)
|
||||||
class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
class SafeUnstagedDiff(statusEntry: StatusEntry) : UnstagedDiff(statusEntry)
|
||||||
|
|
||||||
|
abstract val filePath: String
|
||||||
|
abstract val statusType: StatusType
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -131,3 +131,17 @@ fun prepareTreeParser(repository: Repository, commit: RevCommit): AbstractTreeIt
|
|||||||
return treeParser
|
return treeParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class TextDiffType(val value: Int) {
|
||||||
|
SPLIT(0),
|
||||||
|
UNIFIED(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun textDiffTypeFromValue(diffTypeValue: Int): TextDiffType {
|
||||||
|
return when (diffTypeValue) {
|
||||||
|
TextDiffType.SPLIT.value -> TextDiffType.SPLIT
|
||||||
|
TextDiffType.UNIFIED.value -> TextDiffType.UNIFIED
|
||||||
|
else -> throw NotImplementedError("Diff type not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ package app.git.diff
|
|||||||
|
|
||||||
data class Hunk(val header: String, val lines: List<Line>)
|
data class Hunk(val header: String, val lines: List<Line>)
|
||||||
|
|
||||||
|
data class SplitHunk(val hunk: Hunk, val lines: List<Pair<Line?, Line?>>)
|
||||||
|
|
||||||
data class Line(val text: String, val oldLineNumber: Int, val newLineNumber: Int, val lineType: LineType) {
|
data class Line(val text: String, val oldLineNumber: Int, val newLineNumber: Int, val lineType: LineType) {
|
||||||
// lines numbers are stored based on 0 being the first one but on a file the first line is the 1, so increment it!
|
// lines numbers are stored based on 0 being the first one but on a file the first line is the 1, so increment it!
|
||||||
val displayOldLineNumber: Int = oldLineNumber + 1
|
val displayOldLineNumber: Int = oldLineNumber + 1
|
||||||
|
@ -221,6 +221,10 @@ sealed class DiffResult(
|
|||||||
diffEntry: DiffEntry,
|
diffEntry: DiffEntry,
|
||||||
val hunks: List<Hunk>
|
val hunks: List<Hunk>
|
||||||
) : DiffResult(diffEntry)
|
) : DiffResult(diffEntry)
|
||||||
|
class TextSplit(
|
||||||
|
diffEntry: DiffEntry,
|
||||||
|
val hunks: List<SplitHunk>
|
||||||
|
) : DiffResult(diffEntry)
|
||||||
|
|
||||||
class NonText(
|
class NonText(
|
||||||
diffEntry: DiffEntry,
|
diffEntry: DiffEntry,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package app.preferences
|
package app.preferences
|
||||||
|
|
||||||
import app.extensions.defaultWindowPlacement
|
import app.extensions.defaultWindowPlacement
|
||||||
|
import app.git.TextDiffType
|
||||||
|
import app.git.textDiffTypeFromValue
|
||||||
import app.theme.ColorsScheme
|
import app.theme.ColorsScheme
|
||||||
import app.theme.Theme
|
import app.theme.Theme
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -22,6 +24,7 @@ private const val PREF_COMMITS_LIMIT_ENABLED = "commitsLimitEnabled"
|
|||||||
private const val PREF_WINDOW_PLACEMENT = "windowsPlacement"
|
private const val PREF_WINDOW_PLACEMENT = "windowsPlacement"
|
||||||
private const val PREF_CUSTOM_THEME = "customTheme"
|
private const val PREF_CUSTOM_THEME = "customTheme"
|
||||||
private const val PREF_UI_SCALE = "ui_scale"
|
private const val PREF_UI_SCALE = "ui_scale"
|
||||||
|
private const val PREF_DIFF_TYPE = "diffType"
|
||||||
|
|
||||||
|
|
||||||
private const val PREF_GIT_FF_MERGE = "gitFFMerge"
|
private const val PREF_GIT_FF_MERGE = "gitFFMerge"
|
||||||
@ -31,7 +34,7 @@ private const val DEFAULT_COMMITS_LIMIT_ENABLED = true
|
|||||||
const val DEFAULT_UI_SCALE = -1f
|
const val DEFAULT_UI_SCALE = -1f
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppPreferences @Inject constructor() {
|
class AppSettings @Inject constructor() {
|
||||||
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
|
private val preferences: Preferences = Preferences.userRoot().node(PREFERENCES_NAME)
|
||||||
|
|
||||||
private val _themeState = MutableStateFlow(theme)
|
private val _themeState = MutableStateFlow(theme)
|
||||||
@ -52,6 +55,9 @@ class AppPreferences @Inject constructor() {
|
|||||||
private val _scaleUiFlow = MutableStateFlow(scaleUi)
|
private val _scaleUiFlow = MutableStateFlow(scaleUi)
|
||||||
val scaleUiFlow: StateFlow<Float> = _scaleUiFlow
|
val scaleUiFlow: StateFlow<Float> = _scaleUiFlow
|
||||||
|
|
||||||
|
private val _textDiffTypeFlow = MutableStateFlow(textDiffType)
|
||||||
|
val textDiffTypeFlow: StateFlow<TextDiffType> = _textDiffTypeFlow
|
||||||
|
|
||||||
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) {
|
||||||
@ -128,6 +134,18 @@ class AppPreferences @Inject constructor() {
|
|||||||
preferences.putInt(PREF_WINDOW_PLACEMENT, placement.value)
|
preferences.putInt(PREF_WINDOW_PLACEMENT, placement.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var textDiffType: TextDiffType
|
||||||
|
get() {
|
||||||
|
val diffTypeValue = preferences.getInt(PREF_DIFF_TYPE, TextDiffType.UNIFIED.value)
|
||||||
|
|
||||||
|
return textDiffTypeFromValue(diffTypeValue)
|
||||||
|
}
|
||||||
|
set(placement) {
|
||||||
|
preferences.putInt(PREF_DIFF_TYPE, placement.value)
|
||||||
|
|
||||||
|
_textDiffTypeFlow.value = textDiffType
|
||||||
|
}
|
||||||
|
|
||||||
fun saveCustomTheme(filePath: String) {
|
fun saveCustomTheme(filePath: String) {
|
||||||
try {
|
try {
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
@ -25,6 +25,8 @@ val lightTheme = ColorsScheme(
|
|||||||
dialogOverlay = Color(0xAA000000),
|
dialogOverlay = Color(0xAA000000),
|
||||||
normalScrollbar = Color(0xFFCCCCCC),
|
normalScrollbar = Color(0xFFCCCCCC),
|
||||||
hoverScrollbar = Color(0xFF0070D8),
|
hoverScrollbar = Color(0xFF0070D8),
|
||||||
|
diffLineAdded = Color(0xFFd7ebd0),
|
||||||
|
diffLineRemoved = Color(0xFFf0d4d4),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +52,10 @@ val darkBlueTheme = ColorsScheme(
|
|||||||
conflictingFile = Color(0xFFFFB638),
|
conflictingFile = Color(0xFFFFB638),
|
||||||
dialogOverlay = Color(0xAA000000),
|
dialogOverlay = Color(0xAA000000),
|
||||||
normalScrollbar = Color(0xFF888888),
|
normalScrollbar = Color(0xFF888888),
|
||||||
hoverScrollbar = Color(0xFFCCCCCC)
|
hoverScrollbar = Color(0xFFCCCCCC),
|
||||||
|
diffLineAdded = Color(0xFF566f5a),
|
||||||
|
diffLineRemoved = Color(0xFF6f585e),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val darkGrayTheme = ColorsScheme(
|
val darkGrayTheme = ColorsScheme(
|
||||||
@ -75,5 +80,7 @@ val darkGrayTheme = ColorsScheme(
|
|||||||
conflictingFile = Color(0xFFFFB638),
|
conflictingFile = Color(0xFFFFB638),
|
||||||
dialogOverlay = Color(0xAA000000),
|
dialogOverlay = Color(0xAA000000),
|
||||||
normalScrollbar = Color(0xFF888888),
|
normalScrollbar = Color(0xFF888888),
|
||||||
hoverScrollbar = Color(0xFFCCCCCC)
|
hoverScrollbar = Color(0xFFCCCCCC),
|
||||||
|
diffLineAdded = Color(0xFF5b7059),
|
||||||
|
diffLineRemoved = Color(0xFF74595c),
|
||||||
)
|
)
|
@ -14,6 +14,7 @@ import kotlinx.serialization.encoding.Decoder
|
|||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
|
||||||
|
// TODO Add line added + removed colors, graph colors and icons color for added/modified/removed files.
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ColorsScheme(
|
data class ColorsScheme(
|
||||||
val primary: Color,
|
val primary: Color,
|
||||||
@ -39,6 +40,8 @@ data class ColorsScheme(
|
|||||||
val dialogOverlay: Color,
|
val dialogOverlay: Color,
|
||||||
val normalScrollbar: Color,
|
val normalScrollbar: Color,
|
||||||
val hoverScrollbar: Color,
|
val hoverScrollbar: Color,
|
||||||
|
val diffLineAdded: Color,
|
||||||
|
val diffLineRemoved: Color,
|
||||||
) {
|
) {
|
||||||
fun toComposeColors(): Colors {
|
fun toComposeColors(): Colors {
|
||||||
return Colors(
|
return Colors(
|
||||||
|
@ -92,6 +92,12 @@ val Colors.secondarySurface: Color
|
|||||||
val Colors.dialogOverlay: Color
|
val Colors.dialogOverlay: Color
|
||||||
get() = appTheme.dialogOverlay
|
get() = appTheme.dialogOverlay
|
||||||
|
|
||||||
|
val Colors.diffLineAdded: Color
|
||||||
|
get() = appTheme.diffLineAdded
|
||||||
|
|
||||||
|
val Colors.diffLineRemoved: Color
|
||||||
|
get() = appTheme.diffLineRemoved
|
||||||
|
|
||||||
|
|
||||||
enum class Theme(val displayName: String) : DropDownOption {
|
enum class Theme(val displayName: String) : DropDownOption {
|
||||||
LIGHT("Light"),
|
LIGHT("Light"),
|
||||||
|
@ -162,9 +162,9 @@ fun HistoryContentLoaded(
|
|||||||
viewDiffResult != null &&
|
viewDiffResult != null &&
|
||||||
viewDiffResult is ViewDiffResult.Loaded
|
viewDiffResult is ViewDiffResult.Loaded
|
||||||
) {
|
) {
|
||||||
val diffResult = viewDiffResult.diffResult
|
when (val diffResult = viewDiffResult.diffResult) {
|
||||||
if (diffResult is DiffResult.Text) {
|
is DiffResult.Text -> {
|
||||||
TextDiff(
|
HunkUnifiedTextDiff(
|
||||||
diffEntryType = viewDiffResult.diffEntryType,
|
diffEntryType = viewDiffResult.diffEntryType,
|
||||||
scrollState = scrollState,
|
scrollState = scrollState,
|
||||||
diffResult = diffResult,
|
diffResult = diffResult,
|
||||||
@ -172,13 +172,27 @@ fun HistoryContentLoaded(
|
|||||||
onStageHunk = { _, _ -> },
|
onStageHunk = { _, _ -> },
|
||||||
onResetHunk = { _, _ -> },
|
onResetHunk = { _, _ -> },
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
is DiffResult.TextSplit -> {
|
||||||
|
HunkSplitTextDiff(
|
||||||
|
diffEntryType = viewDiffResult.diffEntryType,
|
||||||
|
scrollState = scrollState,
|
||||||
|
diffResult = diffResult,
|
||||||
|
onUnstageHunk = { _, _ -> },
|
||||||
|
onStageHunk = { _, _ -> },
|
||||||
|
onResetHunk = { _, _ -> },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -10,10 +10,7 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
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
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.LinearProgressIndicator
|
|
||||||
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
|
||||||
@ -34,10 +31,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import app.extensions.lineDelimiter
|
import app.extensions.lineDelimiter
|
||||||
import app.extensions.removeLineDelimiters
|
import app.extensions.removeLineDelimiters
|
||||||
import app.extensions.toStringWithSpaces
|
import app.extensions.toStringWithSpaces
|
||||||
import app.git.DiffEntryType
|
import app.git.*
|
||||||
import app.git.EntryContent
|
|
||||||
import app.git.StatusEntry
|
|
||||||
import app.git.StatusType
|
|
||||||
import app.git.diff.DiffResult
|
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
|
||||||
@ -61,6 +55,7 @@ fun Diff(
|
|||||||
onCloseDiffView: () -> Unit,
|
onCloseDiffView: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val diffResultState = diffViewModel.diffResult.collectAsState()
|
val diffResultState = diffViewModel.diffResult.collectAsState()
|
||||||
|
val diffType by diffViewModel.diffTypeFlow.collectAsState()
|
||||||
val viewDiffResult = diffResultState.value ?: return
|
val viewDiffResult = diffResultState.value ?: return
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
@ -86,6 +81,7 @@ fun Diff(
|
|||||||
ViewDiffResult.DiffNotFound -> {
|
ViewDiffResult.DiffNotFound -> {
|
||||||
onCloseDiffView()
|
onCloseDiffView()
|
||||||
}
|
}
|
||||||
|
|
||||||
is ViewDiffResult.Loaded -> {
|
is ViewDiffResult.Loaded -> {
|
||||||
val diffEntryType = viewDiffResult.diffEntryType
|
val diffEntryType = viewDiffResult.diffEntryType
|
||||||
val diffEntry = viewDiffResult.diffResult.diffEntry
|
val diffEntry = viewDiffResult.diffResult.diffEntry
|
||||||
@ -95,14 +91,16 @@ fun Diff(
|
|||||||
diffEntryType = diffEntryType,
|
diffEntryType = diffEntryType,
|
||||||
diffEntry = diffEntry,
|
diffEntry = diffEntry,
|
||||||
onCloseDiffView = onCloseDiffView,
|
onCloseDiffView = onCloseDiffView,
|
||||||
stageFile = { diffViewModel.stageFile(it) },
|
diffType = diffType,
|
||||||
unstageFile = { diffViewModel.unstageFile(it) },
|
onStageFile = { diffViewModel.stageFile(it) },
|
||||||
|
onUnstageFile = { diffViewModel.unstageFile(it) },
|
||||||
|
onChangeDiffType = { diffViewModel.changeTextDiffType(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (diffResult is DiffResult.Text) {
|
|
||||||
val scrollState by diffViewModel.lazyListState.collectAsState()
|
val scrollState by diffViewModel.lazyListState.collectAsState()
|
||||||
|
|
||||||
TextDiff(
|
when (diffResult) {
|
||||||
|
is DiffResult.TextSplit -> HunkSplitTextDiff(
|
||||||
diffEntryType = diffEntryType,
|
diffEntryType = diffEntryType,
|
||||||
scrollState = scrollState,
|
scrollState = scrollState,
|
||||||
diffResult = diffResult,
|
diffResult = diffResult,
|
||||||
@ -116,13 +114,40 @@ fun Diff(
|
|||||||
diffViewModel.resetHunk(entry, hunk)
|
diffViewModel.resetHunk(entry, hunk)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else if (diffResult is DiffResult.NonText) {
|
|
||||||
|
is DiffResult.Text -> HunkUnifiedTextDiff(
|
||||||
|
diffEntryType = diffEntryType,
|
||||||
|
scrollState = scrollState,
|
||||||
|
diffResult = diffResult,
|
||||||
|
onUnstageHunk = { entry, hunk ->
|
||||||
|
diffViewModel.unstageHunk(entry, hunk)
|
||||||
|
},
|
||||||
|
onStageHunk = { entry, hunk ->
|
||||||
|
diffViewModel.stageHunk(entry, hunk)
|
||||||
|
},
|
||||||
|
onResetHunk = { entry, hunk ->
|
||||||
|
diffViewModel.resetHunk(entry, hunk)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
is DiffResult.NonText -> {
|
||||||
NonTextDiff(diffResult)
|
NonTextDiff(diffResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewDiffResult.Loading, ViewDiffResult.None -> {
|
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.primaryVariant)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ViewDiffResult.Loading -> {
|
||||||
|
Column {
|
||||||
|
PathOnlyDiffHeader(filePath = viewDiffResult.filePath, onCloseDiffView = onCloseDiffView)
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = MaterialTheme.colors.primaryVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewDiffResult.None -> throw NotImplementedError("None should be a possible state in the diff")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -225,8 +250,9 @@ fun BinaryDiff() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextDiff(
|
fun HunkUnifiedTextDiff(
|
||||||
diffEntryType: DiffEntryType,
|
diffEntryType: DiffEntryType,
|
||||||
scrollState: LazyListState,
|
scrollState: LazyListState,
|
||||||
diffResult: DiffResult.Text,
|
diffResult: DiffResult.Text,
|
||||||
@ -246,7 +272,7 @@ fun TextDiff(
|
|||||||
item {
|
item {
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
HunkHeader(
|
HunkHeader(
|
||||||
hunk = hunk,
|
header = hunk.header,
|
||||||
diffEntryType = diffEntryType,
|
diffEntryType = diffEntryType,
|
||||||
onUnstageHunk = { onUnstageHunk(diffResult.diffEntry, hunk) },
|
onUnstageHunk = { onUnstageHunk(diffResult.diffEntry, hunk) },
|
||||||
onStageHunk = { onStageHunk(diffResult.diffEntry, hunk) },
|
onStageHunk = { onStageHunk(diffResult.diffEntry, hunk) },
|
||||||
@ -269,9 +295,74 @@ fun TextDiff(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HunkSplitTextDiff(
|
||||||
|
diffEntryType: DiffEntryType,
|
||||||
|
scrollState: LazyListState,
|
||||||
|
diffResult: DiffResult.TextSplit,
|
||||||
|
onUnstageHunk: (DiffEntry, Hunk) -> Unit,
|
||||||
|
onStageHunk: (DiffEntry, Hunk) -> Unit,
|
||||||
|
onResetHunk: (DiffEntry, Hunk) -> Unit,
|
||||||
|
) {
|
||||||
|
val hunks = diffResult.hunks
|
||||||
|
|
||||||
|
SelectionContainer {
|
||||||
|
ScrollableLazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
state = scrollState
|
||||||
|
) {
|
||||||
|
for (splitHunk in hunks) {
|
||||||
|
item {
|
||||||
|
DisableSelection {
|
||||||
|
HunkHeader(
|
||||||
|
header = splitHunk.hunk.header,
|
||||||
|
diffEntryType = diffEntryType,
|
||||||
|
onUnstageHunk = { onUnstageHunk(diffResult.diffEntry, splitHunk.hunk) },
|
||||||
|
onStageHunk = { onStageHunk(diffResult.diffEntry, splitHunk.hunk) },
|
||||||
|
onResetHunk = { onResetHunk(diffResult.diffEntry, splitHunk.hunk) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val oldHighestLineNumber = splitHunk.hunk.lines.maxOf { it.displayOldLineNumber }
|
||||||
|
val newHighestLineNumber = splitHunk.hunk.lines.maxOf { it.displayNewLineNumber }
|
||||||
|
val highestLineNumber = max(oldHighestLineNumber, newHighestLineNumber)
|
||||||
|
val highestLineNumberLength = highestLineNumber.toString().count()
|
||||||
|
|
||||||
|
items(splitHunk.lines) { linesPair ->
|
||||||
|
SplitDiffLine(highestLineNumberLength, linesPair.first, linesPair.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SplitDiffLine(highestLineNumberLength: Int, first: Line?, second: Line?) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.secondarySurface)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
if (first != null)
|
||||||
|
SplitDiffLine(highestLineNumberLength, first, first.oldLineNumber + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
|
if (second != null)
|
||||||
|
SplitDiffLine(highestLineNumberLength, second, second.newLineNumber + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HunkHeader(
|
fun HunkHeader(
|
||||||
hunk: Hunk,
|
header: String,
|
||||||
diffEntryType: DiffEntryType,
|
diffEntryType: DiffEntryType,
|
||||||
onUnstageHunk: () -> Unit,
|
onUnstageHunk: () -> Unit,
|
||||||
onStageHunk: () -> Unit,
|
onStageHunk: () -> Unit,
|
||||||
@ -285,7 +376,7 @@ fun HunkHeader(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = hunk.header,
|
text = header,
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
style = MaterialTheme.typography.body1,
|
style = MaterialTheme.typography.body1,
|
||||||
)
|
)
|
||||||
@ -332,13 +423,16 @@ fun HunkHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DiffHeader(
|
private fun DiffHeader(
|
||||||
diffEntryType: DiffEntryType,
|
diffEntryType: DiffEntryType,
|
||||||
diffEntry: DiffEntry,
|
diffEntry: DiffEntry,
|
||||||
|
diffType: TextDiffType,
|
||||||
onCloseDiffView: () -> Unit,
|
onCloseDiffView: () -> Unit,
|
||||||
stageFile: (StatusEntry) -> Unit,
|
onStageFile: (StatusEntry) -> Unit,
|
||||||
unstageFile: (StatusEntry) -> Unit,
|
onUnstageFile: (StatusEntry) -> Unit,
|
||||||
|
onChangeDiffType: (TextDiffType) -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -362,7 +456,76 @@ fun DiffHeader(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
if (diffEntryType.statusType != StatusType.ADDED && diffEntryType.statusType != StatusType.REMOVED) {
|
||||||
|
DiffTypeButtons(diffType = diffType, onChangeDiffType = onChangeDiffType)
|
||||||
|
}
|
||||||
|
|
||||||
if (diffEntryType is DiffEntryType.UncommitedDiff) {
|
if (diffEntryType is DiffEntryType.UncommitedDiff) {
|
||||||
|
UncommitedDiffFileHeaderButtons(
|
||||||
|
diffEntryType,
|
||||||
|
onUnstageFile = onUnstageFile,
|
||||||
|
onStageFile = onStageFile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onCloseDiffView,
|
||||||
|
modifier = Modifier
|
||||||
|
.pointerHoverIcon(PointerIconDefaults.Hand)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource("close.svg"),
|
||||||
|
contentDescription = "Close diff",
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryTextColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DiffTypeButtons(diffType: TextDiffType, onChangeDiffType: (TextDiffType) -> Unit) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Unified",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
)
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = diffType == TextDiffType.SPLIT,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
val newType = if (checked)
|
||||||
|
TextDiffType.SPLIT
|
||||||
|
else
|
||||||
|
TextDiffType.UNIFIED
|
||||||
|
|
||||||
|
onChangeDiffType(newType)
|
||||||
|
},
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
uncheckedThumbColor = MaterialTheme.colors.secondaryVariant,
|
||||||
|
uncheckedTrackColor = MaterialTheme.colors.secondaryVariant,
|
||||||
|
uncheckedTrackAlpha = 0.54f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Split",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
// modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UncommitedDiffFileHeaderButtons(
|
||||||
|
diffEntryType: DiffEntryType.UncommitedDiff,
|
||||||
|
onUnstageFile: (StatusEntry) -> Unit,
|
||||||
|
onStageFile: (StatusEntry) -> Unit
|
||||||
|
) {
|
||||||
val buttonText: String
|
val buttonText: String
|
||||||
val color: Color
|
val color: Color
|
||||||
|
|
||||||
@ -379,14 +542,37 @@ fun DiffHeader(
|
|||||||
backgroundButton = color,
|
backgroundButton = color,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (diffEntryType is DiffEntryType.StagedDiff) {
|
if (diffEntryType is DiffEntryType.StagedDiff) {
|
||||||
unstageFile(diffEntryType.statusEntry)
|
onUnstageFile(diffEntryType.statusEntry)
|
||||||
} else {
|
} else {
|
||||||
stageFile(diffEntryType.statusEntry)
|
onStageFile(diffEntryType.statusEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun PathOnlyDiffHeader(
|
||||||
|
filePath: String,
|
||||||
|
onCloseDiffView: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
.background(MaterialTheme.colors.headerBackground)
|
||||||
|
.padding(start = 8.dp, end = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = filePath,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onCloseDiffView,
|
onClick = onCloseDiffView,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -407,15 +593,9 @@ fun DiffLine(
|
|||||||
line: Line,
|
line: Line,
|
||||||
) {
|
) {
|
||||||
val backgroundColor = when (line.lineType) {
|
val backgroundColor = when (line.lineType) {
|
||||||
LineType.ADDED -> {
|
LineType.ADDED -> MaterialTheme.colors.diffLineAdded
|
||||||
Color(0x77a9d49b)
|
LineType.REMOVED -> MaterialTheme.colors.diffLineRemoved
|
||||||
}
|
LineType.CONTEXT -> MaterialTheme.colors.background
|
||||||
LineType.REMOVED -> {
|
|
||||||
Color(0x77dea2a2)
|
|
||||||
}
|
|
||||||
LineType.CONTEXT -> {
|
|
||||||
MaterialTheme.colors.background
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -442,9 +622,42 @@ fun DiffLine(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DiffLineText(line.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SplitDiffLine(
|
||||||
|
highestLineNumberLength: Int,
|
||||||
|
line: Line,
|
||||||
|
lineNumber: Int,
|
||||||
|
) {
|
||||||
|
val backgroundColor = when (line.lineType) {
|
||||||
|
LineType.ADDED -> MaterialTheme.colors.diffLineAdded
|
||||||
|
LineType.REMOVED -> MaterialTheme.colors.diffLineRemoved
|
||||||
|
LineType.CONTEXT -> MaterialTheme.colors.background
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(backgroundColor)
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
) {
|
||||||
|
DisableSelection {
|
||||||
|
LineNumber(
|
||||||
|
text = lineNumber.toStringWithSpaces(highestLineNumberLength),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DiffLineText(line.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DiffLineText(text: String) {
|
||||||
Row {
|
Row {
|
||||||
Text(
|
Text(
|
||||||
text = line.text.replace( // TODO this replace is a workaround until this issue gets fixed https://github.com/JetBrains/compose-jb/issues/615
|
text = text.replace(
|
||||||
"\t",
|
"\t",
|
||||||
" "
|
" "
|
||||||
).removeLineDelimiters(),
|
).removeLineDelimiters(),
|
||||||
@ -456,7 +669,7 @@ fun DiffLine(
|
|||||||
overflow = TextOverflow.Visible,
|
overflow = TextOverflow.Visible,
|
||||||
)
|
)
|
||||||
|
|
||||||
val lineDelimiter = line.text.lineDelimiter
|
val lineDelimiter = text.lineDelimiter
|
||||||
|
|
||||||
// Display line delimiter in its own text with a maxLines = 1. This will fix the issue
|
// Display line delimiter in its own text with a maxLines = 1. This will fix the issue
|
||||||
// where copying a line didn't contain the line ending & also fix the issue where the text line would
|
// where copying a line didn't contain the line ending & also fix the issue where the text line would
|
||||||
@ -470,7 +683,6 @@ fun DiffLine(
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LineNumber(text: String) {
|
fun LineNumber(text: String) {
|
@ -26,6 +26,7 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.PathBuilder
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
@ -102,6 +103,7 @@ fun Log(
|
|||||||
val logStatus = logStatusState.value
|
val logStatus = logStatusState.value
|
||||||
val showLogDialog by logViewModel.logDialog.collectAsState()
|
val showLogDialog by logViewModel.logDialog.collectAsState()
|
||||||
|
|
||||||
|
|
||||||
val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) {
|
val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) {
|
||||||
selectedItem.revCommit
|
selectedItem.revCommit
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.preferences.AppPreferences
|
import app.preferences.AppSettings
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
@ -14,7 +14,7 @@ class BranchesViewModel @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 appSettings: AppSettings,
|
||||||
) : ExpandableViewModel(true) {
|
) : ExpandableViewModel(true) {
|
||||||
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
private val _branches = MutableStateFlow<List<Ref>>(listOf())
|
||||||
val branches: StateFlow<List<Ref>>
|
val branches: StateFlow<List<Ref>>
|
||||||
@ -51,7 +51,7 @@ class BranchesViewModel @Inject constructor(
|
|||||||
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
|
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
) { git ->
|
) { git ->
|
||||||
mergeManager.mergeBranch(git, ref, appPreferences.ffMerge)
|
mergeManager.mergeBranch(git, ref, appSettings.ffMerge)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteBranch(branch: Ref) = tabState.safeProcessing(
|
fun deleteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
//asdasd
|
||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import app.exceptions.MissingDiffEntryException
|
import app.exceptions.MissingDiffEntryException
|
||||||
import app.extensions.delayedStateChange
|
import app.extensions.delayedStateChange
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.git.diff.Hunk
|
import app.git.diff.*
|
||||||
|
import app.preferences.AppSettings
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import java.lang.Integer.max
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD = 200L
|
private const val DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD = 200L
|
||||||
@ -16,10 +22,30 @@ class DiffViewModel @Inject constructor(
|
|||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val diffManager: DiffManager,
|
private val diffManager: DiffManager,
|
||||||
private val statusManager: StatusManager,
|
private val statusManager: StatusManager,
|
||||||
|
private val settings: AppSettings,
|
||||||
) {
|
) {
|
||||||
private val _diffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.Loading)
|
private val _diffResult = MutableStateFlow<ViewDiffResult>(ViewDiffResult.Loading(""))
|
||||||
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
|
val diffResult: StateFlow<ViewDiffResult?> = _diffResult
|
||||||
|
|
||||||
|
val diffTypeFlow = settings.textDiffTypeFlow
|
||||||
|
private var diffEntryType: DiffEntryType? = null
|
||||||
|
private var diffTypeFlowChangesCount = 0
|
||||||
|
|
||||||
|
private var diffJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
tabState.managerScope.launch {
|
||||||
|
diffTypeFlow.collect {
|
||||||
|
val diffEntryType = this@DiffViewModel.diffEntryType
|
||||||
|
if (diffTypeFlowChangesCount > 0 && diffEntryType != null) { // Ignore the first time the flow triggers, we only care about updates
|
||||||
|
updateDiff(diffEntryType)
|
||||||
|
}
|
||||||
|
|
||||||
|
diffTypeFlowChangesCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val lazyListState = MutableStateFlow(
|
val lazyListState = MutableStateFlow(
|
||||||
LazyListState(
|
LazyListState(
|
||||||
0,
|
0,
|
||||||
@ -27,10 +53,10 @@ class DiffViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO Cancel job if the user closed the diff view while loading
|
fun updateDiff(diffEntryType: DiffEntryType) {
|
||||||
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation(
|
diffJob = tabState.runOperation(refreshType = RefreshType.NONE) { git ->
|
||||||
refreshType = RefreshType.NONE,
|
this.diffEntryType = diffEntryType
|
||||||
) { git ->
|
|
||||||
var oldDiffEntryType: DiffEntryType? = null
|
var oldDiffEntryType: DiffEntryType? = null
|
||||||
val oldDiffResult = _diffResult.value
|
val oldDiffResult = _diffResult.value
|
||||||
|
|
||||||
@ -38,10 +64,10 @@ class DiffViewModel @Inject constructor(
|
|||||||
oldDiffEntryType = oldDiffResult.diffEntryType
|
oldDiffEntryType = oldDiffResult.diffEntryType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If it's a different file or different state (index or workdir), reset the scroll state
|
// If it's a different file or different state (index or workdir), reset the scroll state
|
||||||
if (oldDiffEntryType != null &&
|
if (oldDiffEntryType != null &&
|
||||||
oldDiffEntryType is DiffEntryType.UncommitedDiff && diffEntryType is DiffEntryType.UncommitedDiff &&
|
oldDiffEntryType is DiffEntryType.UncommitedDiff &&
|
||||||
|
diffEntryType is DiffEntryType.UncommitedDiff &&
|
||||||
oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath
|
oldDiffEntryType.statusEntry.filePath != diffEntryType.statusEntry.filePath
|
||||||
) {
|
) {
|
||||||
lazyListState.value = LazyListState(
|
lazyListState.value = LazyListState(
|
||||||
@ -50,14 +76,30 @@ class DiffViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isFirstLoad = oldDiffResult is ViewDiffResult.Loading && oldDiffResult.filePath.isEmpty()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
delayedStateChange(
|
delayedStateChange(
|
||||||
delayMs = DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD,
|
delayMs = if (isFirstLoad) 0 else DIFF_MIN_TIME_IN_MS_TO_SHOW_LOAD,
|
||||||
onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading }
|
onDelayTriggered = { _diffResult.value = ViewDiffResult.Loading(diffEntryType.filePath) }
|
||||||
) {
|
) {
|
||||||
val diffFormat = diffManager.diffFormat(git, diffEntryType)
|
val diffFormat = diffManager.diffFormat(git, diffEntryType)
|
||||||
|
val diffEntry = diffFormat.diffEntry
|
||||||
|
if (
|
||||||
|
diffTypeFlow.value == TextDiffType.SPLIT &&
|
||||||
|
diffFormat is DiffResult.Text &&
|
||||||
|
diffEntry.changeType != DiffEntry.ChangeType.ADD &&
|
||||||
|
diffEntry.changeType != DiffEntry.ChangeType.DELETE
|
||||||
|
) {
|
||||||
|
val splitHunkList = generateSplitDiffFormat(diffFormat)
|
||||||
|
_diffResult.value = ViewDiffResult.Loaded(
|
||||||
|
diffEntryType,
|
||||||
|
DiffResult.TextSplit(diffEntry, splitHunkList)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
_diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
|
_diffResult.value = ViewDiffResult.Loaded(diffEntryType, diffFormat)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
if (ex is MissingDiffEntryException) {
|
if (ex is MissingDiffEntryException) {
|
||||||
tabState.refreshData(refreshType = RefreshType.UNCOMMITED_CHANGES)
|
tabState.refreshData(refreshType = RefreshType.UNCOMMITED_CHANGES)
|
||||||
@ -66,6 +108,99 @@ class DiffViewModel @Inject constructor(
|
|||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateSplitDiffFormat(diffFormat: DiffResult.Text): List<SplitHunk> {
|
||||||
|
val unifiedHunksList = diffFormat.hunks
|
||||||
|
val hunksList = mutableListOf<SplitHunk>()
|
||||||
|
|
||||||
|
for (hunk in unifiedHunksList) {
|
||||||
|
val linesNewSideCount =
|
||||||
|
hunk.lines.count { it.lineType == LineType.ADDED || it.lineType == LineType.CONTEXT }
|
||||||
|
val linesOldSideCount =
|
||||||
|
hunk.lines.count { it.lineType == LineType.REMOVED || it.lineType == LineType.CONTEXT }
|
||||||
|
|
||||||
|
val addedLines = hunk.lines.filter { it.lineType == LineType.ADDED }
|
||||||
|
val removedLines = hunk.lines.filter { it.lineType == LineType.REMOVED }
|
||||||
|
|
||||||
|
val maxLinesCountOfBothParts = max(linesNewSideCount, linesOldSideCount)
|
||||||
|
|
||||||
|
val oldLinesArray = arrayOfNulls<Line?>(maxLinesCountOfBothParts)
|
||||||
|
val newLinesArray = arrayOfNulls<Line?>(maxLinesCountOfBothParts)
|
||||||
|
|
||||||
|
val lines = hunk.lines
|
||||||
|
val firstLineOldNumber = hunk.lines.first().oldLineNumber
|
||||||
|
val firstLineNewNumber = hunk.lines.first().newLineNumber
|
||||||
|
|
||||||
|
val firstLine = if (maxLinesCountOfBothParts == linesOldSideCount) {
|
||||||
|
firstLineOldNumber
|
||||||
|
} else
|
||||||
|
firstLineNewNumber
|
||||||
|
|
||||||
|
val contextLines = lines.filter { it.lineType == LineType.CONTEXT }
|
||||||
|
|
||||||
|
for (contextLine in contextLines) {
|
||||||
|
|
||||||
|
val lineNumber = if (maxLinesCountOfBothParts == linesOldSideCount) {
|
||||||
|
contextLine.oldLineNumber
|
||||||
|
} else
|
||||||
|
contextLine.newLineNumber
|
||||||
|
|
||||||
|
oldLinesArray[lineNumber - firstLine] = contextLine
|
||||||
|
newLinesArray[lineNumber - firstLine] = contextLine
|
||||||
|
}
|
||||||
|
|
||||||
|
for (removedLine in removedLines) {
|
||||||
|
val previousLinesToCurrent = lines.takeWhile { it != removedLine }
|
||||||
|
val previousContextLine = previousLinesToCurrent.lastOrNull { it.lineType == LineType.CONTEXT }
|
||||||
|
|
||||||
|
val contextArrayPosition = if (previousContextLine != null)
|
||||||
|
oldLinesArray.indexOf(previousContextLine)
|
||||||
|
else
|
||||||
|
-1
|
||||||
|
|
||||||
|
// Get the position the list of null position of the array
|
||||||
|
val availableIndexes =
|
||||||
|
newLinesArray.mapIndexed { index, line ->
|
||||||
|
if (line != null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
index
|
||||||
|
}.filterNotNull()
|
||||||
|
val nextAvailableLinePosition = availableIndexes.first { index -> index > contextArrayPosition }
|
||||||
|
oldLinesArray[nextAvailableLinePosition] = removedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
for (addedLine in addedLines) {
|
||||||
|
val previousLinesToCurrent = lines.takeWhile { it != addedLine }
|
||||||
|
val previousContextLine = previousLinesToCurrent.lastOrNull { it.lineType == LineType.CONTEXT }
|
||||||
|
|
||||||
|
val contextArrayPosition = if (previousContextLine != null)
|
||||||
|
newLinesArray.indexOf(previousContextLine)
|
||||||
|
else
|
||||||
|
-1
|
||||||
|
|
||||||
|
val availableIndexes =
|
||||||
|
newLinesArray.mapIndexed { index, line -> if (line != null) null else index }.filterNotNull()
|
||||||
|
val newLinePosition = availableIndexes.first { index -> index > contextArrayPosition }
|
||||||
|
|
||||||
|
newLinesArray[newLinePosition] = addedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
val newHunkLines = mutableListOf<Pair<Line?, Line?>>()
|
||||||
|
|
||||||
|
for (i in 0 until maxLinesCountOfBothParts) {
|
||||||
|
val old = oldLinesArray[i]
|
||||||
|
val new = newLinesArray[i]
|
||||||
|
|
||||||
|
newHunkLines.add(old to new)
|
||||||
|
}
|
||||||
|
|
||||||
|
hunksList.add(SplitHunk(hunk, newHunkLines))
|
||||||
|
}
|
||||||
|
|
||||||
|
return hunksList
|
||||||
|
}
|
||||||
|
|
||||||
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation(
|
||||||
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
refreshType = RefreshType.UNCOMMITED_CHANGES,
|
||||||
@ -97,6 +232,12 @@ class DiffViewModel @Inject constructor(
|
|||||||
) { git ->
|
) { git ->
|
||||||
statusManager.unstage(git, statusEntry)
|
statusManager.unstage(git, statusEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancelRunningJobs() {
|
||||||
|
diffJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeTextDiffType(newDiffType: TextDiffType) {
|
||||||
|
settings.textDiffType = newDiffType
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import app.git.DiffEntryType
|
|||||||
import app.git.DiffManager
|
import app.git.DiffManager
|
||||||
import app.git.RefreshType
|
import app.git.RefreshType
|
||||||
import app.git.TabState
|
import app.git.TabState
|
||||||
|
import app.preferences.AppSettings
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
@ -15,6 +16,7 @@ import javax.inject.Inject
|
|||||||
class HistoryViewModel @Inject constructor(
|
class HistoryViewModel @Inject constructor(
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
private val diffManager: DiffManager,
|
private val diffManager: DiffManager,
|
||||||
|
private val settings: AppSettings,
|
||||||
) {
|
) {
|
||||||
private val _historyState = MutableStateFlow<HistoryState>(HistoryState.Loading(""))
|
private val _historyState = MutableStateFlow<HistoryState>(HistoryState.Loading(""))
|
||||||
val historyState: StateFlow<HistoryState> = _historyState
|
val historyState: StateFlow<HistoryState> = _historyState
|
||||||
|
@ -6,7 +6,7 @@ import app.extensions.delayedStateChange
|
|||||||
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.preferences.AppPreferences
|
import app.preferences.AppSettings
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
import app.ui.log.LogDialog
|
import app.ui.log.LogDialog
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -40,7 +40,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 appSettings: AppSettings,
|
||||||
) {
|
) {
|
||||||
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
|
||||||
|
|
||||||
@ -66,12 +66,12 @@ class LogViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
appPreferences.commitsLimitEnabledFlow.collect {
|
appSettings.commitsLimitEnabledFlow.collect {
|
||||||
tabState.refreshData(RefreshType.ONLY_LOG)
|
tabState.refreshData(RefreshType.ONLY_LOG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
appPreferences.commitsLimitFlow.collect {
|
appSettings.commitsLimitFlow.collect {
|
||||||
tabState.refreshData(RefreshType.ONLY_LOG)
|
tabState.refreshData(RefreshType.ONLY_LOG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,13 +90,13 @@ class LogViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val hasUncommitedChanges = statusSummary.total > 0
|
val hasUncommitedChanges = statusSummary.total > 0
|
||||||
val commitsLimit = if (appPreferences.commitsLimitEnabled) {
|
val commitsLimit = if (appSettings.commitsLimitEnabled) {
|
||||||
appPreferences.commitsLimit
|
appSettings.commitsLimit
|
||||||
} else
|
} else
|
||||||
Int.MAX_VALUE
|
Int.MAX_VALUE
|
||||||
|
|
||||||
val commitsLimitDisplayed = if (appPreferences.commitsLimitEnabled) {
|
val commitsLimitDisplayed = if (appSettings.commitsLimitEnabled) {
|
||||||
appPreferences.commitsLimit
|
appSettings.commitsLimit
|
||||||
} else
|
} else
|
||||||
-1
|
-1
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ class LogViewModel @Inject constructor(
|
|||||||
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
|
fun mergeBranch(ref: Ref) = tabState.safeProcessing(
|
||||||
refreshType = RefreshType.ALL_DATA,
|
refreshType = RefreshType.ALL_DATA,
|
||||||
) { git ->
|
) { git ->
|
||||||
mergeManager.mergeBranch(git, ref, appPreferences.ffMerge)
|
mergeManager.mergeBranch(git, ref, appSettings.ffMerge)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteBranch(branch: Ref) = tabState.safeProcessing(
|
fun deleteBranch(branch: Ref) = tabState.safeProcessing(
|
||||||
|
@ -1,60 +1,60 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import app.preferences.AppPreferences
|
import app.preferences.AppSettings
|
||||||
import app.theme.Theme
|
import app.theme.Theme
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class SettingsViewModel @Inject constructor(
|
class SettingsViewModel @Inject constructor(
|
||||||
val appPreferences: AppPreferences,
|
val appSettings: AppSettings,
|
||||||
) {
|
) {
|
||||||
// Temporary values to detect changed variables
|
// Temporary values to detect changed variables
|
||||||
var commitsLimit: Int = -1
|
var commitsLimit: Int = -1
|
||||||
|
|
||||||
val themeState = appPreferences.themeState
|
val themeState = appSettings.themeState
|
||||||
val customThemeFlow = appPreferences.customThemeFlow
|
val customThemeFlow = appSettings.customThemeFlow
|
||||||
val ffMergeFlow = appPreferences.ffMergeFlow
|
val ffMergeFlow = appSettings.ffMergeFlow
|
||||||
val commitsLimitEnabledFlow = appPreferences.commitsLimitEnabledFlow
|
val commitsLimitEnabledFlow = appSettings.commitsLimitEnabledFlow
|
||||||
|
|
||||||
var scaleUi: Float
|
var scaleUi: Float
|
||||||
get() = appPreferences.scaleUi
|
get() = appSettings.scaleUi
|
||||||
set(value) {
|
set(value) {
|
||||||
appPreferences.scaleUi = value
|
appSettings.scaleUi = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var commitsLimitEnabled: Boolean
|
var commitsLimitEnabled: Boolean
|
||||||
get() = appPreferences.commitsLimitEnabled
|
get() = appSettings.commitsLimitEnabled
|
||||||
set(value) {
|
set(value) {
|
||||||
appPreferences.commitsLimitEnabled = value
|
appSettings.commitsLimitEnabled = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var ffMerge: Boolean
|
var ffMerge: Boolean
|
||||||
get() = appPreferences.ffMerge
|
get() = appSettings.ffMerge
|
||||||
set(value) {
|
set(value) {
|
||||||
appPreferences.ffMerge = value
|
appSettings.ffMerge = value
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme: Theme
|
var theme: Theme
|
||||||
get() = appPreferences.theme
|
get() = appSettings.theme
|
||||||
set(value) {
|
set(value) {
|
||||||
appPreferences.theme = value
|
appSettings.theme = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCustomTheme(filePath: String) {
|
fun saveCustomTheme(filePath: String) {
|
||||||
appPreferences.saveCustomTheme(filePath)
|
appSettings.saveCustomTheme(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun resetInfo() {
|
fun resetInfo() {
|
||||||
commitsLimit = appPreferences.commitsLimit
|
commitsLimit = appSettings.commitsLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePendingChanges() {
|
fun savePendingChanges() {
|
||||||
val commitsLimit = this.commitsLimit
|
val commitsLimit = this.commitsLimit
|
||||||
|
|
||||||
if (appPreferences.commitsLimit != commitsLimit) {
|
if (appSettings.commitsLimit != commitsLimit) {
|
||||||
appPreferences.commitsLimit = commitsLimit
|
appSettings.commitsLimit = commitsLimit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -352,8 +352,10 @@ class TabViewModel @Inject constructor(
|
|||||||
diffViewModel = diffViewModelProvider.get()
|
diffViewModel = diffViewModelProvider.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diffViewModel?.cancelRunningJobs()
|
||||||
diffViewModel?.updateDiff(diffSelected)
|
diffViewModel?.updateDiff(diffSelected)
|
||||||
} else {
|
} else {
|
||||||
|
diffViewModel?.cancelRunningJobs()
|
||||||
diffViewModel = null // Free the view model from the memory if not being used.
|
diffViewModel = null // Free the view model from the memory if not being used.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package app.viewmodels
|
package app.viewmodels
|
||||||
|
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
|
import app.git.TextDiffType
|
||||||
import app.git.diff.DiffResult
|
import app.git.diff.DiffResult
|
||||||
|
|
||||||
sealed interface ViewDiffResult {
|
sealed interface ViewDiffResult {
|
||||||
object None : ViewDiffResult
|
object None : ViewDiffResult
|
||||||
object Loading : ViewDiffResult
|
|
||||||
|
data class Loading(val filePath: String) : ViewDiffResult
|
||||||
|
|
||||||
object DiffNotFound : ViewDiffResult
|
object DiffNotFound : ViewDiffResult
|
||||||
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult) : ViewDiffResult
|
|
||||||
|
data class Loaded(val diffEntryType: DiffEntryType, val diffResult: DiffResult/*, val diffType: TextDiffType*/) : ViewDiffResult
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user