mirror of https://github.com/Ashinch/ReadYou.git
Compare commits
5 Commits
6d3918db14
...
1f133ce443
Author | SHA1 | Date |
---|---|---|
Ash | 1f133ce443 | |
junkfood | 571840a2fa | |
junkfood | 1bf597d32e | |
Moderpach | 1199c6850b | |
Ash | c45b867607 |
|
@ -179,7 +179,6 @@ dependencies {
|
|||
implementation "androidx.compose.material3:material3:$material3"
|
||||
|
||||
// https://github.com/google/accompanist/releases
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist"
|
||||
implementation "com.google.accompanist:accompanist-pager:$accompanist"
|
||||
implementation "com.google.accompanist:accompanist-flowlayout:$accompanist"
|
||||
implementation "com.google.accompanist:accompanist-navigation-animation:$accompanist"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
|
||||
package me.ash.reader.infrastructure.android
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -41,8 +43,6 @@ import androidx.compose.ui.text.font.FontFamily
|
|||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.infrastructure.preference.LocalDarkTheme
|
||||
import me.ash.reader.infrastructure.preference.LocalOpenLink
|
||||
|
@ -55,11 +55,9 @@ import me.ash.reader.ui.theme.AppTheme
|
|||
class CrashReportActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
|
||||
v.setPadding(0, 0, 0, 0)
|
||||
insets
|
||||
}
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
val errorMessage: String = intent.getStringExtra(ERROR_REPORT_KEY).toString()
|
||||
|
||||
setContent {
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.util.Log
|
|||
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
|
@ -45,12 +46,10 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_NO_LIMITS)
|
||||
}
|
||||
Log.i("RLog", "onCreate: ${ProfileInstallerInitializer().create(this)}")
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Set the language
|
||||
if (Build.VERSION.SDK_INT < 33) {
|
||||
LanguagesPreference.fromValue(languages).let {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package me.ash.reader.ui.component.menu
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
|
@ -26,7 +27,6 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.ui.AbsoluteAlignment
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
|
@ -38,12 +38,14 @@ import androidx.compose.ui.util.fastFirstOrNull
|
|||
import androidx.compose.ui.util.fastMap
|
||||
import androidx.compose.ui.window.PopupPositionProvider
|
||||
import me.ash.reader.domain.model.constant.ElevationTokens
|
||||
import me.ash.reader.ui.component.menu.MenuPosition.Horizontal
|
||||
import me.ash.reader.ui.component.menu.MenuPosition.Vertical
|
||||
import me.ash.reader.ui.motion.EmphasizedAccelerate
|
||||
import me.ash.reader.ui.motion.EmphasizedDecelerate
|
||||
import me.ash.reader.ui.motion.EnterDuration
|
||||
import me.ash.reader.ui.motion.ExitDuration
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
private const val TAG = "DropdownMenuImpl"
|
||||
|
||||
/**
|
||||
* Interfaces for positioning a menu within a window. This is the same purpose as the interface
|
||||
|
@ -385,25 +387,32 @@ internal data class DropdownMenuPositionProvider(
|
|||
it >= 0 && it + popupContentSize.width <= windowSize.width
|
||||
} ?: xCandidates.last()
|
||||
|
||||
val yCandidates = listOf(
|
||||
topToAnchorBottom,
|
||||
bottomToAnchorTop,
|
||||
centerToAnchorTop,
|
||||
if (anchorBounds.center.y < windowSize.height / 2) {
|
||||
topToWindowTop
|
||||
} else {
|
||||
bottomToWindowBottom
|
||||
}
|
||||
).fastMap {
|
||||
it.position(
|
||||
/* val yCandidates = listOf(
|
||||
topToAnchorBottom,
|
||||
bottomToAnchorTop,
|
||||
centerToAnchorTop,
|
||||
if (anchorBounds.center.y < windowSize.height / 2) {
|
||||
topToWindowTop
|
||||
} else {
|
||||
bottomToWindowBottom
|
||||
}
|
||||
).fastMap {
|
||||
it.position(
|
||||
anchorBounds = anchorBounds,
|
||||
windowSize = windowSize,
|
||||
menuHeight = popupContentSize.height
|
||||
)
|
||||
}
|
||||
val y = yCandidates.fastFirstOrNull {
|
||||
it >= verticalMargin && it + popupContentSize.height <= windowSize.height - verticalMargin
|
||||
} ?: yCandidates.last()*/
|
||||
|
||||
val y =
|
||||
(if (anchorBounds.top < windowSize.height / 2) topToAnchorBottom else bottomToAnchorTop).position(
|
||||
anchorBounds = anchorBounds,
|
||||
windowSize = windowSize,
|
||||
menuHeight = popupContentSize.height
|
||||
)
|
||||
}
|
||||
val y = yCandidates.fastFirstOrNull {
|
||||
it >= verticalMargin && it + popupContentSize.height <= windowSize.height - verticalMargin
|
||||
} ?: yCandidates.last()
|
||||
|
||||
val menuOffset = IntOffset(x, y)
|
||||
onPositionCalculated(/* anchorBounds = */anchorBounds,/* menuBounds = */
|
||||
|
|
|
@ -12,12 +12,10 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import me.ash.reader.domain.model.general.Filter
|
||||
|
@ -144,12 +142,6 @@ fun HomeEntry(
|
|||
else LocalDarkTheme.current.isDarkTheme()
|
||||
) {
|
||||
|
||||
rememberSystemUiController().run {
|
||||
setStatusBarColor(Color.Transparent, !useDarkTheme)
|
||||
setSystemBarsColor(Color.Transparent, !useDarkTheme)
|
||||
setNavigationBarColor(Color.Transparent, !useDarkTheme)
|
||||
}
|
||||
|
||||
AnimatedNavHost(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
||||
navController = navController,
|
||||
|
|
|
@ -90,7 +90,7 @@ fun FeedsPage(
|
|||
) {
|
||||
var accountTabVisible by remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
// val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val topBarTonalElevation = LocalFeedsTopBarTonalElevation.current
|
||||
val groupListTonalElevation = LocalFeedsGroupListTonalElevation.current
|
||||
|
|
|
@ -377,9 +377,7 @@ fun SwipeableArticleItem(
|
|||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
offset = density.run {
|
||||
if (LocalLayoutDirection.current == LayoutDirection.Ltr)
|
||||
DpOffset(menuOffset.x.toDp(), 0.dp)
|
||||
else DpOffset(0.dp, 0.dp)
|
||||
DpOffset(menuOffset.x.toDp(), 0.dp)
|
||||
},
|
||||
) {
|
||||
ArticleItemMenuContent(
|
||||
|
|
|
@ -2,8 +2,10 @@ package me.ash.reader.ui.page.home.reading
|
|||
|
||||
|
||||
import androidx.compose.animation.core.FloatExponentialDecaySpec
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animate
|
||||
import androidx.compose.animation.core.animateDecay
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.MutatorMutex
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
|
@ -33,7 +35,7 @@ import me.ash.reader.ui.page.home.reading.PullToLoadDefaults.ContentOffsetMultip
|
|||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private const val TAG = "PullRelease"
|
||||
private const val TAG = "PullToLoad"
|
||||
|
||||
/**
|
||||
* A [NestedScrollConnection] that provides scroll events to a hoisted [state].
|
||||
|
@ -51,7 +53,7 @@ private class ReaderNestedScrollConnection(
|
|||
private val enabled: Boolean,
|
||||
private val onPreScroll: (Float) -> Float,
|
||||
private val onPostScroll: (Float) -> Float,
|
||||
private val onRelease: (Float) -> Unit,
|
||||
private val onRelease: () -> Unit,
|
||||
private val onScroll: ((Float) -> Unit)? = null
|
||||
) : NestedScrollConnection {
|
||||
|
||||
|
@ -81,7 +83,7 @@ private class ReaderNestedScrollConnection(
|
|||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
onRelease(available.y)
|
||||
onRelease()
|
||||
return Velocity.Zero
|
||||
}
|
||||
}
|
||||
|
@ -217,21 +219,22 @@ class PullToLoadState internal constructor(
|
|||
return if (offsetPulled.signOpposites(pullDelta)) onPull(pullDelta) else 0f
|
||||
}
|
||||
|
||||
internal fun onRelease(velocity: Float): Float {
|
||||
internal fun onRelease(): Float {
|
||||
// Snap to 0f and hide the indicator
|
||||
animateDistanceTo(0f)
|
||||
|
||||
when (status) {
|
||||
// We don't change the pull offset here because the animation for loading another content
|
||||
// should be handled outside, and this state will be soon disposed
|
||||
Status.PulledDown -> {
|
||||
onLoadPrevious.value()
|
||||
}
|
||||
|
||||
Status.PulledUp -> {
|
||||
animateDistanceTo(0f)
|
||||
onLoadNext.value()
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Snap to 0f and hide the indicator
|
||||
animateDistanceTo(0f)
|
||||
|
||||
}
|
||||
}
|
||||
return 0f
|
||||
|
@ -247,7 +250,8 @@ class PullToLoadState internal constructor(
|
|||
animate(
|
||||
initialValue = offsetPulled,
|
||||
targetValue = float,
|
||||
initialVelocity = velocity
|
||||
initialVelocity = velocity,
|
||||
animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
|
||||
) { value, _ ->
|
||||
offsetPulled = value
|
||||
}
|
||||
|
@ -310,7 +314,7 @@ fun Modifier.pullToLoad(
|
|||
state: PullToLoadState,
|
||||
contentOffsetMultiple: Int = ContentOffsetMultiple,
|
||||
onScroll: ((Float) -> Unit)? = null,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
): Modifier =
|
||||
nestedScroll(
|
||||
ReaderNestedScrollConnection(
|
||||
|
|
|
@ -20,70 +20,117 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.Idle
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PulledDown
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PulledUp
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PullingDown
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PullingUp
|
||||
import kotlin.math.abs
|
||||
|
||||
@Composable
|
||||
fun BoxScope.PullToLoadIndicator(state:PullToLoadState) {
|
||||
state.status.run {
|
||||
val fraction = state.offsetFraction
|
||||
val absFraction = abs(fraction)
|
||||
val imageVector = when (this) {
|
||||
PullToLoadState.Status.PulledDown -> Icons.Outlined.KeyboardArrowUp
|
||||
PullToLoadState.Status.PulledUp -> Icons.Outlined.KeyboardArrowDown
|
||||
else -> null
|
||||
}
|
||||
fun BoxScope.PullToLoadIndicator(
|
||||
state: PullToLoadState,
|
||||
canLoadPrevious: Boolean = true,
|
||||
canLoadNext: Boolean = true
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val status = state.status
|
||||
|
||||
val alignment = if (fraction < 0f) {
|
||||
Alignment.BottomCenter
|
||||
} else {
|
||||
Alignment.TopCenter
|
||||
}
|
||||
if (this != PullToLoadState.Status.Idle) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(alignment)
|
||||
.padding(vertical = 80.dp)
|
||||
.offset(y = (fraction * 48).dp)
|
||||
.width(36.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = MaterialTheme.shapes.extraLarge
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = imageVector, modifier = Modifier.align(
|
||||
Alignment.CenterHorizontally
|
||||
), transitionSpec = {
|
||||
(fadeIn(animationSpec = tween(220, delayMillis = 0)))
|
||||
.togetherWith(fadeOut(animationSpec = tween(90)))
|
||||
}, label = ""
|
||||
) {
|
||||
if (it != null) {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(vertical = (2 * absFraction).dp)
|
||||
.size(32.dp)
|
||||
)
|
||||
} else {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(36.dp)
|
||||
.height((12 * absFraction).dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(status) {
|
||||
when {
|
||||
canLoadPrevious && status == PulledDown -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
}
|
||||
canLoadNext && status == PulledUp -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
val fraction = state.offsetFraction
|
||||
val absFraction = abs(fraction)
|
||||
|
||||
val imageVector = when (status) {
|
||||
PulledDown -> Icons.Outlined.KeyboardArrowUp
|
||||
PulledUp -> Icons.Outlined.KeyboardArrowDown
|
||||
else -> null
|
||||
}
|
||||
|
||||
val alignment = if (fraction < 0f) {
|
||||
Alignment.BottomCenter
|
||||
} else {
|
||||
Alignment.TopCenter
|
||||
}
|
||||
|
||||
val visible = remember(status, canLoadPrevious, canLoadNext) {
|
||||
when (status) {
|
||||
Idle -> {
|
||||
false
|
||||
}
|
||||
|
||||
PullingUp, PulledUp -> {
|
||||
canLoadNext
|
||||
}
|
||||
|
||||
PulledDown, PullingDown -> {
|
||||
canLoadPrevious
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(alignment)
|
||||
.padding(vertical = 80.dp)
|
||||
.offset(y = (fraction * 48).dp)
|
||||
.width(36.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = MaterialTheme.shapes.extraLarge
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = imageVector, modifier = Modifier.align(
|
||||
Alignment.CenterHorizontally
|
||||
), transitionSpec = {
|
||||
(fadeIn(animationSpec = tween(220, delayMillis = 0)))
|
||||
.togetherWith(fadeOut(animationSpec = tween(90)))
|
||||
}, label = ""
|
||||
) {
|
||||
if (it != null) {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(vertical = (2 * absFraction).dp)
|
||||
.size(32.dp)
|
||||
)
|
||||
} else {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(36.dp)
|
||||
.height((12 * absFraction).dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
|
@ -115,10 +114,10 @@ fun ReadingPage(
|
|||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
val context = LocalContext.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val isNextArticleAvailable = !readerState.nextArticleId.isNullOrEmpty()
|
||||
val isPreviousArticleAvailable = !readerState.previousArticleId.isNullOrEmpty()
|
||||
|
||||
|
||||
if (readerState.articleId != null) {
|
||||
// Content
|
||||
|
@ -159,16 +158,6 @@ fun ReadingPage(
|
|||
)
|
||||
|
||||
|
||||
LaunchedEffect(state.status) {
|
||||
when (state.status) {
|
||||
PullToLoadState.Status.PulledDown, PullToLoadState.Status.PulledUp -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
val listState = rememberSaveable(
|
||||
inputs = arrayOf(content),
|
||||
saver = LazyListState.Saver
|
||||
|
@ -210,7 +199,11 @@ fun ReadingPage(
|
|||
showFullScreenImageViewer = true
|
||||
}
|
||||
)
|
||||
PullToLoadIndicator(state = state)
|
||||
PullToLoadIndicator(
|
||||
state = state,
|
||||
canLoadPrevious = isPreviousArticleAvailable,
|
||||
canLoadNext = isNextArticleAvailable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +245,8 @@ fun ReadingPage(
|
|||
onSuccess = { context.showToast(context.getString(R.string.image_saved)) },
|
||||
onFailure = {
|
||||
// FIXME: crash the app for error report
|
||||
th -> throw th
|
||||
th ->
|
||||
throw th
|
||||
}
|
||||
)
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue