Added dialog to edit author info

This commit is contained in:
Abdelilah El Aissaoui 2022-06-22 00:53:10 +02:00
parent 43330eb3c4
commit ab8e8c7dbe
8 changed files with 315 additions and 23 deletions

View File

@ -40,3 +40,6 @@ val String.lineDelimiter: String?
else else
null null
} }
val String.nullIfEmpty: String?
get() = this.ifBlank { null }

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

View File

@ -0,0 +1,8 @@
package app.models
data class AuthorInfo(
val globalName: String?,
val globalEmail: String?,
val name: String?,
val email: String?,
)

View File

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

View File

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

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

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

View File

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