Skip to content

Commit

Permalink
Add ability to view PDF files
Browse files Browse the repository at this point in the history
  • Loading branch information
wingio committed Nov 4, 2023
1 parent bdcffc9 commit c77835c
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 61 deletions.
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ apollo-normalized-cache = { group = "com.apollographql.apollo3", name = "apollo-
apollo-runtime = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo" }
coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
compose-imageloader = { group = "io.github.qdsfdhvh", name = "image-loader", version = "1.6.4" }
compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.2.0-alpha07" }
compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose" }
compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "compose" }
compose-pdf = { group = "dev.zt64", name = "compose-pdf", version = "1.0.0" }
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
compose-imageloader = { group = "io.github.qdsfdhvh", name = "image-loader", version = "1.6.4" }
highlights = { group = "dev.snipme", name = "highlights", version = "0.7.1" }
koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version = "3.4.0" }
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
Expand Down
1 change: 1 addition & 0 deletions ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ kotlin {

api(libs.androidx.paging.compose)
api(libs.compose.imageloader)
api(libs.compose.pdf)
api(libs.highlights)
api(libs.koin.core)
api(libs.koin.androidx.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.materiiapps.gloom.ui.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.materiiapps.gloom.Res
import dev.icerock.moko.resources.compose.stringResource

@Composable
fun ErrorMessage(
message: String,
modifier: Modifier = Modifier,
onRetryClick: (() -> Unit)? = null
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.padding(16.dp)
) {
Icon(
imageVector = Icons.Filled.Cancel,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(50.dp)
)

Text(
message,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)

Spacer(modifier = Modifier.height(16.dp))

onRetryClick?.let {
FilledTonalButton(
onClick = onRetryClick,
) {
Text(stringResource(Res.strings.action_try_again))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,38 @@
package com.materiiapps.gloom.ui.screens.explorer

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import com.materiiapps.gloom.Res
import com.materiiapps.gloom.domain.manager.AuthManager
import com.materiiapps.gloom.domain.manager.ShareManager
import com.materiiapps.gloom.gql.fragment.RepoFile
import com.materiiapps.gloom.ui.components.ErrorMessage
import com.materiiapps.gloom.ui.components.RefreshIndicator
import com.materiiapps.gloom.ui.components.toolbar.SmallToolbar
import com.materiiapps.gloom.ui.screens.explorer.viewers.ImageFileViewer
import com.materiiapps.gloom.ui.screens.explorer.viewers.MarkdownFileViewer
import com.materiiapps.gloom.ui.screens.explorer.viewers.PdfFileViewer
import com.materiiapps.gloom.ui.screens.explorer.viewers.TextFileViewer
import com.materiiapps.gloom.ui.viewmodels.explorer.FileViewerViewModel
import dev.icerock.moko.resources.compose.stringResource
Expand All @@ -60,9 +49,11 @@ class FileViewerScreen(
@Composable
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
override fun Content() {
val viewModel: FileViewerViewModel = getScreenModel { parametersOf(FileViewerViewModel.Input(owner, name, branch, path)) }
val viewModel: FileViewerViewModel =
getScreenModel { parametersOf(FileViewerViewModel.Input(owner, name, branch, path)) }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val pullRefreshState = rememberPullRefreshState(viewModel.isLoading, onRefresh = { viewModel.getRepoFile() })
val pullRefreshState =
rememberPullRefreshState(viewModel.isLoading, onRefresh = { viewModel.getRepoFile() })
val file = viewModel.file?.gitObject?.onCommit?.file

Scaffold(
Expand All @@ -79,7 +70,10 @@ class FileViewerScreen(
when (viewModel.hasError) {
true -> ErrorMessage(
message = stringResource(Res.strings.msg_file_load_error),
onRetryClick = viewModel::getRepoFile
onRetryClick = viewModel::getRepoFile,
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
)

false -> FileContent(file)
Expand All @@ -95,49 +89,12 @@ class FileViewerScreen(
when (file?.fileType?.__typename) {
"MarkdownFileType" -> MarkdownFileViewer(file.fileType?.onMarkdownFileType!!)
"ImageFileType" -> ImageFileViewer(file.fileType?.onImageFileType!!)
"PdfFileType" -> Box(Modifier.fillMaxSize())
"PdfFileType" -> PdfFileViewer(file.fileType?.onPdfFileType!!)
"TextFileType" -> TextFileViewer(file.fileType?.onTextFileType!!, file.extension ?: "")
else -> {}
}
}

@Composable
private fun BoxScope.ErrorMessage(
message: String,
onRetryClick: () -> Unit
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
.padding(16.dp)
) {
Icon(
imageVector = Icons.Filled.Cancel,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(50.dp)
)

Text(
message,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)

Spacer(modifier = Modifier.height(16.dp))

FilledTonalButton(
onClick = onRetryClick,
) {
Text(stringResource(Res.strings.action_try_again))
}
}
}

@Composable
private fun RowScope.FileActions(file: RepoFile.File?) {
val shareManager: ShareManager = get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.layout.ContentScale
import com.materiiapps.gloom.gql.fragment.RepoFile
import com.seiko.imageloader.rememberImagePainter
Expand All @@ -23,17 +21,14 @@ fun ImageFileViewer(
imageFile.url?.let { imageUrl ->
val zoomState = rememberZoomState()
val currentBg = MaterialTheme.colorScheme.background
val background = remember(currentBg) {
if (currentBg.luminance() > 0.5f) Color.White else Color.Black
}

Image(
painter = rememberImagePainter(imageUrl),
contentDescription = null,
contentScale = ContentScale.Fit,
alignment = Alignment.Center,
modifier = Modifier
.background(background)
.background(Color.Black)
.fillMaxSize()
.zoomable(zoomState)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.materiiapps.gloom.ui.screens.explorer.viewers

import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Error
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.dp
import com.materiiapps.gloom.Res
import com.materiiapps.gloom.gql.fragment.RepoFile
import com.materiiapps.gloom.ui.components.ErrorMessage
import dev.icerock.moko.resources.compose.stringResource
import dev.zt64.compose.pdf.LoadState
import dev.zt64.compose.pdf.RemotePdfState
import dev.zt64.compose.pdf.component.PdfPage
import dev.zt64.compose.pdf.component.PdfVerticalPager
import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
import java.net.URL

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PdfFileViewer(
pdfFile: RepoFile.OnPdfFileType
) {
val errIcon = rememberVectorPainter(Icons.Outlined.Error)
val loadIcon = rememberVectorPainter(Icons.Outlined.Refresh)
pdfFile.url?.let { url ->
val pdfState = remember { RemotePdfState(URL(url), errIcon, loadIcon) }
val pagerState = rememberPagerState(initialPage = 1) {
pdfState.pageCount
}

Box(
modifier = Modifier
.background(Color.Black)
.fillMaxSize()
) {
PdfVerticalPager(
state = pdfState,
pagerState = pagerState,
page = { page ->
val zoomState = rememberZoomState()
Box(
modifier = Modifier.fillMaxSize()
) {
PdfPage(
state = pdfState,
index = page,
errorIndicator = {
ErrorMessage(
message = stringResource(Res.strings.msg_file_load_error),
onRetryClick = { pdfState.loadPdf() },
modifier = Modifier.align(Alignment.Center)
)
},
loadingIndicator = {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
},
modifier = Modifier
.align(Alignment.Center)
.zoomable(zoomState)
)
}
},
modifier = Modifier.fillMaxSize()
)

if (pdfState.loadState == LoadState.Success) {
Text(
"${pagerState.currentPage + 1} / ${pdfState.pageCount}",
style = MaterialTheme.typography.labelLarge,
color = Color.White,
modifier = Modifier
.align(Alignment.BottomEnd)
.systemBarsPadding()
.padding(16.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.5f))
.padding(horizontal = 10.dp, vertical = 6.dp)
.animateContentSize()
)
}
}
}
}

0 comments on commit c77835c

Please sign in to comment.