Added option to sign off commits

Fixes #60
This commit is contained in:
Abdelilah El Aissaoui 2023-05-01 16:20:11 +02:00
parent 07e1bbd4ed
commit ddc198a0d7
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
15 changed files with 360 additions and 53 deletions

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package com.jetpackduba.gitnuro.models
data class SignOffConfig(
val isEnabled: Boolean,
val format: String,
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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