Skip to content

Commit

Permalink
Merge branch 'dev' into 'main'
Browse files Browse the repository at this point in the history
New features:
- Page image can now be viewed in full screen

Bug fixes:
- Fixed page text reset after device orientation change

Dev:
- Upgrade AGP and Compose versions
  • Loading branch information
nsh07 committed Oct 19, 2024
2 parents 5e6e8b2 + ac66434 commit f23fd87
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 95 deletions.
5 changes: 3 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ android {
applicationId = "org.nsh07.wikireader"
minSdk = 26
targetSdk = 35
versionCode = 4
versionName = "1.2.1"
versionCode = 5
versionName = "1.3.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -62,6 +62,7 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)

implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit2.retrofit)
Expand Down
113 changes: 85 additions & 28 deletions app/src/main/java/org/nsh07/wikireader/AppScreen.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.nsh07.wikireader

import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
Expand All @@ -16,14 +20,24 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import org.nsh07.wikireader.ui.AppFab
import org.nsh07.wikireader.ui.AppHomeScreen
import org.nsh07.wikireader.ui.AppSearchBar
import org.nsh07.wikireader.ui.FullScreenImage
import org.nsh07.wikireader.ui.UiViewModel

@Serializable
object HomeScreen

@Serializable
object FSImage

@Composable
fun AppScreen(
modifier: Modifier = Modifier,
Expand All @@ -33,8 +47,6 @@ fun AppScreen(
val homeScreenState by viewModel.homeScreenState.collectAsState()
val listState by viewModel.listState.collectAsState()

viewModel.setDefaultContent(LocalContext.current)

val coroutineScope = rememberCoroutineScope()

val index by remember { derivedStateOf { listState.firstVisibleItemIndex } }
Expand All @@ -47,31 +59,76 @@ fun AppScreen(
val fabEnter = scaleIn(transformOrigin = TransformOrigin(1f, 1f)) + fadeIn()
val fabExit = scaleOut(transformOrigin = TransformOrigin(1f, 1f)) + fadeOut()

Scaffold(
floatingActionButton = {
AppFab(
focusSearch = { viewModel.focusSearchBar() },
scrollToTop = { coroutineScope.launch { listState.animateScrollToItem(0) } },
index = index,
extendedFab = extendedFab,
fabEnter = fabEnter,
fabExit = fabExit
)
},
modifier = Modifier.fillMaxSize()
) { insets ->
Column(modifier = modifier.padding(top = insets.calculateTopPadding())) {
AppSearchBar(
searchBarState = searchBarState,
performSearch = { viewModel.performSearch(it) },
setExpanded = { viewModel.setExpanded(it) },
setQuery = { viewModel.setQuery(it) }
)
AppHomeScreen(
homeScreenState = homeScreenState,
listState = listState,
modifier = Modifier
.fillMaxSize()
val navController = rememberNavController()

NavHost(
navController = navController,
startDestination = HomeScreen,
modifier = Modifier.background(androidx.compose.ui.graphics.Color.Black)
) {
composable<HomeScreen>(
enterTransition = {
slideInHorizontally(
initialOffsetX = { -it/8 },
animationSpec = tween(300)
) + fadeIn(tween(100))
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { -it/8 },
animationSpec = tween(300)
) + fadeOut(tween(100))
}
) {
Scaffold(
floatingActionButton = {
AppFab(
focusSearch = { viewModel.focusSearchBar() },
scrollToTop = { coroutineScope.launch { listState.animateScrollToItem(0) } },
index = index,
extendedFab = extendedFab,
fabEnter = fabEnter,
fabExit = fabExit
)
},
modifier = Modifier.fillMaxSize()
) { insets ->
Column(modifier = modifier.padding(top = insets.calculateTopPadding())) {
AppSearchBar(
searchBarState = searchBarState,
performSearch = { viewModel.performSearch(it) },
setExpanded = { viewModel.setExpanded(it) },
setQuery = { viewModel.setQuery(it) }
)
AppHomeScreen(
homeScreenState = homeScreenState,
listState = listState,
onImageClick = { navController.navigate(FSImage) },
modifier = Modifier
.fillMaxSize()
)
}
}
}

composable<FSImage>(
enterTransition = {
slideInHorizontally(
initialOffsetX = { it/8 },
animationSpec = tween(300)
) + fadeIn(tween(100))
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { it/8 },
animationSpec = tween(300)
) + fadeOut(tween(100))
}
) {
FullScreenImage(
photo = homeScreenState.photo!!,
photoDesc = homeScreenState.photoDesc!!,
onBack = { navController.navigateUp() }
)
}
}
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/org/nsh07/wikireader/ui/AppFab.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.outlined.KeyboardArrowUp
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallFloatingActionButton
Expand Down Expand Up @@ -39,7 +39,7 @@ fun AppFab(
onClick = focusSearch
) {
Icon(
Icons.Rounded.Search,
Icons.Outlined.Search,
contentDescription = stringResource(R.string.search)
)
}
Expand All @@ -56,7 +56,7 @@ fun AppFab(
onClick = scrollToTop,
icon = {
Icon(
Icons.Rounded.KeyboardArrowUp,
Icons.Outlined.KeyboardArrowUp,
contentDescription = stringResource(R.string.up_arrow)
)
},
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/nsh07/wikireader/ui/AppHomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
fun AppHomeScreen(
homeScreenState: HomeScreenState,
listState: LazyListState,
onImageClick: () -> Unit,
modifier: Modifier = Modifier
) {
val photo = homeScreenState.photo
Expand Down Expand Up @@ -59,7 +60,8 @@ fun AppHomeScreen(
if (photoDesc != null) {
WikiImageCard(
photo = photo,
photoDesc = photoDesc
photoDesc = photoDesc,
onClick = onImageClick
)
}
}
Expand Down
118 changes: 118 additions & 0 deletions app/src/main/java/org/nsh07/wikireader/ui/FullScreenImage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.nsh07.wikireader.ui

import androidx.compose.foundation.gestures.animateZoomBy
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntSize
import kotlinx.coroutines.launch
import org.nsh07.wikireader.R
import org.nsh07.wikireader.data.WikiPhoto
import org.nsh07.wikireader.data.WikiPhotoDesc

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FullScreenImage(
photo: WikiPhoto,
photoDesc: WikiPhotoDesc,
onBack: () -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(photoDesc.label[0]) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color(0f, 0f, 0f, 0.5f),
titleContentColor = Color.White,
navigationIconContentColor = Color.White
)
)
},
containerColor = Color.Black,
modifier = modifier
) { _ ->
var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
var size by remember { mutableStateOf(IntSize.Zero) }

// Zoom/Offset logic. Also prevents image from going out of bounds
val state = rememberTransformableState { scaleChange, offsetChange, _ ->
val maxX = (size.width * (scale - 1) / 2f)
val maxY = (size.height * (scale - 1) / 2f)

scale = (scale * scaleChange).coerceIn(1f..8f)

offset += offsetChange.times(scale)
offset = Offset(
offset.x.coerceIn(-maxX, maxX),
offset.y.coerceIn(-maxY, maxY)
)
}

val coroutineScope = rememberCoroutineScope()

Box(modifier = Modifier.fillMaxSize()) {
PageImage(
photo = photo,
photoDesc = photoDesc,
contentScale = ContentScale.Inside,
modifier = Modifier
.onSizeChanged { size = it }
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
)
.transformable(state = state)
.align(Alignment.Center)
.pointerInput(Unit) {
detectTapGestures(onDoubleTap = {
coroutineScope.launch {
if (scale == 1f) // Zoom in only if the image is zoomed out
state.animateZoomBy(4f)
else
state.animateZoomBy(0.25f)
}
})
}
)
}
}
}
Loading

0 comments on commit f23fd87

Please sign in to comment.