Gitnuro/src/main/kotlin/app/ui/CommitChanges.kt
Abdelilah El Aissaoui b518c5007e Added avatar caching
2021-12-02 01:50:59 +01:00

259 lines
7.9 KiB
Kotlin

package app.ui
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.useResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.extensions.*
import kotlinx.coroutines.*
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.revwalk.RevCommit
import app.theme.headerBackground
import app.theme.primaryTextColor
import app.theme.secondaryTextColor
import java.net.HttpURLConnection
import java.net.URL
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.toComposeImageBitmap
import app.ui.components.ScrollableLazyColumn
import app.git.GitManager
import app.images.ImagesCache
import app.images.InMemoryImagesCache
import app.theme.headerText
import org.eclipse.jgit.lib.PersonIdent
import org.jetbrains.skia.Image.Companion.makeFromEncoded
@Composable
fun CommitChanges(
gitManager: GitManager,
commit: RevCommit,
onDiffSelected: (DiffEntry) -> Unit
) {
var diff by remember { mutableStateOf(emptyList<DiffEntry>()) }
LaunchedEffect(commit) {
diff = gitManager.diffListFromCommit(commit)
}
Column(
modifier = Modifier
.fillMaxSize(),
) {
val scroll = rememberScrollState(0)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
) {
SelectionContainer {
Text(
text = commit.fullMessage,
fontSize = 14.sp,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.background)
.height(120.dp)
.padding(8.dp)
.verticalScroll(scroll),
)
}
Divider(modifier = Modifier.fillMaxWidth())
Author(commit.authorIdent)
}
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f, fill = true)
.padding(horizontal = 8.dp, vertical = 8.dp)
.background(MaterialTheme.colors.background)
) {
Text(
modifier = Modifier
.background(MaterialTheme.colors.headerBackground)
.padding(vertical = 8.dp)
.fillMaxWidth(),
text = "Files changed",
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
color = MaterialTheme.colors.headerText,
maxLines = 1,
fontSize = 14.sp,
)
CommitLogChanges(diff, onDiffSelected = onDiffSelected)
}
}
}
@Composable
fun Author(authorIdent: PersonIdent?) {
if(authorIdent == null)
return
Row(
modifier = Modifier
.fillMaxWidth()
.height(72.dp)
.background(MaterialTheme.colors.background),
verticalAlignment = Alignment.CenterVertically
) {
val url = "https://www.gravatar.com/avatar/${authorIdent.emailAddress.md5}"
Image(
bitmap = rememberNetworkImage(url),
modifier = Modifier
.padding(horizontal = 16.dp)
.height(40.dp)
.clip(CircleShape),
contentDescription = null,
)
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Row {
Text(
text = authorIdent.name,
color = MaterialTheme.colors.primaryTextColor,
maxLines = 1,
fontSize = 14.sp,
)
Spacer(modifier = Modifier.weight(1f, fill = true))
val date = remember(authorIdent) {
authorIdent.`when`.toSmartSystemString()
}
Text(
text = date,
color = MaterialTheme.colors.secondaryTextColor,
maxLines = 1,
modifier = Modifier.padding(horizontal = 16.dp),
fontSize = 12.sp,
)
}
Text(
text = authorIdent.emailAddress,
color = MaterialTheme.colors.secondaryTextColor,
maxLines = 1,
fontSize = 12.sp,
)
}
}
}
suspend fun loadImage(link: String): ByteArray = withContext(Dispatchers.IO) {
val url = URL(link)
val connection = url.openConnection() as HttpURLConnection
connection.connect()
connection.inputStream.toByteArray()
}
@Composable
fun rememberNetworkImage(url: String, cache: ImagesCache = InMemoryImagesCache): ImageBitmap {
val cachedImage = cache.getCachedObject(url)
var image by remember(url) {
if(cachedImage != null)
mutableStateOf(makeFromEncoded(cachedImage).toComposeImageBitmap())
else
mutableStateOf(
useResource("image.jpg") {
makeFromEncoded(it.toByteArray()).toComposeImageBitmap()
}
)
}
if(cachedImage == null) {
LaunchedEffect(url) {
try {
loadImage(url).let {
image = makeFromEncoded(it).toComposeImageBitmap()
cache.cacheImage(url, it)
}
} catch (ex: Exception) {
println("Avatar loading failed: ${ex.message}")
}
}
}
return image
}
@Composable
fun CommitLogChanges(diffEntries: List<DiffEntry>, onDiffSelected: (DiffEntry) -> Unit) {
val selectedIndex = remember(diffEntries) { mutableStateOf(-1) }
ScrollableLazyColumn(
modifier = Modifier
.fillMaxSize()
) {
itemsIndexed(items = diffEntries) { index, diffEntry ->
val textColor = if (selectedIndex.value == index) {
MaterialTheme.colors.primary
} else
MaterialTheme.colors.primaryTextColor
Column(
modifier = Modifier
.height(48.dp)
.fillMaxWidth()
.clickable {
selectedIndex.value = index
onDiffSelected(diffEntry)
},
verticalArrangement = Arrangement.Center,
) {
Spacer(modifier = Modifier.weight(2f))
Row {
Icon(
modifier = Modifier
.padding(start = 8.dp)
.size(16.dp),
imageVector = diffEntry.icon,
contentDescription = null,
tint = diffEntry.iconColor,
)
Text(
text = diffEntry.filePath,
modifier = Modifier.padding(horizontal = 8.dp),
color = textColor,
maxLines = 1,
fontSize = 14.sp,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.weight(2f))
Divider()
}
}
}
}