Added dialog to edit author info
This commit is contained in:
parent
43330eb3c4
commit
ab8e8c7dbe
@ -40,3 +40,6 @@ val String.lineDelimiter: String?
|
|||||||
else
|
else
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val String.nullIfEmpty: String?
|
||||||
|
get() = this.ifBlank { null }
|
61
src/main/kotlin/app/git/AuthorManager.kt
Normal file
61
src/main/kotlin/app/git/AuthorManager.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package app.git
|
||||||
|
|
||||||
|
import app.models.AuthorInfo
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.storage.file.FileBasedConfig
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AuthorManager @Inject constructor() {
|
||||||
|
suspend fun loadAuthor(git: Git) = withContext(Dispatchers.IO) {
|
||||||
|
val config = git.repository.config
|
||||||
|
val globalConfig = git.repository.config.baseConfig
|
||||||
|
|
||||||
|
val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs)
|
||||||
|
repoConfig.load()
|
||||||
|
|
||||||
|
val globalName = globalConfig.getString("user", null, "name")
|
||||||
|
val globalEmail = globalConfig.getString("user", null, "email")
|
||||||
|
|
||||||
|
val name = repoConfig.getString("user", null, "name")
|
||||||
|
val email = repoConfig.getString("user", null, "email")
|
||||||
|
|
||||||
|
return@withContext AuthorInfo(
|
||||||
|
globalName,
|
||||||
|
globalEmail,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveAuthorInfo(git: Git, newAuthorInfo: AuthorInfo) = withContext(Dispatchers.IO) {
|
||||||
|
val config = git.repository.config
|
||||||
|
val globalConfig = git.repository.config.baseConfig
|
||||||
|
val repoConfig = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs)
|
||||||
|
repoConfig.load()
|
||||||
|
|
||||||
|
if (globalConfig is FileBasedConfig) {
|
||||||
|
globalConfig.setStringProperty("user", null, "name", newAuthorInfo.globalName)
|
||||||
|
globalConfig.setStringProperty("user", null, "email", newAuthorInfo.globalEmail)
|
||||||
|
globalConfig.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.setStringProperty("user", null, "name", newAuthorInfo.name)
|
||||||
|
config.setStringProperty("user", null, "email", newAuthorInfo.email)
|
||||||
|
config.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FileBasedConfig.setStringProperty(
|
||||||
|
section: String,
|
||||||
|
subsection: String?,
|
||||||
|
name: String,
|
||||||
|
value: String?,
|
||||||
|
) {
|
||||||
|
if(value == null) {
|
||||||
|
unset(section, subsection, name)
|
||||||
|
} else {
|
||||||
|
setString(section, subsection, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/main/kotlin/app/models/AuthorInfo.kt
Normal file
8
src/main/kotlin/app/models/AuthorInfo.kt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package app.models
|
||||||
|
|
||||||
|
data class AuthorInfo(
|
||||||
|
val globalName: String?,
|
||||||
|
val globalEmail: String?,
|
||||||
|
val name: String?,
|
||||||
|
val email: String?,
|
||||||
|
)
|
@ -1,6 +1,6 @@
|
|||||||
package app.git
|
package app.models
|
||||||
|
|
||||||
data class UserInfo(
|
data class AuthorInfoSimple(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val email: String?,
|
val email: String?,
|
||||||
)
|
)
|
@ -12,10 +12,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.PointerIcon
|
import androidx.compose.ui.input.pointer.PointerIcon
|
||||||
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
import androidx.compose.ui.input.pointer.pointerHoverIcon
|
||||||
|
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 androidx.compose.ui.unit.sp
|
||||||
|
import app.extensions.handMouseClickable
|
||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
import app.theme.*
|
import app.theme.*
|
||||||
|
import app.ui.dialogs.AuthorDialog
|
||||||
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.dialogs.StashWithMessageDialog
|
||||||
@ -39,7 +42,8 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
val selectedItem by tabViewModel.selectedItem.collectAsState()
|
||||||
val blameState by tabViewModel.blameState.collectAsState()
|
val blameState by tabViewModel.blameState.collectAsState()
|
||||||
val showHistory by tabViewModel.showHistory.collectAsState()
|
val showHistory by tabViewModel.showHistory.collectAsState()
|
||||||
val userInfo by tabViewModel.userInfo.collectAsState()
|
val userInfo by tabViewModel.authorInfoSimple.collectAsState()
|
||||||
|
val showAuthorInfo by tabViewModel.showAuthorInfo.collectAsState()
|
||||||
|
|
||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
||||||
@ -64,6 +68,16 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
showStashWithMessageDialog = false
|
showStashWithMessageDialog = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else if (showAuthorInfo) {
|
||||||
|
val authorViewModel = tabViewModel.authorViewModel
|
||||||
|
if (authorViewModel != null) {
|
||||||
|
AuthorDialog(
|
||||||
|
authorViewModel = authorViewModel,
|
||||||
|
onClose = {
|
||||||
|
tabViewModel.closeAuthorInfoDialog()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@ -107,13 +121,20 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
|
|||||||
.background(MaterialTheme.colors.surface)
|
.background(MaterialTheme.colors.surface)
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.handMouseClickable { tabViewModel.showAuthorInfoDialog() },
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${userInfo.name ?: "Name not set"} <${userInfo.email?: "Email not set"}>",
|
text = "${userInfo.name ?: "Name not set"} <${userInfo.email ?: "Email not set"}>",
|
||||||
color = MaterialTheme.colors.primaryTextColor,
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
150
src/main/kotlin/app/ui/dialogs/AuthorDialog.kt
Normal file
150
src/main/kotlin/app/ui/dialogs/AuthorDialog.kt
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package app.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.mouseClickable
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
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.sp
|
||||||
|
import app.theme.outlinedTextFieldColors
|
||||||
|
import app.theme.primaryTextColor
|
||||||
|
import app.theme.textButtonColors
|
||||||
|
import app.ui.components.PrimaryButton
|
||||||
|
import app.viewmodels.AuthorViewModel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AuthorDialog(
|
||||||
|
authorViewModel: AuthorViewModel,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
val authorInfo by authorViewModel.authorInfo.collectAsState()
|
||||||
|
|
||||||
|
var globalName by remember(authorInfo) { mutableStateOf(authorInfo.globalName.orEmpty()) }
|
||||||
|
var globalEmail by remember(authorInfo) { mutableStateOf(authorInfo.globalEmail.orEmpty()) }
|
||||||
|
var name by remember(authorInfo) { mutableStateOf(authorInfo.name.orEmpty()) }
|
||||||
|
var email by remember(authorInfo) { mutableStateOf(authorInfo.email.orEmpty()) }
|
||||||
|
|
||||||
|
MaterialDialog(onCloseRequested = onClose) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Global settings",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
)
|
||||||
|
|
||||||
|
TextInput(
|
||||||
|
title = "Name",
|
||||||
|
value = globalName,
|
||||||
|
onValueChange = { globalName = it },
|
||||||
|
)
|
||||||
|
TextInput(
|
||||||
|
title = "Email",
|
||||||
|
value = globalEmail,
|
||||||
|
onValueChange = { globalEmail = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Repository settings",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
modifier = Modifier.padding(top = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
TextInput(
|
||||||
|
title = "Name",
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
)
|
||||||
|
TextInput(
|
||||||
|
title = "Email",
|
||||||
|
value = email,
|
||||||
|
onValueChange = { email = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Repository-level values will override global values",
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
colors = textButtonColors(),
|
||||||
|
onClick = {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
PrimaryButton(
|
||||||
|
onClick = {
|
||||||
|
authorViewModel.saveAuthorInfo(
|
||||||
|
globalName,
|
||||||
|
globalEmail,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
text = "Save"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TextInput(
|
||||||
|
title: String,
|
||||||
|
value: String,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(400.dp)
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colors.primaryTextColor,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(100.dp)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = value,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
enabled = enabled,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
colors = outlinedTextFieldColors(),
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
40
src/main/kotlin/app/viewmodels/AuthorViewModel.kt
Normal file
40
src/main/kotlin/app/viewmodels/AuthorViewModel.kt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package app.viewmodels
|
||||||
|
|
||||||
|
import app.extensions.nullIfEmpty
|
||||||
|
import app.git.AuthorManager
|
||||||
|
import app.git.RefreshType
|
||||||
|
import app.git.TabState
|
||||||
|
import app.models.AuthorInfo
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AuthorViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
|
private val authorManager: AuthorManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val _authorInfo = MutableStateFlow(AuthorInfo(null, null, null, null))
|
||||||
|
val authorInfo: StateFlow<AuthorInfo> = _authorInfo
|
||||||
|
|
||||||
|
fun loadAuthorInfo() = tabState.runOperation(
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
showError = true,
|
||||||
|
) { git ->
|
||||||
|
_authorInfo.value = authorManager.loadAuthor(git)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAuthorInfo(globalName: String, globalEmail: String, name: String, email: String) = tabState.runOperation(
|
||||||
|
showError = true,
|
||||||
|
refreshType = RefreshType.REPO_STATE,
|
||||||
|
) { git ->
|
||||||
|
val newAuthorInfo = AuthorInfo(
|
||||||
|
globalName.nullIfEmpty,
|
||||||
|
globalEmail.nullIfEmpty,
|
||||||
|
name.nullIfEmpty,
|
||||||
|
email.nullIfEmpty,
|
||||||
|
)
|
||||||
|
|
||||||
|
authorManager.saveAuthorInfo(git, newAuthorInfo)
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import app.credentials.CredentialsState
|
|||||||
import app.credentials.CredentialsStateManager
|
import app.credentials.CredentialsStateManager
|
||||||
import app.git.*
|
import app.git.*
|
||||||
import app.logging.printLog
|
import app.logging.printLog
|
||||||
|
import app.models.AuthorInfoSimple
|
||||||
import app.newErrorNow
|
import app.newErrorNow
|
||||||
import app.ui.SelectedItem
|
import app.ui.SelectedItem
|
||||||
import app.updates.Update
|
import app.updates.Update
|
||||||
@ -19,7 +20,6 @@ import org.eclipse.jgit.blame.BlameResult
|
|||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
import org.eclipse.jgit.lib.RepositoryState
|
import org.eclipse.jgit.lib.RepositoryState
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import org.eclipse.jgit.storage.file.FileBasedConfig
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@ -46,6 +46,7 @@ class TabViewModel @Inject constructor(
|
|||||||
private val diffViewModelProvider: Provider<DiffViewModel>,
|
private val diffViewModelProvider: Provider<DiffViewModel>,
|
||||||
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
private val rebaseInteractiveViewModelProvider: Provider<RebaseInteractiveViewModel>,
|
||||||
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
private val historyViewModelProvider: Provider<HistoryViewModel>,
|
||||||
|
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||||
private val repositoryManager: RepositoryManager,
|
private val repositoryManager: RepositoryManager,
|
||||||
private val tabState: TabState,
|
private val tabState: TabState,
|
||||||
val appStateManager: AppStateManager,
|
val appStateManager: AppStateManager,
|
||||||
@ -88,12 +89,18 @@ class TabViewModel @Inject constructor(
|
|||||||
private val _showHistory = MutableStateFlow(false)
|
private val _showHistory = MutableStateFlow(false)
|
||||||
val showHistory: StateFlow<Boolean> = _showHistory
|
val showHistory: StateFlow<Boolean> = _showHistory
|
||||||
|
|
||||||
private val _userInfo = MutableStateFlow(UserInfo(null, null))
|
private val _showAuthorInfo = MutableStateFlow(false)
|
||||||
val userInfo: StateFlow<UserInfo> = _userInfo
|
val showAuthorInfo: StateFlow<Boolean> = _showAuthorInfo
|
||||||
|
|
||||||
|
private val _authorInfoSimple = MutableStateFlow(AuthorInfoSimple(null, null))
|
||||||
|
val authorInfoSimple: StateFlow<AuthorInfoSimple> = _authorInfoSimple
|
||||||
|
|
||||||
var historyViewModel: HistoryViewModel? = null
|
var historyViewModel: HistoryViewModel? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var authorViewModel: AuthorViewModel? = null
|
||||||
|
private set
|
||||||
|
|
||||||
val showError = MutableStateFlow(false)
|
val showError = MutableStateFlow(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -185,27 +192,29 @@ class TabViewModel @Inject constructor(
|
|||||||
printLog(TAG, "Refreshing repository state $newRepoState")
|
printLog(TAG, "Refreshing repository state $newRepoState")
|
||||||
_repositoryState.value = newRepoState
|
_repositoryState.value = newRepoState
|
||||||
|
|
||||||
loadConfigInfo(git)
|
loadAuthorInfo(git)
|
||||||
|
|
||||||
onRepositoryStateChanged(newRepoState)
|
onRepositoryStateChanged(newRepoState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadConfigInfo(git: Git) {
|
private fun loadAuthorInfo(git: Git) {
|
||||||
val config = git.repository.config
|
val config = git.repository.config
|
||||||
config.load()
|
config.load()
|
||||||
val userName = config.getString("user", null, "name")
|
val userName = config.getString("user", null, "name")
|
||||||
val email = config.getString("user", null, "email")
|
val email = config.getString("user", null, "email")
|
||||||
|
|
||||||
// TODO Load file-specific config
|
_authorInfoSimple.value = AuthorInfoSimple(userName, email)
|
||||||
// val fcfg = FileBasedConfig((config as FileBasedConfig).file, git.repository.fs)
|
}
|
||||||
// fcfg.load()
|
|
||||||
// val fname = fcfg.getString("user", null, "name")
|
|
||||||
// val fmail = fcfg.getString("user", null, "email")
|
|
||||||
// println("Fname $fname\nFmail $fmail")
|
|
||||||
|
|
||||||
println(userName)
|
fun showAuthorInfoDialog() {
|
||||||
println(email)
|
authorViewModel = authorViewModelProvider.get()
|
||||||
_userInfo.value = UserInfo(userName, email)
|
authorViewModel?.loadAuthorInfo()
|
||||||
|
_showAuthorInfo.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeAuthorInfoDialog() {
|
||||||
|
_showAuthorInfo.value = false
|
||||||
|
authorViewModel = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRepositoryStateChanged(newRepoState: RepositoryState) {
|
private fun onRepositoryStateChanged(newRepoState: RepositoryState) {
|
||||||
|
Loading…
Reference in New Issue
Block a user