parent
07e1bbd4ed
commit
ddc198a0d7
@ -42,6 +42,7 @@ object AppIcons {
|
|||||||
const val REVERT = "revert.svg"
|
const val REVERT = "revert.svg"
|
||||||
const val SEARCH = "search.svg"
|
const val SEARCH = "search.svg"
|
||||||
const val SETTINGS = "settings.svg"
|
const val SETTINGS = "settings.svg"
|
||||||
|
const val SIGN = "sign.svg"
|
||||||
const val SOURCE = "source.svg"
|
const val SOURCE = "source.svg"
|
||||||
const val START = "start.svg"
|
const val START = "start.svg"
|
||||||
const val STASH = "stash.svg"
|
const val STASH = "stash.svg"
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.jetpackduba.gitnuro.git.config
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.extensions.nullIfEmpty
|
||||||
|
import com.jetpackduba.gitnuro.models.SignOffConfig
|
||||||
|
import org.eclipse.jgit.lib.Repository
|
||||||
|
import org.eclipse.jgit.storage.file.FileBasedConfig
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class LoadSignOffConfigUseCase @Inject constructor() {
|
||||||
|
operator fun invoke(repository: Repository): SignOffConfig {
|
||||||
|
val configFile = File(repository.directory, LocalConfigConstants.CONFIG_FILE_NAME)
|
||||||
|
configFile.createNewFile()
|
||||||
|
|
||||||
|
val config = FileBasedConfig(configFile, repository.fs)
|
||||||
|
config.load()
|
||||||
|
|
||||||
|
val enabled = config.getBoolean(
|
||||||
|
LocalConfigConstants.SignOff.SECTION,
|
||||||
|
null,
|
||||||
|
LocalConfigConstants.SignOff.FIELD_ENABLED,
|
||||||
|
LocalConfigConstants.SignOff.DEFAULT_SIGN_OFF_ENABLED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val format = config.getString(
|
||||||
|
LocalConfigConstants.SignOff.SECTION,
|
||||||
|
null,
|
||||||
|
LocalConfigConstants.SignOff.FIELD_FORMAT
|
||||||
|
)?.nullIfEmpty ?: LocalConfigConstants.SignOff.DEFAULT_SIGN_OFF_FORMAT
|
||||||
|
|
||||||
|
return SignOffConfig(enabled, format)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jetpackduba.gitnuro.git.config
|
||||||
|
|
||||||
|
object LocalConfigConstants {
|
||||||
|
const val CONFIG_FILE_NAME = "gitnuro"
|
||||||
|
|
||||||
|
object SignOff {
|
||||||
|
const val SECTION = "signoff"
|
||||||
|
const val FIELD_ENABLED = "enabled"
|
||||||
|
const val FIELD_FORMAT = "format"
|
||||||
|
|
||||||
|
const val DEFAULT_SIGN_OFF_FORMAT_USER = "%user"
|
||||||
|
const val DEFAULT_SIGN_OFF_FORMAT_EMAIL = "%email"
|
||||||
|
const val DEFAULT_SIGN_OFF_FORMAT = "Signed-off-by: $DEFAULT_SIGN_OFF_FORMAT_USER <$DEFAULT_SIGN_OFF_FORMAT_EMAIL>"
|
||||||
|
const val DEFAULT_SIGN_OFF_ENABLED = false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.jetpackduba.gitnuro.git.config
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.models.SignOffConfig
|
||||||
|
import org.eclipse.jgit.lib.Repository
|
||||||
|
import org.eclipse.jgit.storage.file.FileBasedConfig
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SaveLocalRepositoryConfigUseCase @Inject constructor() {
|
||||||
|
operator fun invoke(
|
||||||
|
repository: Repository,
|
||||||
|
signOffConfig: SignOffConfig,
|
||||||
|
) {
|
||||||
|
val configFile = File(repository.directory, LocalConfigConstants.CONFIG_FILE_NAME)
|
||||||
|
configFile.createNewFile()
|
||||||
|
|
||||||
|
val config = FileBasedConfig(configFile, repository.fs)
|
||||||
|
|
||||||
|
config.setBoolean(
|
||||||
|
LocalConfigConstants.SignOff.SECTION,
|
||||||
|
null,
|
||||||
|
LocalConfigConstants.SignOff.FIELD_ENABLED,
|
||||||
|
signOffConfig.isEnabled
|
||||||
|
)
|
||||||
|
|
||||||
|
config.setString(
|
||||||
|
LocalConfigConstants.SignOff.SECTION,
|
||||||
|
null,
|
||||||
|
LocalConfigConstants.SignOff.FIELD_FORMAT,
|
||||||
|
signOffConfig.format
|
||||||
|
)
|
||||||
|
|
||||||
|
config.save()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
package com.jetpackduba.gitnuro.git.workspace
|
package com.jetpackduba.gitnuro.git.workspace
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.git.author.LoadAuthorUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.config.LoadSignOffConfigUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.config.LocalConfigConstants
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
@ -7,15 +10,31 @@ import org.eclipse.jgit.lib.PersonIdent
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DoCommitUseCase @Inject constructor() {
|
class DoCommitUseCase @Inject constructor(
|
||||||
|
private val loadSignOffConfigUseCase: LoadSignOffConfigUseCase,
|
||||||
|
private val loadAuthorUseCase: LoadAuthorUseCase,
|
||||||
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
git: Git,
|
git: Git,
|
||||||
message: String,
|
message: String,
|
||||||
amend: Boolean,
|
amend: Boolean,
|
||||||
author: PersonIdent?,
|
author: PersonIdent?,
|
||||||
): RevCommit = withContext(Dispatchers.IO) {
|
): RevCommit = withContext(Dispatchers.IO) {
|
||||||
|
val signOffConfig = loadSignOffConfigUseCase(git.repository)
|
||||||
|
|
||||||
|
val finalMessage = if(signOffConfig.isEnabled) {
|
||||||
|
val authorToSign = author ?: loadAuthorUseCase(git).toPersonIdent()
|
||||||
|
|
||||||
|
val signature = signOffConfig.format
|
||||||
|
.replace(LocalConfigConstants.SignOff.DEFAULT_SIGN_OFF_FORMAT_USER, authorToSign.name)
|
||||||
|
.replace(LocalConfigConstants.SignOff.DEFAULT_SIGN_OFF_FORMAT_EMAIL, authorToSign.emailAddress)
|
||||||
|
|
||||||
|
"$message\n\n$signature"
|
||||||
|
} else
|
||||||
|
message
|
||||||
|
|
||||||
git.commit()
|
git.commit()
|
||||||
.setMessage(message)
|
.setMessage(finalMessage)
|
||||||
.setAllowEmpty(amend) // Only allow empty commits when amending
|
.setAllowEmpty(amend) // Only allow empty commits when amending
|
||||||
.setAmend(amend)
|
.setAmend(amend)
|
||||||
.setAuthor(author)
|
.setAuthor(author)
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
package com.jetpackduba.gitnuro.models
|
package com.jetpackduba.gitnuro.models
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent
|
||||||
|
|
||||||
data class AuthorInfo(
|
data class AuthorInfo(
|
||||||
val globalName: String?,
|
val globalName: String?,
|
||||||
val globalEmail: String?,
|
val globalEmail: String?,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val email: String?,
|
val email: String?,
|
||||||
)
|
) {
|
||||||
|
fun toPersonIdent() = PersonIdent(
|
||||||
|
name ?: globalName ?: "",
|
||||||
|
email ?: globalEmail ?: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.jetpackduba.gitnuro.models
|
||||||
|
|
||||||
|
data class SignOffConfig(
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
val format: String,
|
||||||
|
)
|
@ -24,6 +24,7 @@ import com.jetpackduba.gitnuro.git.DiffEntryType
|
|||||||
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.gitnuroDynamicViewModel
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||||
import com.jetpackduba.gitnuro.ui.diff.Diff
|
import com.jetpackduba.gitnuro.ui.diff.Diff
|
||||||
import com.jetpackduba.gitnuro.ui.log.Log
|
import com.jetpackduba.gitnuro.ui.log.Log
|
||||||
@ -53,6 +54,7 @@ fun RepositoryOpenPage(
|
|||||||
var showNewBranchDialog by remember { mutableStateOf(false) }
|
var showNewBranchDialog by remember { mutableStateOf(false) }
|
||||||
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
var showStashWithMessageDialog by remember { mutableStateOf(false) }
|
||||||
var showQuickActionsDialog by remember { mutableStateOf(false) }
|
var showQuickActionsDialog by remember { mutableStateOf(false) }
|
||||||
|
var showSignOffDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showNewBranchDialog) {
|
if (showNewBranchDialog) {
|
||||||
NewBranchDialog(
|
NewBranchDialog(
|
||||||
@ -93,9 +95,15 @@ fun RepositoryOpenPage(
|
|||||||
QuickActionType.OPEN_DIR_IN_FILE_MANAGER -> tabViewModel.openFolderInFileExplorer()
|
QuickActionType.OPEN_DIR_IN_FILE_MANAGER -> tabViewModel.openFolderInFileExplorer()
|
||||||
QuickActionType.CLONE -> onShowCloneDialog()
|
QuickActionType.CLONE -> onShowCloneDialog()
|
||||||
QuickActionType.REFRESH -> tabViewModel.refreshAll()
|
QuickActionType.REFRESH -> tabViewModel.refreshAll()
|
||||||
|
QuickActionType.SIGN_OFF -> showSignOffDialog = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
} else if (showSignOffDialog) {
|
||||||
|
SignOffDialog(
|
||||||
|
viewModel = gitnuroDynamicViewModel(),
|
||||||
|
onClose = { showSignOffDialog = false },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
@ -43,10 +44,7 @@ import com.jetpackduba.gitnuro.keybindings.KeybindingOption
|
|||||||
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
import com.jetpackduba.gitnuro.keybindings.matchesBinding
|
||||||
import com.jetpackduba.gitnuro.theme.*
|
import com.jetpackduba.gitnuro.theme.*
|
||||||
import com.jetpackduba.gitnuro.ui.components.*
|
import com.jetpackduba.gitnuro.ui.components.*
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
|
import com.jetpackduba.gitnuro.ui.context_menu.*
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
|
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.EntryType
|
|
||||||
import com.jetpackduba.gitnuro.ui.context_menu.statusEntriesContextMenuItems
|
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.CommitAuthorDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.CommitAuthorDialog
|
||||||
import com.jetpackduba.gitnuro.viewmodels.CommitterDataRequestState
|
import com.jetpackduba.gitnuro.viewmodels.CommitterDataRequestState
|
||||||
import com.jetpackduba.gitnuro.viewmodels.StageState
|
import com.jetpackduba.gitnuro.viewmodels.StageState
|
||||||
@ -306,7 +304,7 @@ fun UncommitedChanges(
|
|||||||
onAmendChecked = { isAmend ->
|
onAmendChecked = { isAmend ->
|
||||||
statusViewModel.amend(isAmend)
|
statusViewModel.amend(isAmend)
|
||||||
},
|
},
|
||||||
onCommit = { doCommit() },
|
onCommit = doCommit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,8 +320,6 @@ fun UncommitedChangesButtons(
|
|||||||
onAmendChecked: (Boolean) -> Unit,
|
onAmendChecked: (Boolean) -> Unit,
|
||||||
onCommit: () -> Unit
|
onCommit: () -> Unit
|
||||||
) {
|
) {
|
||||||
var showDropDownMenu by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val buttonText = if (isAmend)
|
val buttonText = if (isAmend)
|
||||||
"Amend"
|
"Amend"
|
||||||
else
|
else
|
||||||
@ -368,47 +364,8 @@ fun UncommitedChangesButtons(
|
|||||||
onCommit()
|
onCommit()
|
||||||
},
|
},
|
||||||
enabled = canCommit || (canAmend && isAmend),
|
enabled = canCommit || (canAmend && isAmend),
|
||||||
shape = MaterialTheme.shapes.small.copy(topEnd = CornerSize(0.dp), bottomEnd = CornerSize(0.dp))
|
shape = RoundedCornerShape(4.dp)
|
||||||
)
|
)
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(1.dp)
|
|
||||||
.height(36.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(36.dp)
|
|
||||||
.clip(MaterialTheme.shapes.small.copy(topStart = CornerSize(0.dp), bottomStart = CornerSize(0.dp)))
|
|
||||||
.background(MaterialTheme.colors.primary)
|
|
||||||
.handMouseClickable { showDropDownMenu = true }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.ArrowDropDown,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.align(Alignment.Center),
|
|
||||||
)
|
|
||||||
DropdownMenu(
|
|
||||||
onDismissRequest = {
|
|
||||||
showDropDownMenu = false
|
|
||||||
},
|
|
||||||
content = {
|
|
||||||
/*DropDownContent(
|
|
||||||
enabled = canAmend,
|
|
||||||
dropDownContentData = DropDownContentData(
|
|
||||||
label = "Amend previous commit",
|
|
||||||
icon = null,
|
|
||||||
onClick = onCommit
|
|
||||||
),
|
|
||||||
onDismiss = { showDropDownMenu = false }
|
|
||||||
)*/
|
|
||||||
},
|
|
||||||
expanded = showDropDownMenu,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,13 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
import com.jetpackduba.gitnuro.managers.AppStateManager
|
|
||||||
import com.jetpackduba.gitnuro.LocalTabScope
|
import com.jetpackduba.gitnuro.LocalTabScope
|
||||||
import com.jetpackduba.gitnuro.di.AppComponent
|
import com.jetpackduba.gitnuro.di.AppComponent
|
||||||
import com.jetpackduba.gitnuro.di.DaggerTabComponent
|
import com.jetpackduba.gitnuro.di.DaggerTabComponent
|
||||||
import com.jetpackduba.gitnuro.di.TabComponent
|
import com.jetpackduba.gitnuro.di.TabComponent
|
||||||
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||||
|
import com.jetpackduba.gitnuro.managers.AppStateManager
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -264,9 +264,12 @@ inline fun <reified T> gitnuroViewModel(): T {
|
|||||||
tab.tabViewModelsHolder.viewModels[T::class] as T
|
tab.tabViewModelsHolder.viewModels[T::class] as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
inline fun <reified T> gitnuroDynamicViewModel(): T {
|
inline fun <reified T> gitnuroDynamicViewModel(): T {
|
||||||
val tab = LocalTabScope.current
|
val tab = LocalTabScope.current
|
||||||
|
|
||||||
return tab.tabViewModelsHolder.dynamicViewModel(T::class) as T
|
return remember(tab) {
|
||||||
|
tab.tabViewModelsHolder.dynamicViewModel(T::class) as T
|
||||||
|
}
|
||||||
}
|
}
|
@ -36,6 +36,7 @@ fun QuickActionsDialog(
|
|||||||
QuickAction(AppIcons.CODE, "Open repository in file manager", QuickActionType.OPEN_DIR_IN_FILE_MANAGER),
|
QuickAction(AppIcons.CODE, "Open repository in file manager", QuickActionType.OPEN_DIR_IN_FILE_MANAGER),
|
||||||
QuickAction(AppIcons.DOWNLOAD, "Clone new repository", QuickActionType.CLONE),
|
QuickAction(AppIcons.DOWNLOAD, "Clone new repository", QuickActionType.CLONE),
|
||||||
QuickAction(AppIcons.REFRESH, "Refresh repository data", QuickActionType.REFRESH),
|
QuickAction(AppIcons.REFRESH, "Refresh repository data", QuickActionType.REFRESH),
|
||||||
|
QuickAction(AppIcons.SIGN, "Signoff config", QuickActionType.SIGN_OFF),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,5 +127,6 @@ data class QuickAction(val icon: String, val title: String, val type: QuickActio
|
|||||||
enum class QuickActionType {
|
enum class QuickActionType {
|
||||||
OPEN_DIR_IN_FILE_MANAGER,
|
OPEN_DIR_IN_FILE_MANAGER,
|
||||||
CLONE,
|
CLONE,
|
||||||
REFRESH;
|
REFRESH,
|
||||||
|
SIGN_OFF
|
||||||
}
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
package com.jetpackduba.gitnuro.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.Checkbox
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusProperties
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
|
import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.SignOffDialogViewModel
|
||||||
|
import com.jetpackduba.gitnuro.viewmodels.SignOffState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignOffDialog(
|
||||||
|
viewModel: SignOffDialogViewModel,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
val state = viewModel.state.collectAsState().value
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.loadSignOffFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var signOffField by remember(viewModel, state) {
|
||||||
|
val signOff = if (state is SignOffState.Loaded) {
|
||||||
|
state.signOffConfig.format
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableStateOf(signOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabledSignOff by remember(viewModel, state) {
|
||||||
|
val signOff = if (state is SignOffState.Loaded) {
|
||||||
|
state.signOffConfig.isEnabled
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableStateOf(signOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
val signOffFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
MaterialDialog(onCloseRequested = onClose) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier.width(IntrinsicSize.Min),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(AppIcons.SIGN),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
.size(64.dp),
|
||||||
|
tint = MaterialTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Edit sign off",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Enable or disable the signoff or adjust its format",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
AdjustableOutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(signOffFieldFocusRequester)
|
||||||
|
.focusProperties {
|
||||||
|
this.next = buttonFieldFocusRequester
|
||||||
|
}
|
||||||
|
.width(300.dp),
|
||||||
|
value = signOffField,
|
||||||
|
enabled = state is SignOffState.Loaded,
|
||||||
|
maxLines = 1,
|
||||||
|
onValueChange = {
|
||||||
|
signOffField = it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.handMouseClickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
) {
|
||||||
|
if (state is SignOffState.Loaded) {
|
||||||
|
enabledSignOff = !enabledSignOff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = enabledSignOff,
|
||||||
|
enabled = state is SignOffState.Loaded,
|
||||||
|
onCheckedChange = {
|
||||||
|
enabledSignOff = it
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
.size(12.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Enable signoff for this repository",
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
) {
|
||||||
|
PrimaryButton(
|
||||||
|
text = "Cancel",
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
onClick = onClose,
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
textColor = MaterialTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(buttonFieldFocusRequester)
|
||||||
|
.focusProperties {
|
||||||
|
this.previous = signOffFieldFocusRequester
|
||||||
|
this.next = signOffFieldFocusRequester
|
||||||
|
},
|
||||||
|
enabled = signOffField.isNotBlank() && state is SignOffState.Loaded,
|
||||||
|
onClick = {
|
||||||
|
viewModel.saveSignOffFormat(enabledSignOff, signOffField)
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
text = "Save"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
signOffFieldFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.jetpackduba.gitnuro.viewmodels
|
||||||
|
|
||||||
|
import com.jetpackduba.gitnuro.git.RefreshType
|
||||||
|
import com.jetpackduba.gitnuro.git.TabState
|
||||||
|
import com.jetpackduba.gitnuro.git.config.LoadSignOffConfigUseCase
|
||||||
|
import com.jetpackduba.gitnuro.git.config.SaveLocalRepositoryConfigUseCase
|
||||||
|
import com.jetpackduba.gitnuro.models.SignOffConfig
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SignOffDialogViewModel @Inject constructor(
|
||||||
|
private val tabState: TabState,
|
||||||
|
private val loadSignOffConfigUseCase: LoadSignOffConfigUseCase,
|
||||||
|
private val saveLocalRepositoryConfigUseCase: SaveLocalRepositoryConfigUseCase,
|
||||||
|
) {
|
||||||
|
private val _state = MutableStateFlow<SignOffState>(SignOffState.Loading)
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
fun loadSignOffFormat() = tabState.runOperation(
|
||||||
|
showError = true,
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
val signOffConfig = loadSignOffConfigUseCase(git.repository)
|
||||||
|
|
||||||
|
_state.value = SignOffState.Loaded(signOffConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSignOffFormat(newIsEnabled: Boolean, newFormat: String) = tabState.runOperation(
|
||||||
|
showError = true,
|
||||||
|
refreshType = RefreshType.NONE,
|
||||||
|
) { git ->
|
||||||
|
saveLocalRepositoryConfigUseCase(git.repository, SignOffConfig(newIsEnabled, newFormat))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface SignOffState {
|
||||||
|
object Loading : SignOffState
|
||||||
|
data class Loaded(val signOffConfig: SignOffConfig) : SignOffState
|
||||||
|
}
|
@ -23,6 +23,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
private val authorViewModelProvider: Provider<AuthorViewModel>,
|
||||||
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
private val changeDefaultUpstreamBranchViewModelProvider: Provider<ChangeDefaultUpstreamBranchViewModel>,
|
||||||
private val submoduleDialogViewModelProvider: Provider<SubmoduleDialogViewModel>,
|
private val submoduleDialogViewModelProvider: Provider<SubmoduleDialogViewModel>,
|
||||||
|
private val signOffDialogViewModelProvider: Provider<SignOffDialogViewModel>,
|
||||||
) {
|
) {
|
||||||
val viewModels = mapOf(
|
val viewModels = mapOf(
|
||||||
logViewModel::class to logViewModel,
|
logViewModel::class to logViewModel,
|
||||||
@ -43,6 +44,7 @@ class TabViewModelsHolder @Inject constructor(
|
|||||||
AuthorViewModel::class -> authorViewModelProvider.get()
|
AuthorViewModel::class -> authorViewModelProvider.get()
|
||||||
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
ChangeDefaultUpstreamBranchViewModel::class -> changeDefaultUpstreamBranchViewModelProvider.get()
|
||||||
SubmoduleDialogViewModel::class -> submoduleDialogViewModelProvider.get()
|
SubmoduleDialogViewModel::class -> submoduleDialogViewModelProvider.get()
|
||||||
|
SignOffDialogViewModel::class -> signOffDialogViewModelProvider.get()
|
||||||
else -> throw NotImplementedError("View model provider not implemented")
|
else -> throw NotImplementedError("View model provider not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
src/main/resources/sign.svg
Normal file
1
src/main/resources/sign.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M16.81,8.94l-3.75-3.75L4,14.25V18h3.75L16.81,8.94z M6,16v-0.92l7.06-7.06l0.92,0.92L6.92,16H6z"/><path d="M19.71,6.04c0.39-0.39,0.39-1.02,0-1.41l-2.34-2.34C17.17,2.09,16.92,2,16.66,2c-0.25,0-0.51,0.1-0.7,0.29l-1.83,1.83 l3.75,3.75L19.71,6.04z"/><rect height="4" width="20" x="2" y="20"/></g></g></svg>
|
After Width: | Height: | Size: 500 B |
Loading…
Reference in New Issue
Block a user