Added support for GIFs & animated WebP

Fixes #2
This commit is contained in:
Abdelilah El Aissaoui 2022-10-02 23:27:52 +02:00
parent 115a195a61
commit 527d78229e
3 changed files with 62 additions and 21 deletions

View file

@ -28,6 +28,7 @@ dependencies {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.desktop.components.splitPane)
implementation(compose("org.jetbrains.compose.ui:ui-util"))
implementation(compose("org.jetbrains.compose.components:components-animatedimage"))
implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r")
implementation("org.apache.sshd:sshd-core:2.9.0")
implementation("com.google.dagger:dagger:2.43.2")

View file

@ -12,23 +12,23 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator
import org.eclipse.jgit.treewalk.WorkingTreeIterator
import org.eclipse.jgit.util.LfsFactory
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.createTempFile
private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD
private const val IMAGE_CONTENT_TYPE = "image/"
val animatedImages = arrayOf(
"image/gif",
"image/webp"
)
class RawFileManager @Inject constructor(
private val tempFilesManager: TempFilesManager,
) {
private val imageFormatsSupported = listOf(
"png",
"jpg",
"jpeg",
"webp",
)
private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource {
return if (iterator is WorkingTreeIterator)
ContentSource.create(iterator)
@ -86,15 +86,14 @@ class RawFileManager @Inject constructor(
ldr.copyTo(out)
}
return EntryContent.ImageBinary(tempFile)
return EntryContent.ImageBinary(tempFile, Files.probeContentType(Path.of(entry.newPath)).orEmpty())
}
// todo check if it's an image checking the binary format, checking the extension is a temporary workaround
private fun isImage(entry: DiffEntry): Boolean {
val path = entry.newPath
val fileExtension = path.split(".").lastOrNull() ?: return false
val contentType = Files.probeContentType(Path.of(path))
return imageFormatsSupported.contains(fileExtension.lowercase())
return contentType?.startsWith(IMAGE_CONTENT_TYPE) ?: false
}
}
@ -103,7 +102,7 @@ sealed class EntryContent {
object InvalidObjectBlob : EntryContent()
data class Text(val rawText: RawText) : EntryContent()
sealed class BinaryContent : EntryContent()
data class ImageBinary(val tempFilePath: Path) : BinaryContent()
data class ImageBinary(val tempFilePath: Path, val contentType: String) : BinaryContent()
object Binary : BinaryContent()
object TooLargeEntry : EntryContent()
}

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerIconDefaults
@ -30,6 +31,10 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.git.animatedImages
import com.jetpackduba.gitnuro.git.diff.DiffResult
import com.jetpackduba.gitnuro.git.diff.Hunk
import com.jetpackduba.gitnuro.git.diff.Line
@ -38,16 +43,19 @@ import com.jetpackduba.gitnuro.git.workspace.StatusEntry
import com.jetpackduba.gitnuro.git.workspace.StatusType
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
import com.jetpackduba.gitnuro.viewmodels.DiffViewModel
import com.jetpackduba.gitnuro.viewmodels.TextDiffType
import com.jetpackduba.gitnuro.viewmodels.ViewDiffResult
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.theme.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.diff.DiffEntry
import org.jetbrains.compose.animatedimage.Blank
import org.jetbrains.compose.animatedimage.animate
import org.jetbrains.compose.animatedimage.loadAnimatedImage
import org.jetbrains.compose.resources.loadOrNull
import java.io.FileInputStream
import java.nio.file.Path
import kotlin.io.path.absolutePathString
@ -206,7 +214,7 @@ fun NonTextDiff(diffResult: DiffResult.NonText) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
SideTitle("Binary file")
// SideTitle("Binary file")
Spacer(modifier = Modifier.height(24.dp))
SideDiff(newBinaryContent)
}
@ -227,7 +235,7 @@ fun SideTitle(text: String) {
fun SideDiff(entryContent: EntryContent) {
when (entryContent) {
EntryContent.Binary -> BinaryDiff()
is EntryContent.ImageBinary -> ImageDiff(entryContent.tempFilePath)
is EntryContent.ImageBinary -> ImageDiff(entryContent.tempFilePath, entryContent.contentType)
else -> {
}
// is EntryContent.Text -> //TODO maybe have a text view if the file was a binary before?
@ -236,13 +244,46 @@ fun SideDiff(entryContent: EntryContent) {
}
@Composable
fun ImageDiff(tempImagePath: Path) {
private fun ImageDiff(tempImagePath: Path, contentType: String) {
val imagePath = tempImagePath.absolutePathString()
if(animatedImages.contains(contentType)) {
AnimatedImage(imagePath)
} else {
StaticImage(imagePath)
}
}
@Composable
private fun StaticImage(tempImagePath: String) {
var image by remember(tempImagePath) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(tempImagePath) {
withContext(Dispatchers.IO) {
FileInputStream(tempImagePath).use { inputStream ->
image = loadImageBitmap(inputStream = inputStream)
}
}
}
Image(
bitmap = loadImageBitmap(inputStream = FileInputStream(tempImagePath.absolutePathString())),
bitmap = image ?: ImageBitmap.Blank,
contentDescription = null,
modifier = Modifier.fillMaxSize()
.handMouseClickable {
openFileWithExternalApp(tempImagePath.absolutePathString())
openFileWithExternalApp(tempImagePath)
}
)
}
@Composable
private fun AnimatedImage(tempImagePath: String) {
Image(
bitmap = loadOrNull(tempImagePath) { loadAnimatedImage(tempImagePath) }?.animate() ?: ImageBitmap.Blank,
contentDescription = null,
modifier = Modifier.fillMaxSize()
.handMouseClickable {
openFileWithExternalApp(tempImagePath)
}
)
}