mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
feat: 迁移至material3支持深色模式 (#9)
This commit is contained in:
parent
9244742169
commit
265c9f2cc0
|
@ -122,7 +122,8 @@ dependencies {
|
|||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.material)
|
||||
// implementation(libs.compose.material)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.preview)
|
||||
debugImplementation(libs.compose.tooling)
|
||||
androidTestImplementation(libs.compose.junit4)
|
||||
|
|
|
@ -46,11 +46,6 @@
|
|||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<activity
|
||||
android:name="li.songe.gkd.MainActivity"
|
||||
android:configChanges="uiMode|screenSize|orientation|keyboardHidden|touchscreen|smallestScreenSize|screenLayout|navigation|mnc|mcc|locale|layoutDirection|keyboard|fontWeightAdjustment|fontScale|density|colorMode"
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.compose.material3.pullrefresh
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.debugInspectorInfo
|
||||
import androidx.compose.ui.platform.inspectable
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
|
||||
/**
|
||||
* A nested scroll modifier that provides scroll events to [state].
|
||||
*
|
||||
* Note that this modifier must be added above a scrolling container, such as a lazy column, in
|
||||
* order to receive scroll events. For example:
|
||||
*
|
||||
* @sample androidx.compose.material.samples.PullRefreshSample
|
||||
*
|
||||
* @param state The [PullRefreshState] associated with this pull-to-refresh component.
|
||||
* The state will be updated by this modifier.
|
||||
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
|
||||
*/
|
||||
// TODO(b/244423199): Move pullRefresh into its own material library similar to material-ripple.
|
||||
fun Modifier.pullRefresh(
|
||||
state: PullRefreshState,
|
||||
enabled: Boolean = true,
|
||||
) = inspectable(
|
||||
inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefresh"
|
||||
properties["state"] = state
|
||||
properties["enabled"] = enabled
|
||||
},
|
||||
) {
|
||||
Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
|
||||
}
|
||||
|
||||
/**
|
||||
* A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom
|
||||
* pull refresh components.
|
||||
*
|
||||
* Note that this modifier must be added above a scrolling container, such as a lazy column, in
|
||||
* order to receive scroll events. For example:
|
||||
*
|
||||
* @sample androidx.compose.material.samples.CustomPullRefreshSample
|
||||
*
|
||||
* @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
|
||||
* Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
|
||||
* down despite being at the top of a scrollable component), whereas negative delta (swiping up) is
|
||||
* dispatched first (in case it is needed to push the indicator back up), and then the unconsumed
|
||||
* delta is passed on to the child. The callback returns how much delta was consumed.
|
||||
* @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
|
||||
* The callback returns how much velocity was consumed - in most cases this should only consume
|
||||
* velocity if pull refresh has been dragged already and the velocity is positive (the fling is
|
||||
* downwards), as an upwards fling should typically still scroll a scrollable component beneath the
|
||||
* pullRefresh. This is invoked before any remaining velocity is passed to the child.
|
||||
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
|
||||
* [onPull] nor [onRelease] will be invoked.
|
||||
*/
|
||||
fun Modifier.pullRefresh(
|
||||
onPull: (pullDelta: Float) -> Float,
|
||||
onRelease: suspend (flingVelocity: Float) -> Float,
|
||||
enabled: Boolean = true,
|
||||
) = inspectable(
|
||||
inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefresh"
|
||||
properties["onPull"] = onPull
|
||||
properties["onRelease"] = onRelease
|
||||
properties["enabled"] = enabled
|
||||
},
|
||||
) {
|
||||
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
|
||||
}
|
||||
|
||||
private class PullRefreshNestedScrollConnection(
|
||||
private val onPull: (pullDelta: Float) -> Float,
|
||||
private val onRelease: suspend (flingVelocity: Float) -> Float,
|
||||
private val enabled: Boolean,
|
||||
) : NestedScrollConnection {
|
||||
|
||||
override fun onPreScroll(
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset = when {
|
||||
!enabled -> Offset.Zero
|
||||
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
|
||||
else -> Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset = when {
|
||||
!enabled -> Offset.Zero
|
||||
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
|
||||
else -> Offset.Zero
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
return Velocity(0f, onRelease(available.y))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.compose.material3.pullrefresh
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.
|
||||
*
|
||||
* @sample androidx.compose.material.samples.PullRefreshSample
|
||||
*
|
||||
* @param refreshing A boolean representing whether a refresh is occurring.
|
||||
* @param state The [PullRefreshState] which controls where and how the indicator will be drawn.
|
||||
* @param modifier Modifiers for the indicator.
|
||||
* @param backgroundColor The color of the indicator's background.
|
||||
* @param contentColor The color of the indicator's arc and arrow.
|
||||
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
|
||||
*/
|
||||
// TODO(b/244423199): Consider whether the state parameter should be replaced with lambdas to
|
||||
// enable people to use this indicator with custom pull-to-refresh components.
|
||||
@Composable
|
||||
fun PullRefreshIndicator(
|
||||
refreshing: Boolean,
|
||||
state: PullRefreshState,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.surface,
|
||||
contentColor: Color = contentColorFor(backgroundColor),
|
||||
scale: Boolean = false,
|
||||
) {
|
||||
val showElevation by remember(refreshing, state) {
|
||||
derivedStateOf { refreshing || state.position > 0.5f }
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(IndicatorSize)
|
||||
.pullRefreshIndicatorTransform(state, scale)
|
||||
.shadow(if (showElevation) Elevation else 0.dp, SpinnerShape, clip = true)
|
||||
.background(
|
||||
color = surfaceColorAtElevation(
|
||||
color = backgroundColor, elevation = Elevation
|
||||
), shape = SpinnerShape
|
||||
),
|
||||
) {
|
||||
Crossfade(
|
||||
targetState = refreshing,
|
||||
animationSpec = tween(durationMillis = CrossfadeDurationMs),
|
||||
) { refreshing ->
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val spinnerSize = (ArcRadius + StrokeWidth).times(2)
|
||||
|
||||
if (refreshing) {
|
||||
CircularProgressIndicator(
|
||||
color = contentColor,
|
||||
strokeWidth = StrokeWidth,
|
||||
modifier = Modifier.size(spinnerSize),
|
||||
)
|
||||
} else {
|
||||
CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier.size MUST be specified.
|
||||
*/
|
||||
@Composable
|
||||
private fun CircularArrowIndicator(
|
||||
state: PullRefreshState,
|
||||
color: Color,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
|
||||
|
||||
val targetAlpha by remember(state) {
|
||||
derivedStateOf {
|
||||
if (state.progress >= 1f) MaxAlpha else MinAlpha
|
||||
}
|
||||
}
|
||||
|
||||
val alphaState = animateFloatAsState(targetValue = targetAlpha, animationSpec = AlphaTween)
|
||||
|
||||
// Empty semantics for tests
|
||||
Canvas(modifier.semantics {}) {
|
||||
val values = ArrowValues(state.progress)
|
||||
val alpha = alphaState.value
|
||||
|
||||
rotate(degrees = values.rotation) {
|
||||
val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
|
||||
val arcBounds = Rect(
|
||||
size.center.x - arcRadius,
|
||||
size.center.y - arcRadius,
|
||||
size.center.x + arcRadius,
|
||||
size.center.y + arcRadius,
|
||||
)
|
||||
drawArc(
|
||||
color = color,
|
||||
alpha = alpha,
|
||||
startAngle = values.startAngle,
|
||||
sweepAngle = values.endAngle - values.startAngle,
|
||||
useCenter = false,
|
||||
topLeft = arcBounds.topLeft,
|
||||
size = arcBounds.size,
|
||||
style = Stroke(
|
||||
width = StrokeWidth.toPx(),
|
||||
cap = StrokeCap.Square,
|
||||
),
|
||||
)
|
||||
drawArrow(path, arcBounds, color, alpha, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private class ArrowValues(
|
||||
val rotation: Float,
|
||||
val startAngle: Float,
|
||||
val endAngle: Float,
|
||||
val scale: Float,
|
||||
)
|
||||
|
||||
private fun ArrowValues(progress: Float): ArrowValues {
|
||||
// Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%.
|
||||
val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3
|
||||
// How far beyond the threshold pull has gone, as a percentage of the threshold.
|
||||
val overshootPercent = abs(progress) - 1.0f
|
||||
// Limit the overshoot to 200%. Linear between 0 and 200.
|
||||
val linearTension = overshootPercent.coerceIn(0f, 2f)
|
||||
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
|
||||
val tensionPercent = linearTension - linearTension.pow(2) / 4
|
||||
|
||||
// Calculations based on SwipeRefreshLayout specification.
|
||||
val endTrim = adjustedPercent * MaxProgressArc
|
||||
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
|
||||
val startAngle = rotation * 360
|
||||
val endAngle = (rotation + endTrim) * 360
|
||||
val scale = min(1f, adjustedPercent)
|
||||
|
||||
return ArrowValues(rotation, startAngle, endAngle, scale)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawArrow(
|
||||
arrow: Path,
|
||||
bounds: Rect,
|
||||
color: Color,
|
||||
alpha: Float,
|
||||
values: ArrowValues,
|
||||
) {
|
||||
arrow.reset()
|
||||
arrow.moveTo(0f, 0f) // Move to left corner
|
||||
arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
|
||||
|
||||
// Line to tip of arrow
|
||||
arrow.lineTo(
|
||||
x = ArrowWidth.toPx() * values.scale / 2,
|
||||
y = ArrowHeight.toPx() * values.scale,
|
||||
)
|
||||
|
||||
val radius = min(bounds.width, bounds.height) / 2f
|
||||
val inset = ArrowWidth.toPx() * values.scale / 2f
|
||||
arrow.translate(
|
||||
Offset(
|
||||
x = radius + bounds.center.x - inset,
|
||||
y = bounds.center.y + StrokeWidth.toPx() / 2f,
|
||||
),
|
||||
)
|
||||
arrow.close()
|
||||
rotate(degrees = values.endAngle) {
|
||||
drawPath(path = arrow, color = color, alpha = alpha)
|
||||
}
|
||||
}
|
||||
|
||||
private const val CrossfadeDurationMs = 100
|
||||
private const val MaxProgressArc = 0.8f
|
||||
|
||||
private val IndicatorSize = 40.dp
|
||||
private val SpinnerShape = CircleShape
|
||||
private val ArcRadius = 7.5.dp
|
||||
private val StrokeWidth = 2.5.dp
|
||||
private val ArrowWidth = 10.dp
|
||||
private val ArrowHeight = 5.dp
|
||||
private val Elevation = 6.dp
|
||||
|
||||
// Values taken from SwipeRefreshLayout
|
||||
private const val MinAlpha = 0.3f
|
||||
private const val MaxAlpha = 1f
|
||||
private val AlphaTween = tween<Float>(300, easing = LinearEasing)
|
|
@ -0,0 +1,13 @@
|
|||
package androidx.compose.material3.pullrefresh
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable
|
||||
internal fun surfaceColorAtElevation(color: Color, elevation: Dp): Color = when (color) {
|
||||
MaterialTheme.colorScheme.surface -> MaterialTheme.colorScheme.surfaceColorAtElevation(elevation)
|
||||
else -> color
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.compose.material3.pullrefresh
|
||||
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.debugInspectorInfo
|
||||
import androidx.compose.ui.platform.inspectable
|
||||
|
||||
/**
|
||||
* A modifier for translating the position and scaling the size of a pull-to-refresh indicator
|
||||
* based on the given [PullRefreshState].
|
||||
*
|
||||
* @sample androidx.compose.material.samples.PullRefreshIndicatorTransformSample
|
||||
*
|
||||
* @param state The [PullRefreshState] which determines the position of the indicator.
|
||||
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
|
||||
*/
|
||||
// TODO: Consider whether the state parameter should be replaced with lambdas.
|
||||
fun Modifier.pullRefreshIndicatorTransform(
|
||||
state: PullRefreshState,
|
||||
scale: Boolean = false,
|
||||
) = inspectable(
|
||||
inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefreshIndicatorTransform"
|
||||
properties["state"] = state
|
||||
properties["scale"] = scale
|
||||
},
|
||||
) {
|
||||
Modifier
|
||||
// Essentially we only want to clip the at the top, so the indicator will not appear when
|
||||
// the position is 0. It is preferable to clip the indicator as opposed to the layout that
|
||||
// contains the indicator, as this would also end up clipping shadows drawn by items in a
|
||||
// list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE
|
||||
// for the other dimensions to allow for more room for elevation / arbitrary indicators - we
|
||||
// only ever really want to clip at the top edge.
|
||||
.drawWithContent {
|
||||
clipRect(
|
||||
top = 0f,
|
||||
left = -Float.MAX_VALUE,
|
||||
right = Float.MAX_VALUE,
|
||||
bottom = Float.MAX_VALUE,
|
||||
) {
|
||||
this@drawWithContent.drawContent()
|
||||
}
|
||||
}
|
||||
.graphicsLayer {
|
||||
translationY = state.position - size.height
|
||||
|
||||
if (scale && !state.refreshing) {
|
||||
val scaleFraction = LinearOutSlowInEasing
|
||||
.transform(state.position / state.threshold)
|
||||
.coerceIn(0f, 1f)
|
||||
scaleX = scaleFraction
|
||||
scaleY = scaleFraction
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.compose.material3.pullrefresh
|
||||
|
||||
import androidx.compose.animation.core.animate
|
||||
import androidx.compose.foundation.MutatorMutex
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
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.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Creates a [PullRefreshState] that is remembered across compositions.
|
||||
*
|
||||
* Changes to [refreshing] will result in [PullRefreshState] being updated.
|
||||
*
|
||||
* @sample androidx.compose.material.samples.PullRefreshSample
|
||||
*
|
||||
* @param refreshing A boolean representing whether a refresh is currently occurring.
|
||||
* @param onRefresh The function to be called to trigger a refresh.
|
||||
* @param refreshThreshold The threshold below which, if a release
|
||||
* occurs, [onRefresh] will be called.
|
||||
* @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This
|
||||
* offset corresponds to the position of the bottom of the indicator.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPullRefreshState(
|
||||
refreshing: Boolean,
|
||||
onRefresh: () -> Unit,
|
||||
refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
|
||||
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
|
||||
): PullRefreshState {
|
||||
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val onRefreshState = rememberUpdatedState(onRefresh)
|
||||
val thresholdPx: Float
|
||||
val refreshingOffsetPx: Float
|
||||
|
||||
with(LocalDensity.current) {
|
||||
thresholdPx = refreshThreshold.toPx()
|
||||
refreshingOffsetPx = refreshingOffset.toPx()
|
||||
}
|
||||
|
||||
val state = remember(scope) {
|
||||
PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx)
|
||||
}
|
||||
|
||||
SideEffect {
|
||||
state.setRefreshing(refreshing)
|
||||
state.setThreshold(thresholdPx)
|
||||
state.setRefreshingOffset(refreshingOffsetPx)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh
|
||||
* behaviour to a scroll component. Based on Android's SwipeRefreshLayout.
|
||||
*
|
||||
* Provides [progress], a float representing how far the user has pulled as a percentage of the
|
||||
* refreshThreshold. Values of one or less indicate that the user has not yet pulled past the
|
||||
* threshold. Values greater than one indicate how far past the threshold the user has pulled.
|
||||
*
|
||||
* Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like
|
||||
* pull-to-refresh behaviour with a custom indicator.
|
||||
*
|
||||
* Should be created using [rememberPullRefreshState].
|
||||
*/
|
||||
class PullRefreshState internal constructor(
|
||||
private val animationScope: CoroutineScope,
|
||||
private val onRefreshState: State<() -> Unit>,
|
||||
refreshingOffset: Float,
|
||||
threshold: Float,
|
||||
) {
|
||||
/**
|
||||
* A float representing how far the user has pulled as a percentage of the refreshThreshold.
|
||||
*
|
||||
* If the component has not been pulled at all, progress is zero. If the pull has reached
|
||||
* halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has
|
||||
* gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to
|
||||
* two times the refreshThreshold.
|
||||
*/
|
||||
val progress get() = adjustedDistancePulled / threshold
|
||||
|
||||
internal val refreshing get() = _refreshing
|
||||
internal val position get() = _position
|
||||
internal val threshold get() = _threshold
|
||||
|
||||
private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier }
|
||||
|
||||
private var _refreshing by mutableStateOf(false)
|
||||
private var _position by mutableFloatStateOf(0f)
|
||||
private var distancePulled by mutableFloatStateOf(0f)
|
||||
private var _threshold by mutableFloatStateOf(threshold)
|
||||
private var _refreshingOffset by mutableFloatStateOf(refreshingOffset)
|
||||
|
||||
internal fun onPull(pullDelta: Float): Float {
|
||||
if (_refreshing) return 0f // Already refreshing, do nothing.
|
||||
|
||||
val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f)
|
||||
val dragConsumed = newOffset - distancePulled
|
||||
distancePulled = newOffset
|
||||
_position = calculateIndicatorPosition()
|
||||
return dragConsumed
|
||||
}
|
||||
|
||||
internal fun onRelease(velocity: Float): Float {
|
||||
if (refreshing) return 0f // Already refreshing, do nothing
|
||||
|
||||
if (adjustedDistancePulled > threshold) {
|
||||
onRefreshState.value()
|
||||
}
|
||||
animateIndicatorTo(0f)
|
||||
val consumed = when {
|
||||
// We are flinging without having dragged the pull refresh (for example a fling inside
|
||||
// a list) - don't consume
|
||||
distancePulled == 0f -> 0f
|
||||
// If the velocity is negative, the fling is upwards, and we don't want to prevent the
|
||||
// the list from scrolling
|
||||
velocity < 0f -> 0f
|
||||
// We are showing the indicator, and the fling is downwards - consume everything
|
||||
else -> velocity
|
||||
}
|
||||
distancePulled = 0f
|
||||
return consumed
|
||||
}
|
||||
|
||||
internal fun setRefreshing(refreshing: Boolean) {
|
||||
if (_refreshing != refreshing) {
|
||||
_refreshing = refreshing
|
||||
distancePulled = 0f
|
||||
animateIndicatorTo(if (refreshing) _refreshingOffset else 0f)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setThreshold(threshold: Float) {
|
||||
_threshold = threshold
|
||||
}
|
||||
|
||||
internal fun setRefreshingOffset(refreshingOffset: Float) {
|
||||
if (_refreshingOffset != refreshingOffset) {
|
||||
_refreshingOffset = refreshingOffset
|
||||
if (refreshing) animateIndicatorTo(refreshingOffset)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to cancel any existing animations when we launch a new one. We use this instead of
|
||||
// Animatable as calling snapTo() on every drag delta has a one frame delay, and some extra
|
||||
// overhead of running through the animation pipeline instead of directly mutating the state.
|
||||
private val mutatorMutex = MutatorMutex()
|
||||
|
||||
private fun animateIndicatorTo(offset: Float) = animationScope.launch {
|
||||
mutatorMutex.mutate {
|
||||
animate(initialValue = _position, targetValue = offset) { value, _ ->
|
||||
_position = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateIndicatorPosition(): Float = when {
|
||||
// If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
|
||||
adjustedDistancePulled <= threshold -> adjustedDistancePulled
|
||||
else -> {
|
||||
// How far beyond the threshold pull has gone, as a percentage of the threshold.
|
||||
val overshootPercent = abs(progress) - 1.0f
|
||||
// Limit the overshoot to 200%. Linear between 0 and 200.
|
||||
val linearTension = overshootPercent.coerceIn(0f, 2f)
|
||||
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
|
||||
val tensionPercent = linearTension - linearTension.pow(2) / 4
|
||||
// The additional offset beyond the threshold.
|
||||
val extraOffset = threshold * tensionPercent
|
||||
threshold + extraOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default parameter values for [rememberPullRefreshState].
|
||||
*/
|
||||
object PullRefreshDefaults {
|
||||
/**
|
||||
* If the indicator is below this threshold offset when it is released, a refresh
|
||||
* will be triggered.
|
||||
*/
|
||||
val RefreshThreshold = 80.dp
|
||||
|
||||
/**
|
||||
* The offset at which the indicator should be rendered whilst a refresh is occurring.
|
||||
*/
|
||||
val RefreshingOffset = 56.dp
|
||||
}
|
||||
|
||||
/**
|
||||
* The distance pulled is multiplied by this value to give us the adjusted distance pulled, which
|
||||
* is used in calculating the indicator position (when the adjusted distance pulled is less than
|
||||
* the refresh threshold, it is the indicator position, otherwise the indicator position is
|
||||
* derived from the progress).
|
||||
*/
|
||||
private const val DragMultiplier = 0.5f
|
|
@ -47,9 +47,9 @@ class MainActivity : CompositionActivity({
|
|||
}
|
||||
|
||||
setContent {
|
||||
UpgradeDialog()
|
||||
val navController = rememberNavController()
|
||||
AppTheme(false) {
|
||||
AppTheme {
|
||||
UpgradeDialog()
|
||||
CompositionLocalProvider(
|
||||
LocalLauncher provides launcher,
|
||||
LocalPickContentLauncher provides pickContentLauncher,
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
package li.songe.gkd.icon
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
// @DslMarker
|
||||
// https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-react/src/jsMain/kotlin/react/ChildrenBuilder.kt
|
||||
val AddIcon = materialIcon(name = "AddIcon") {
|
||||
addPath(
|
||||
pathData = addPathNodes("M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"),
|
||||
fill = Brush.linearGradient(listOf(Color.Black, Color.Black))
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewAddIcon() {
|
||||
Icon(imageVector = AddIcon, contentDescription = null)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package li.songe.gkd.icon
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
val ArrowIcon = materialIcon(name = "ArrowIcon") {
|
||||
addPath(
|
||||
pathData = addPathNodes("M6.23 20.23L8 22l10-10L8 2L6.23 3.77L14.46 12z"),
|
||||
fill = Brush.linearGradient(listOf(Color.Black, Color.Black))
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewArrowIcon() {
|
||||
Icon(imageVector = ArrowIcon, contentDescription = null)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package li.songe.gkd.icon
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
val HomeIcon = materialIcon(name = "ArrowIcon") {
|
||||
addPath(
|
||||
pathData = addPathNodes("M16.612 2.214a1.01 1.01 0 0 0-1.242 0L1 13.419l1.243 1.572L4 13.621V26a2.004 2.004 0 0 0 2 2h20a2.004 2.004 0 0 0 2-2V13.63L29.757 15L31 13.428zM18 26h-4v-8h4zm2 0v-8a2.002 2.002 0 0 0-2-2h-4a2.002 2.002 0 0 0-2 2v8H6V12.062l10-7.79l10 7.8V26z"),
|
||||
fill = Brush.linearGradient(listOf(Color.Black, Color.Black))
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewHomeIcon() {
|
||||
Icon(imageVector = HomeIcon, contentDescription = null)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package li.songe.gkd.icon
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTestDsl() {
|
||||
val vectorString = """
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8l8,8l1.41,-1.41L7.83,13H20v-2z" />
|
||||
</vector>
|
||||
""".trim()
|
||||
val drawable = Drawable.createFromStream(vectorString.byteInputStream(), "ic_back")
|
||||
if (drawable != null) {
|
||||
Image(painter = rememberDrawablePainter(drawable = drawable), contentDescription = null)
|
||||
} else {
|
||||
Text(text = "null drawable")
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ val abNotif by lazy {
|
|||
Notif(
|
||||
id = 100,
|
||||
icon = SafeR.ic_launcher,
|
||||
title = "搞快点",
|
||||
title = "GKD",
|
||||
text = "无障碍正在运行",
|
||||
ongoing = true,
|
||||
autoCancel = false
|
||||
|
@ -26,7 +26,7 @@ val screenshotNotif by lazy {
|
|||
Notif(
|
||||
id = 101,
|
||||
icon = SafeR.ic_launcher,
|
||||
title = "搞快点",
|
||||
title = "GKD",
|
||||
text = "截屏服务正在运行",
|
||||
ongoing = true,
|
||||
autoCancel = false
|
||||
|
@ -37,7 +37,7 @@ val floatingNotif by lazy {
|
|||
Notif(
|
||||
id = 102,
|
||||
icon = SafeR.ic_launcher,
|
||||
title = "搞快点",
|
||||
title = "GKD",
|
||||
text = "悬浮窗按钮正在显示",
|
||||
ongoing = true,
|
||||
autoCancel = false
|
||||
|
@ -47,7 +47,7 @@ val httpNotif by lazy {
|
|||
Notif(
|
||||
id = 103,
|
||||
icon = SafeR.ic_launcher,
|
||||
title = "搞快点",
|
||||
title = "GKD",
|
||||
text = "HTTP服务正在运行",
|
||||
ongoing = true,
|
||||
autoCancel = false
|
||||
|
|
|
@ -10,7 +10,7 @@ data class NotifChannel(
|
|||
|
||||
val defaultChannel by lazy {
|
||||
NotifChannel(
|
||||
id = "default", name = "搞快点", desc = "显示服务运行状态"
|
||||
id = "default", name = "GKD", desc = "显示服务运行状态"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.graphics.Bitmap
|
|||
import android.os.Build
|
||||
import android.view.Display
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.blankj.utilcode.util.NetworkUtils
|
||||
import com.blankj.utilcode.util.ScreenUtils
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
package li.songe.gkd.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
|
@ -23,18 +19,6 @@ import li.songe.gkd.util.ProfileTransitions
|
|||
@Destination(style = ProfileTransitions::class)
|
||||
@Composable
|
||||
fun AboutPage() {
|
||||
// val systemUiController = rememberSystemUiController()
|
||||
// val context = LocalContext.current as ComponentActivity
|
||||
// DisposableEffect(systemUiController) {
|
||||
// val oldVisible = systemUiController.isStatusBarVisible
|
||||
// systemUiController.isStatusBarVisible = false
|
||||
// WindowCompat.setDecorFitsSystemWindows(context.window, false)
|
||||
// onDispose {
|
||||
// systemUiController.isStatusBarVisible = oldVisible
|
||||
// WindowCompat.setDecorFitsSystemWindows(context.window, true)
|
||||
// }
|
||||
// }
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
Scaffold(topBar = {
|
||||
SimpleTopAppBar(onClickIcon = { navController.popBackStack() }, title = "关于")
|
||||
|
|
|
@ -16,18 +16,21 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -88,7 +91,7 @@ fun AppItemPage(
|
|||
)
|
||||
}
|
||||
|
||||
val editable = subsItemId < 0
|
||||
val editable = subsItem != null && subsItemId < 0
|
||||
|
||||
var showAddDlg by remember { mutableStateOf(false) }
|
||||
|
||||
|
@ -115,172 +118,172 @@ fun AppItemPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
content = { contentPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
appRaw?.groups?.let { groupsVal ->
|
||||
itemsIndexed(groupsVal, { i, g -> i.toString() + g.key }) { _, group ->
|
||||
Row(
|
||||
}, content = { contentPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(contentPadding)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
appRaw?.groups?.let { groupsVal ->
|
||||
itemsIndexed(groupsVal, { i, g -> i.toString() + g.key }) { _, group ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
if (group.key == focusGroupKey) MaterialTheme.colorScheme.inversePrimary else Color.Transparent
|
||||
)
|
||||
.clickable { setShowGroupItem(group) }
|
||||
.padding(10.dp, 6.dp)
|
||||
.fillMaxWidth()
|
||||
.height(45.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
if (group.key == focusGroupKey) Color(0x500a95ff) else Color.Transparent
|
||||
)
|
||||
.clickable { setShowGroupItem(group) }
|
||||
.padding(10.dp, 6.dp)
|
||||
.fillMaxWidth()
|
||||
.height(45.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
.weight(1f)
|
||||
.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = group.name,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
if (group.valid) {
|
||||
Text(
|
||||
text = group.name,
|
||||
text = group.desc ?: "-",
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "规则组损坏",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
if (editable) {
|
||||
IconButton(onClick = {
|
||||
setMenuGroupRaw(group)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "more",
|
||||
modifier = Modifier.size(30.dp)
|
||||
)
|
||||
if (group.valid) {
|
||||
Text(
|
||||
text = group.desc ?: "-",
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "规则组损坏",
|
||||
color = Color.Red,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
if (editable) {
|
||||
IconButton(onClick = {
|
||||
setMenuGroupRaw(group)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "more",
|
||||
modifier = Modifier.size(30.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
}
|
||||
|
||||
val subsConfig = subsConfigs.find { it.groupKey == group.key }
|
||||
Switch(checked = (subsConfig?.enable ?: group.enable) ?: true,
|
||||
modifier = Modifier,
|
||||
onCheckedChange = scope.launchAsFn { enable ->
|
||||
val newItem = (subsConfig?.copy(enable = enable) ?: SubsConfig(
|
||||
type = SubsConfig.GroupType,
|
||||
subsItemId = subsItemId,
|
||||
appId = appId,
|
||||
groupKey = group.key,
|
||||
enable = enable
|
||||
))
|
||||
DbSet.subsConfigDao.insert(newItem)
|
||||
})
|
||||
}
|
||||
|
||||
val subsConfig = subsConfigs.find { it.groupKey == group.key }
|
||||
Switch(checked = (subsConfig?.enable ?: group.enable) ?: true,
|
||||
modifier = Modifier,
|
||||
onCheckedChange = scope.launchAsFn { enable ->
|
||||
val newItem = (subsConfig?.copy(enable = enable) ?: SubsConfig(
|
||||
type = SubsConfig.GroupType,
|
||||
subsItemId = subsItemId,
|
||||
appId = appId,
|
||||
groupKey = group.key,
|
||||
enable = enable
|
||||
))
|
||||
DbSet.subsConfigDao.insert(newItem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
showGroupItem?.let { showGroupItemVal ->
|
||||
AlertDialog(modifier = Modifier.defaultMinSize(300.dp),
|
||||
onDismissRequest = { setShowGroupItem(null) },
|
||||
title = {
|
||||
Text(text = showGroupItemVal.name)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
if (showGroupItemVal.enable == false) {
|
||||
Text(text = "该规则组默认不启用", color = Color.Blue)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
Text(text = showGroupItemVal.desc ?: "-")
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val groupAppText = Singleton.json.encodeToString(
|
||||
appRaw?.copy(
|
||||
groups = listOf(showGroupItemVal)
|
||||
)
|
||||
)
|
||||
ClipboardUtils.copyText(groupAppText)
|
||||
ToastUtils.showShort("复制成功")
|
||||
}) {
|
||||
Text(text = "复制规则组")
|
||||
}
|
||||
})
|
||||
onDismissRequest = { setShowGroupItem(null) },
|
||||
title = {
|
||||
Text(text = showGroupItemVal.name)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
if (showGroupItemVal.enable == false) {
|
||||
Text(text = "该规则组默认不启用")
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
Text(text = showGroupItemVal.desc ?: "-")
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val groupAppText = Singleton.json.encodeToString(
|
||||
appRaw?.copy(
|
||||
groups = listOf(showGroupItemVal)
|
||||
)
|
||||
)
|
||||
ClipboardUtils.copyText(groupAppText)
|
||||
ToastUtils.showShort("复制成功")
|
||||
}) {
|
||||
Text(text = "复制规则组")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (menuGroupRaw != null && appRawVal != null && subsItemVal != null) {
|
||||
Dialog(onDismissRequest = { setMenuGroupRaw(null) }) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.width(200.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(text = "编辑", modifier = Modifier
|
||||
.clickable {
|
||||
setEditGroupRaw(menuGroupRaw)
|
||||
setMenuGroupRaw(null)
|
||||
}
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth())
|
||||
Text(text = "删除", color = Color.Red, modifier = Modifier
|
||||
.clickable {
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@launchTry
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps
|
||||
.toMutableList()
|
||||
.apply {
|
||||
set(indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups.filter { g -> g.key != menuGroupRaw.key })
|
||||
)
|
||||
})
|
||||
subsItemVal.subsFile.writeText(
|
||||
Singleton.json.encodeToString(
|
||||
newSubsRaw
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(
|
||||
subsItemVal.id, appRawVal.id, menuGroupRaw.key
|
||||
)
|
||||
ToastUtils.showShort("删除成功")
|
||||
Column {
|
||||
Text(text = "编辑", modifier = Modifier
|
||||
.clickable {
|
||||
setEditGroupRaw(menuGroupRaw)
|
||||
setMenuGroupRaw(null)
|
||||
}
|
||||
}
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth())
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth())
|
||||
Text(text = "删除", modifier = Modifier
|
||||
.clickable {
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@launchTry
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps
|
||||
.toMutableList()
|
||||
.apply {
|
||||
set(
|
||||
indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups.filter { g -> g.key != menuGroupRaw.key })
|
||||
)
|
||||
})
|
||||
subsItemVal.subsFile.writeText(
|
||||
Singleton.json.encodeToString(
|
||||
newSubsRaw
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(
|
||||
subsItemVal.id, appRawVal.id, menuGroupRaw.key
|
||||
)
|
||||
ToastUtils.showShort("删除成功")
|
||||
setMenuGroupRaw(null)
|
||||
}
|
||||
}
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(), color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,153 +292,146 @@ fun AppItemPage(
|
|||
var source by remember {
|
||||
mutableStateOf(Singleton.json.encodeToString(editGroupRaw))
|
||||
}
|
||||
Dialog(onDismissRequest = { setEditGroupRaw(null) }) {
|
||||
Column(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 300.dp),
|
||||
) {
|
||||
Text(text = "编辑规则组", fontSize = 18.sp, modifier = Modifier.padding(10.dp))
|
||||
val oldSource = remember { source }
|
||||
AlertDialog(
|
||||
title = { Text(text = "编辑规则组") },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则组") },
|
||||
maxLines = 8,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 10.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
val newGroupRaw = try {
|
||||
SubscriptionRaw.parseGroupRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则:${e.message}")
|
||||
return@TextButton
|
||||
}
|
||||
if (newGroupRaw.key != editGroupRaw.key) {
|
||||
ToastUtils.showShort("不能更改规则组的key")
|
||||
return@TextButton
|
||||
}
|
||||
if (!newGroupRaw.valid) {
|
||||
ToastUtils.showShort("非法规则:存在非法选择器")
|
||||
return@TextButton
|
||||
}
|
||||
setEditGroupRaw(null)
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@TextButton
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps.toMutableList().apply {
|
||||
set(indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups.toMutableList().apply {
|
||||
set(
|
||||
indexOfFirst { g -> g.key == newGroupRaw.key }, newGroupRaw
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(
|
||||
Singleton.json.encodeToString(
|
||||
newSubsRaw
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
ToastUtils.showShort("更新成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "更新")
|
||||
}
|
||||
},
|
||||
onDismissRequest = { setEditGroupRaw(null) },
|
||||
dismissButton = {
|
||||
TextButton(onClick = { setEditGroupRaw(null) }) {
|
||||
Text(text = "取消")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (oldSource == source) {
|
||||
ToastUtils.showShort("规则无变动")
|
||||
return@TextButton
|
||||
}
|
||||
val newGroupRaw = try {
|
||||
SubscriptionRaw.parseGroupRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则:${e.message}")
|
||||
return@TextButton
|
||||
}
|
||||
if (newGroupRaw.key != editGroupRaw.key) {
|
||||
ToastUtils.showShort("不能更改规则组的key")
|
||||
return@TextButton
|
||||
}
|
||||
if (!newGroupRaw.valid) {
|
||||
ToastUtils.showShort("非法规则:存在非法选择器")
|
||||
return@TextButton
|
||||
}
|
||||
setEditGroupRaw(null)
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@TextButton
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps.toMutableList().apply {
|
||||
set(indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups.toMutableList().apply {
|
||||
set(
|
||||
indexOfFirst { g -> g.key == newGroupRaw.key }, newGroupRaw
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(
|
||||
Singleton.json.encodeToString(
|
||||
newSubsRaw
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
ToastUtils.showShort("更新成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "更新")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showAddDlg && appRawVal != null && subsItemVal != null) {
|
||||
var source by remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
Dialog(onDismissRequest = { showAddDlg = false }) {
|
||||
Column(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 300.dp),
|
||||
) {
|
||||
Text(text = "添加规则组", fontSize = 18.sp, modifier = Modifier.padding(10.dp))
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则组\n可以是APP规则\n也可以是单个规则组") },
|
||||
maxLines = 8,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 10.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
val newAppRaw = try {
|
||||
SubscriptionRaw.parseAppRaw(source)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
val tempGroups = if (newAppRaw == null) {
|
||||
val newGroupRaw = try {
|
||||
SubscriptionRaw.parseGroupRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则:${e.message}")
|
||||
return@TextButton
|
||||
}
|
||||
listOf(newGroupRaw)
|
||||
} else {
|
||||
if (newAppRaw.id != appRawVal.id) {
|
||||
ToastUtils.showShort("id不一致,无法添加")
|
||||
return@TextButton
|
||||
}
|
||||
if (newAppRaw.groups.isEmpty()) {
|
||||
ToastUtils.showShort("不能添加空规则组")
|
||||
return@TextButton
|
||||
}
|
||||
newAppRaw.groups
|
||||
}
|
||||
if (!tempGroups.all { g -> g.valid }) {
|
||||
ToastUtils.showShort("非法规则:存在非法选择器")
|
||||
return@TextButton
|
||||
}
|
||||
tempGroups.forEach { g ->
|
||||
if (appRawVal.groups.any { g2 -> g2.name == g.name }) {
|
||||
ToastUtils.showShort("存在同名规则[${g.name}]")
|
||||
return@TextButton
|
||||
}
|
||||
}
|
||||
val newKey = appRawVal.groups.maxBy { g -> g.key }.key + 1
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@TextButton
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps.toMutableList().apply {
|
||||
set(indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups + tempGroups.mapIndexed { i, g ->
|
||||
g.copy(
|
||||
key = newKey + i
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(Singleton.json.encodeToString(newSubsRaw))
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
showAddDlg = false
|
||||
ToastUtils.showShort("添加成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
AlertDialog(title = { Text(text = "添加规则组") }, text = {
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则组\n可以是APP规则\n也可以是单个规则组") },
|
||||
maxLines = 8,
|
||||
)
|
||||
}, onDismissRequest = { showAddDlg = false }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val newAppRaw = try {
|
||||
SubscriptionRaw.parseAppRaw(source)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
val tempGroups = if (newAppRaw == null) {
|
||||
val newGroupRaw = try {
|
||||
SubscriptionRaw.parseGroupRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则:${e.message}")
|
||||
return@TextButton
|
||||
}
|
||||
listOf(newGroupRaw)
|
||||
} else {
|
||||
if (newAppRaw.id != appRawVal.id) {
|
||||
ToastUtils.showShort("id不一致,无法添加")
|
||||
return@TextButton
|
||||
}
|
||||
if (newAppRaw.groups.isEmpty()) {
|
||||
ToastUtils.showShort("不能添加空规则组")
|
||||
return@TextButton
|
||||
}
|
||||
newAppRaw.groups
|
||||
}
|
||||
if (!tempGroups.all { g -> g.valid }) {
|
||||
ToastUtils.showShort("非法规则:存在非法选择器")
|
||||
return@TextButton
|
||||
}
|
||||
tempGroups.forEach { g ->
|
||||
if (appRawVal.groups.any { g2 -> g2.name == g.name }) {
|
||||
ToastUtils.showShort("存在同名规则[${g.name}]")
|
||||
return@TextButton
|
||||
}
|
||||
}
|
||||
val newKey = appRawVal.groups.maxBy { g -> g.key }.key + 1
|
||||
val subsRaw = subsIdToRawFlow.value[subsItemId] ?: return@TextButton
|
||||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps.toMutableList().apply {
|
||||
set(indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups + tempGroups.mapIndexed { i, g ->
|
||||
g.copy(
|
||||
key = newKey + i
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(Singleton.json.encodeToString(newSubsRaw))
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
showAddDlg = false
|
||||
ToastUtils.showShort("添加成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
}
|
||||
}, dismissButton = {
|
||||
TextButton(onClick = { showAddDlg = false }) {
|
||||
Text(text = "取消")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package li.songe.gkd.ui
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
@ -16,14 +12,18 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Divider
|
||||
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.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -33,7 +33,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
@ -87,27 +86,22 @@ fun ClickLogPage() {
|
|||
})
|
||||
}, content = { contentPadding ->
|
||||
if (clickLogs.isNotEmpty()) {
|
||||
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 0.dp, 10.dp, 0.dp)
|
||||
.padding(contentPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
}
|
||||
items(clickLogs, { triggerLog -> triggerLog.id }) { triggerLog ->
|
||||
Column(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(BorderStroke(1.dp, Color.Black))
|
||||
.clickable {
|
||||
previewClickLog = triggerLog
|
||||
}) {
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)) {
|
||||
Row {
|
||||
Text(
|
||||
text = triggerLog.id.format("yyyy-MM-dd HH:mm:ss"),
|
||||
text = triggerLog.id.format("MM-dd HH:mm:ss"),
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
@ -132,6 +126,7 @@ fun ClickLogPage() {
|
|||
Text(text = rule.name)
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
@ -151,35 +146,41 @@ fun ClickLogPage() {
|
|||
|
||||
previewClickLog?.let { previewTriggerLogVal ->
|
||||
Dialog(onDismissRequest = { previewClickLog = null }) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(250.dp)
|
||||
.background(Color.White)
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(text = "查看规则组", modifier = Modifier
|
||||
.clickable {
|
||||
previewTriggerLogVal.appId ?: return@clickable
|
||||
navController.navigate(
|
||||
AppItemPageDestination(
|
||||
previewTriggerLogVal.subsId,
|
||||
previewTriggerLogVal.appId,
|
||||
previewTriggerLogVal.groupKey
|
||||
Column {
|
||||
Text(text = "查看规则组", modifier = Modifier
|
||||
.clickable {
|
||||
previewTriggerLogVal.appId ?: return@clickable
|
||||
navController.navigate(
|
||||
AppItemPageDestination(
|
||||
previewTriggerLogVal.subsId,
|
||||
previewTriggerLogVal.appId,
|
||||
previewTriggerLogVal.groupKey
|
||||
)
|
||||
)
|
||||
)
|
||||
previewClickLog = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp))
|
||||
Text(text = "删除", modifier = Modifier
|
||||
.clickable(onClick = scope.launchAsFn {
|
||||
previewClickLog = null
|
||||
DbSet.clickLogDao.delete(previewTriggerLogVal)
|
||||
})
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp))
|
||||
previewClickLog = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
Text(
|
||||
text = "删除",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = scope.launchAsFn {
|
||||
previewClickLog = null
|
||||
DbSet.clickLogDao.delete(previewTriggerLogVal)
|
||||
})
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +192,7 @@ fun ClickLogPage() {
|
|||
showDeleteDlg = false
|
||||
DbSet.clickLogDao.deleteAll()
|
||||
}) {
|
||||
Text(text = "是", color = Color.Red)
|
||||
Text(text = "是", color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
|
|
@ -11,19 +11,19 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
@ -31,7 +31,6 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.blankj.utilcode.util.ToastUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import li.songe.gkd.util.navigate
|
||||
import li.songe.gkd.MainActivity
|
||||
import li.songe.gkd.appScope
|
||||
import li.songe.gkd.service.GkdAbService
|
||||
|
@ -41,12 +40,14 @@ import li.songe.gkd.ui.destinations.ClickLogPageDestination
|
|||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.SafeR
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.navigate
|
||||
import li.songe.gkd.util.storeFlow
|
||||
import li.songe.gkd.util.updateStorage
|
||||
import li.songe.gkd.util.usePollState
|
||||
|
||||
val controlNav = BottomNavItem(label = "主页", icon = SafeR.ic_home, route = "settings")
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ControlPage() {
|
||||
val context = LocalContext.current as MainActivity
|
||||
|
@ -58,9 +59,9 @@ fun ControlPage() {
|
|||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(backgroundColor = Color(0xfff8f9f9), title = {
|
||||
TopAppBar(title = {
|
||||
Text(
|
||||
text = "搞快点", color = Color.Black
|
||||
text = "GKD"
|
||||
)
|
||||
})
|
||||
},
|
||||
|
@ -78,35 +79,33 @@ fun ControlPage() {
|
|||
NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||
}
|
||||
if (!notifEnabled) {
|
||||
AuthCard(title = "通知权限",
|
||||
desc = "用于启动后台服务,展示服务运行状态",
|
||||
onAuthClick = {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.applicationInfo.uid)
|
||||
context.startActivity(intent)
|
||||
})
|
||||
AuthCard(title = "通知权限", desc = "用于启动后台服务,展示服务运行状态", onAuthClick = {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.applicationInfo.uid)
|
||||
context.startActivity(intent)
|
||||
})
|
||||
Divider()
|
||||
}
|
||||
|
||||
val gkdAccessRunning by usePollState { GkdAbService.isRunning() }
|
||||
if (!gkdAccessRunning) {
|
||||
AuthCard(title = "无障碍权限",
|
||||
desc = "用于获取屏幕信息,点击屏幕上的控件",
|
||||
onAuthClick = {
|
||||
if (notifEnabled) {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
// android.content.ActivityNotFoundException
|
||||
// https://bugly.qq.com/v2/crash-reporting/crashes/d0ce46b353/113010?pid=1
|
||||
context.startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
ToastUtils.showShort("必须先开启[通知权限]")
|
||||
}
|
||||
})
|
||||
desc = "用于获取屏幕信息,点击屏幕上的控件",
|
||||
onAuthClick = {
|
||||
if (notifEnabled) {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
// android.content.ActivityNotFoundException
|
||||
// https://bugly.qq.com/v2/crash-reporting/crashes/d0ce46b353/113010?pid=1
|
||||
context.startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
ToastUtils.showShort("必须先开启[通知权限]")
|
||||
}
|
||||
})
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
@ -114,29 +113,29 @@ fun ControlPage() {
|
|||
val canDrawOverlays by usePollState { Settings.canDrawOverlays(context) }
|
||||
if (!canDrawOverlays) {
|
||||
AuthCard(title = "悬浮窗权限",
|
||||
desc = "用于后台提示,显示保存快照按钮等功能",
|
||||
onAuthClick = {
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
context.startActivity(intent)
|
||||
})
|
||||
desc = "用于后台提示,显示保存快照按钮等功能",
|
||||
onAuthClick = {
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
context.startActivity(intent)
|
||||
})
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
||||
if (gkdAccessRunning) {
|
||||
TextSwitch(name = "服务开启",
|
||||
desc = "保持服务开启,根据订阅规则匹配屏幕目标节点",
|
||||
checked = store.enableService,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
enableService = it
|
||||
)
|
||||
)
|
||||
})
|
||||
desc = "保持服务开启,根据订阅规则匹配屏幕目标节点",
|
||||
checked = store.enableService,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
enableService = it
|
||||
)
|
||||
)
|
||||
})
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
|
|
@ -7,24 +7,20 @@ import android.media.projection.MediaProjectionManager
|
|||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -263,46 +259,41 @@ fun DebugPage() {
|
|||
var value by remember {
|
||||
mutableStateOf(store.httpServerPort.toString())
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp)
|
||||
) {
|
||||
Text(text = "请输入新端口", style = MaterialTheme.typography.h6)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
AlertDialog(title = { Text(text = "请输入新端口") }, text = {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = { value = it.trim() },
|
||||
onValueChange = {
|
||||
value = it.trim().let { s -> if (s.length > 5) s.substring(0..4) else s }
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = { showPortDlg = false }) {
|
||||
Text(
|
||||
text = "取消", modifier = Modifier
|
||||
)
|
||||
}, onDismissRequest = { showPortDlg = false }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val newPort = value.toIntOrNull()
|
||||
if (newPort == null || !(5000 <= newPort && newPort <= 65535)) {
|
||||
ToastUtils.showShort("请输入在 5000~65535 的任意数字")
|
||||
return@TextButton
|
||||
}
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
TextButton(onClick = {
|
||||
val newPort = value.toIntOrNull()
|
||||
if (newPort == null || !(5000 <= newPort && newPort <= 65535)) {
|
||||
ToastUtils.showShort("请输入在 5000~65535 的任意数字")
|
||||
return@TextButton
|
||||
}
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
httpServerPort = newPort
|
||||
)
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
httpServerPort = newPort
|
||||
)
|
||||
showPortDlg = false
|
||||
}) {
|
||||
Text(
|
||||
text = "确认", modifier = Modifier
|
||||
)
|
||||
}
|
||||
)
|
||||
showPortDlg = false
|
||||
}) {
|
||||
Text(
|
||||
text = "确认", modifier = Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}, dismissButton = {
|
||||
TextButton(onClick = { showPortDlg = false }) {
|
||||
Text(
|
||||
text = "取消"
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +1,22 @@
|
|||
package li.songe.gkd.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.BottomNavigation
|
||||
import androidx.compose.material.BottomNavigationItem
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
import li.songe.gkd.util.getImportUrl
|
||||
|
||||
val BottomNavItems = listOf(
|
||||
subsNav, controlNav, settingsNav
|
||||
|
@ -39,39 +32,22 @@ data class BottomNavItem(
|
|||
@Destination(style = ProfileTransitions::class)
|
||||
@Composable
|
||||
fun HomePage() {
|
||||
val context = LocalContext.current as Activity
|
||||
val vm = hiltViewModel<HomePageVm>()
|
||||
val tab by vm.tabFlow.collectAsState()
|
||||
val intent by vm.intentFlow.collectAsState()
|
||||
LaunchedEffect(key1 = Unit, block = {
|
||||
vm.intentFlow.value = context.intent
|
||||
})
|
||||
LaunchedEffect(intent, block = {
|
||||
if (getImportUrl(intent) != null) {
|
||||
vm.tabFlow.value = subsNav
|
||||
}
|
||||
})
|
||||
|
||||
Scaffold(bottomBar = {
|
||||
BottomNavigation(
|
||||
backgroundColor = Color.Transparent, elevation = 0.dp
|
||||
) {
|
||||
NavigationBar {
|
||||
BottomNavItems.forEach { navItem ->
|
||||
BottomNavigationItem(selected = tab == navItem,
|
||||
modifier = Modifier.background(Color.Transparent),
|
||||
onClick = {
|
||||
vm.tabFlow.value = navItem
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(id = navItem.icon),
|
||||
contentDescription = navItem.label,
|
||||
modifier = Modifier.padding(2.dp)
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = navItem.label)
|
||||
})
|
||||
NavigationBarItem(selected = tab == navItem, modifier = Modifier, onClick = {
|
||||
vm.tabFlow.value = navItem
|
||||
}, icon = {
|
||||
Icon(
|
||||
painter = painterResource(id = navItem.icon),
|
||||
contentDescription = navItem.label
|
||||
)
|
||||
}, label = {
|
||||
Text(text = navItem.label)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, content = { padding ->
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package li.songe.gkd.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
|
@ -35,7 +34,6 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class HomePageVm @Inject constructor() : ViewModel() {
|
||||
val tabFlow = MutableStateFlow(controlNav)
|
||||
val intentFlow = MutableStateFlow<Intent?>(null)
|
||||
|
||||
init {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
|
|
|
@ -3,10 +3,8 @@ package li.songe.gkd.ui
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
@ -16,20 +14,22 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -38,7 +38,6 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
@ -74,6 +73,7 @@ val settingsNav = BottomNavItem(
|
|||
label = "设置", icon = SafeR.ic_cog, route = "settings"
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsPage() {
|
||||
val context = LocalContext.current as MainActivity
|
||||
|
@ -85,6 +85,9 @@ fun SettingsPage() {
|
|||
var showSubsIntervalDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var showDarkThemeDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var showToastInputDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
@ -96,9 +99,9 @@ fun SettingsPage() {
|
|||
val checkUpdating by checkUpdatingFlow.collectAsState()
|
||||
|
||||
Scaffold(topBar = {
|
||||
TopAppBar(backgroundColor = Color(0xfff8f9f9), title = {
|
||||
TopAppBar(title = {
|
||||
Text(
|
||||
text = "设置", color = Color.Black
|
||||
text = "设置"
|
||||
)
|
||||
})
|
||||
}, content = { contentPadding ->
|
||||
|
@ -112,33 +115,33 @@ fun SettingsPage() {
|
|||
) {
|
||||
|
||||
TextSwitch(name = "后台隐藏",
|
||||
desc = "在[最近任务]界面中隐藏本应用",
|
||||
checked = store.excludeFromRecents,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
excludeFromRecents = it
|
||||
)
|
||||
)
|
||||
})
|
||||
desc = "在[最近任务]界面中隐藏本应用",
|
||||
checked = store.excludeFromRecents,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
excludeFromRecents = it
|
||||
)
|
||||
)
|
||||
})
|
||||
Divider()
|
||||
|
||||
TextSwitch(name = "点击提示",
|
||||
desc = "触发点击时提示:[${store.clickToast}]",
|
||||
checked = store.toastWhenClick,
|
||||
modifier = Modifier.clickable {
|
||||
showToastInputDlg = true
|
||||
},
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
toastWhenClick = it
|
||||
)
|
||||
)
|
||||
if (!Settings.canDrawOverlays(context)) {
|
||||
ToastUtils.showShort("需要悬浮窗权限")
|
||||
}
|
||||
})
|
||||
desc = "触发点击时提示:[${store.clickToast}]",
|
||||
checked = store.toastWhenClick,
|
||||
modifier = Modifier.clickable {
|
||||
showToastInputDlg = true
|
||||
},
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
toastWhenClick = it
|
||||
)
|
||||
)
|
||||
if (it && !Settings.canDrawOverlays(context)) {
|
||||
ToastUtils.showShort("需要悬浮窗权限")
|
||||
}
|
||||
})
|
||||
Divider()
|
||||
|
||||
Row(modifier = Modifier
|
||||
|
@ -153,7 +156,7 @@ fun SettingsPage() {
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = radioOptions.find { it.second == store.updateSubsInterval }?.first
|
||||
text = updateTimeRadioOptions.find { it.second == store.updateSubsInterval }?.first
|
||||
?: store.updateSubsInterval.toString(), fontSize = 14.sp
|
||||
)
|
||||
Icon(
|
||||
|
@ -161,18 +164,18 @@ fun SettingsPage() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
TextSwitch(name = "自动更新应用",
|
||||
desc = "打开应用时自动检测是否存在新版本",
|
||||
checked = store.autoCheckAppUpdate,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
autoCheckAppUpdate = it
|
||||
)
|
||||
)
|
||||
})
|
||||
desc = "打开应用时自动检测是否存在新版本",
|
||||
checked = store.autoCheckAppUpdate,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
autoCheckAppUpdate = it
|
||||
)
|
||||
)
|
||||
})
|
||||
Divider()
|
||||
|
||||
SettingItem(title = if (checkUpdating) "检查更新ing" else "检查更新", onClick = {
|
||||
|
@ -185,6 +188,29 @@ fun SettingsPage() {
|
|||
}
|
||||
})
|
||||
Divider()
|
||||
|
||||
Row(modifier = Modifier
|
||||
.clickable {
|
||||
showDarkThemeDlg = true
|
||||
}
|
||||
.padding(10.dp, 15.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f), text = "深色模式", fontSize = 18.sp
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = darkThemeRadioOptions.find { it.second == store.enableDarkTheme }?.first
|
||||
?: store.enableDarkTheme.toString(), fontSize = 14.sp
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowRight, contentDescription = "more"
|
||||
)
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
|
||||
SettingItem(title = "问题反馈", onClick = {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
// ActivityNotFoundException
|
||||
|
@ -197,28 +223,30 @@ fun SettingsPage() {
|
|||
}
|
||||
})
|
||||
Divider()
|
||||
|
||||
TextSwitch(name = "保存日志",
|
||||
desc = "保存最近7天的日志",
|
||||
checked = store.log2FileSwitch,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
log2FileSwitch = it
|
||||
)
|
||||
)
|
||||
if (!it) {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val logFiles = LogUtils.getLogFiles()
|
||||
if (logFiles.isNotEmpty()) {
|
||||
logFiles.forEach { f ->
|
||||
f.delete()
|
||||
}
|
||||
ToastUtils.showShort("已删除全部日志")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
desc = "保存最近7天的日志",
|
||||
checked = store.log2FileSwitch,
|
||||
onCheckedChange = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
log2FileSwitch = it
|
||||
)
|
||||
)
|
||||
if (!it) {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val logFiles = LogUtils.getLogFiles()
|
||||
if (logFiles.isNotEmpty()) {
|
||||
logFiles.forEach { f ->
|
||||
f.delete()
|
||||
}
|
||||
ToastUtils.showShort("已删除全部日志")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Divider()
|
||||
|
||||
SettingItem(title = "分享日志", onClick = {
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logFiles = LogUtils.getLogFiles()
|
||||
|
@ -230,6 +258,7 @@ fun SettingsPage() {
|
|||
}
|
||||
})
|
||||
Divider()
|
||||
|
||||
SettingItem(title = "高级模式", onClick = {
|
||||
navController.navigate(DebugPageDestination)
|
||||
})
|
||||
|
@ -245,36 +274,81 @@ fun SettingsPage() {
|
|||
|
||||
if (showSubsIntervalDlg) {
|
||||
Dialog(onDismissRequest = { showSubsIntervalDlg = false }) {
|
||||
Column(
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
radioOptions.forEach { option ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(selected = (option.second == store.updateSubsInterval),
|
||||
Column {
|
||||
updateTimeRadioOptions.forEach { option ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(selected = (option.second == store.updateSubsInterval),
|
||||
onClick = {
|
||||
updateStorage(
|
||||
storeFlow,
|
||||
storeFlow.value.copy(updateSubsInterval = option.second)
|
||||
)
|
||||
})
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
selected = (option.second == store.updateSubsInterval),
|
||||
onClick = {
|
||||
updateStorage(
|
||||
storeFlow,
|
||||
storeFlow.value.copy(updateSubsInterval = option.second)
|
||||
)
|
||||
})
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
selected = (option.second == store.updateSubsInterval),
|
||||
onClick = {
|
||||
updateStorage(
|
||||
storeFlow,
|
||||
storeFlow.value.copy(updateSubsInterval = option.second)
|
||||
)
|
||||
})
|
||||
Text(
|
||||
text = option.first,
|
||||
style = MaterialTheme.typography.body1.merge(),
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
Text(
|
||||
text = option.first, modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showDarkThemeDlg) {
|
||||
Dialog(onDismissRequest = { showDarkThemeDlg = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column {
|
||||
darkThemeRadioOptions.forEach { option ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = (option.second == store.enableDarkTheme),
|
||||
onClick = {
|
||||
updateStorage(
|
||||
storeFlow,
|
||||
storeFlow.value.copy(enableDarkTheme = option.second)
|
||||
)
|
||||
})
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
selected = (option.second == store.enableDarkTheme),
|
||||
onClick = {
|
||||
updateStorage(
|
||||
storeFlow,
|
||||
storeFlow.value.copy(enableDarkTheme = option.second)
|
||||
)
|
||||
})
|
||||
Text(
|
||||
text = option.first, modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,37 +360,46 @@ fun SettingsPage() {
|
|||
var value by remember {
|
||||
mutableStateOf(store.clickToast)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp)
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(text = "请输入提示文字", style = MaterialTheme.typography.h6)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
singleLine = true,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp)
|
||||
) {
|
||||
TextButton(onClick = { showToastInputDlg = false }) {
|
||||
Text(
|
||||
text = "取消", modifier = Modifier
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
TextButton(onClick = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
clickToast = value
|
||||
Text(text = "请输入提示文字")
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
singleLine = true,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = { showToastInputDlg = false }) {
|
||||
Text(
|
||||
text = "取消", modifier = Modifier
|
||||
)
|
||||
)
|
||||
showToastInputDlg = false
|
||||
}) {
|
||||
Text(
|
||||
text = "确认", modifier = Modifier
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
TextButton(onClick = {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
clickToast = value
|
||||
)
|
||||
)
|
||||
showToastInputDlg = false
|
||||
}) {
|
||||
Text(
|
||||
text = "确认", modifier = Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,16 +408,16 @@ fun SettingsPage() {
|
|||
|
||||
if (showShareLogDlg) {
|
||||
Dialog(onDismissRequest = { showShareLogDlg = false }) {
|
||||
Box(
|
||||
Modifier
|
||||
.width(200.dp)
|
||||
.background(Color.White)
|
||||
.padding(8.dp)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "调用系统分享", modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
|
@ -399,35 +482,21 @@ fun SettingsPage() {
|
|||
}
|
||||
|
||||
is LoadStatus.Loading -> {
|
||||
Dialog(onDismissRequest = { }) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "上传文件中,请稍等",
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传文件中") },
|
||||
text = {
|
||||
LinearProgressIndicator(progress = uploadStatusVal.progress)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传", color = Color.Red)
|
||||
}
|
||||
},
|
||||
onDismissRequest = { },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
|
@ -454,10 +523,16 @@ fun SettingsPage() {
|
|||
}
|
||||
}
|
||||
|
||||
val radioOptions = listOf(
|
||||
private val updateTimeRadioOptions = listOf(
|
||||
"暂停" to -1L,
|
||||
"每小时" to 60 * 60_000L,
|
||||
"每6小时" to 6 * 60 * 60_000L,
|
||||
"每12小时" to 12 * 60 * 60_000L,
|
||||
"每天" to 24 * 60 * 60_000L
|
||||
)
|
||||
|
||||
private val darkThemeRadioOptions = listOf(
|
||||
"跟随系统" to null,
|
||||
"启用" to true,
|
||||
"关闭" to false,
|
||||
)
|
|
@ -5,12 +5,7 @@ import android.content.Intent
|
|||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
@ -22,16 +17,19 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -41,11 +39,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
@ -85,6 +81,7 @@ fun SnapshotPage() {
|
|||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current as ComponentActivity
|
||||
val navController = LocalNavController.current
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
val pickContentLauncher = LocalPickContentLauncher.current
|
||||
val requestPermissionLauncher = LocalRequestPermissionLauncher.current
|
||||
|
@ -101,7 +98,6 @@ fun SnapshotPage() {
|
|||
var showDeleteDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val scrollState = rememberLazyListState()
|
||||
|
||||
Scaffold(topBar = {
|
||||
SimpleTopAppBar(onClickIcon = { navController.popBackStack() },
|
||||
|
@ -120,22 +116,18 @@ fun SnapshotPage() {
|
|||
}, content = { contentPadding ->
|
||||
if (snapshots.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 0.dp, 10.dp, 0.dp)
|
||||
.padding(contentPadding),
|
||||
state = scrollState,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
) {
|
||||
items(snapshots, { it.id }) { snapshot ->
|
||||
Column(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(BorderStroke(1.dp, Color.Black))
|
||||
.clickable {
|
||||
selectedSnapshot = snapshot
|
||||
}) {
|
||||
}
|
||||
.padding(10.dp)) {
|
||||
Row {
|
||||
Text(
|
||||
text = snapshot.id.format("yyyy-MM-dd HH:mm:ss"),
|
||||
text = snapshot.id.format("MM-dd HH:mm:ss"),
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
@ -144,6 +136,7 @@ fun SnapshotPage() {
|
|||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(text = snapshot.activityId ?: "")
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
@ -162,16 +155,16 @@ fun SnapshotPage() {
|
|||
|
||||
selectedSnapshot?.let { snapshotVal ->
|
||||
Dialog(onDismissRequest = { selectedSnapshot = null }) {
|
||||
Box(
|
||||
Modifier
|
||||
.width(200.dp)
|
||||
.background(Color.White)
|
||||
.padding(8.dp)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "查看", modifier = Modifier
|
||||
.clickable(onClick = scope.launchAsFn {
|
||||
|
@ -184,6 +177,7 @@ fun SnapshotPage() {
|
|||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Divider()
|
||||
Text(
|
||||
text = "分享",
|
||||
modifier = Modifier
|
||||
|
@ -199,11 +193,16 @@ fun SnapshotPage() {
|
|||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
context.startActivity(Intent.createChooser(intent, "分享快照文件"))
|
||||
context.startActivity(
|
||||
Intent.createChooser(
|
||||
intent, "分享快照文件"
|
||||
)
|
||||
)
|
||||
selectedSnapshot = null
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Divider()
|
||||
if (recordStore.snapshotIdMap.containsKey(snapshotVal.id)) {
|
||||
Text(
|
||||
text = "复制链接", modifier = Modifier
|
||||
|
@ -224,6 +223,7 @@ fun SnapshotPage() {
|
|||
.then(modifier)
|
||||
)
|
||||
}
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = "保存截图到相册",
|
||||
|
@ -247,6 +247,7 @@ fun SnapshotPage() {
|
|||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Divider()
|
||||
Text(
|
||||
text = "替换截图(去除隐私)",
|
||||
modifier = Modifier
|
||||
|
@ -281,6 +282,7 @@ fun SnapshotPage() {
|
|||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Divider()
|
||||
Text(
|
||||
text = "删除", modifier = Modifier
|
||||
.clickable(onClick = scope.launchAsFn {
|
||||
|
@ -300,7 +302,7 @@ fun SnapshotPage() {
|
|||
}
|
||||
selectedSnapshot = null
|
||||
})
|
||||
.then(modifier)
|
||||
.then(modifier), color = colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -328,35 +330,21 @@ fun SnapshotPage() {
|
|||
}
|
||||
|
||||
is LoadStatus.Loading -> {
|
||||
Dialog(onDismissRequest = { }) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "上传文件中,请稍等",
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传文件中") },
|
||||
text = {
|
||||
LinearProgressIndicator(progress = uploadStatusVal.progress)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传", color = Color.Red)
|
||||
}
|
||||
},
|
||||
onDismissRequest = { },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
|
@ -386,17 +374,19 @@ fun SnapshotPage() {
|
|||
AlertDialog(onDismissRequest = { showDeleteDlg = false },
|
||||
title = { Text(text = "是否删除全部快照记录?") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = scope.launchAsFn(Dispatchers.IO) {
|
||||
showDeleteDlg = false
|
||||
snapshots.forEach { s ->
|
||||
SnapshotExt.removeAssets(s.id)
|
||||
}
|
||||
DbSet.snapshotDao.deleteAll()
|
||||
updateStorage(
|
||||
recordStoreFlow, recordStoreFlow.value.copy(snapshotIdMap = emptyMap())
|
||||
)
|
||||
}) {
|
||||
Text(text = "是", color = Color.Red)
|
||||
TextButton(
|
||||
onClick = scope.launchAsFn(Dispatchers.IO) {
|
||||
showDeleteDlg = false
|
||||
snapshots.forEach { s ->
|
||||
SnapshotExt.removeAssets(s.id)
|
||||
}
|
||||
DbSet.snapshotDao.deleteAll()
|
||||
updateStorage(
|
||||
recordStoreFlow, recordStoreFlow.value.copy(snapshotIdMap = emptyMap())
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(text = "是", color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
|
|
@ -2,42 +2,36 @@ package li.songe.gkd.ui
|
|||
|
||||
import android.webkit.URLUtil
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
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.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -49,7 +43,6 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
@ -66,7 +59,6 @@ import li.songe.gkd.ui.destinations.SubsPageDestination
|
|||
import li.songe.gkd.util.DEFAULT_SUBS_UPDATE_URL
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.SafeR
|
||||
import li.songe.gkd.util.getImportUrl
|
||||
import li.songe.gkd.util.launchAsFn
|
||||
import li.songe.gkd.util.navigate
|
||||
import li.songe.gkd.util.subsIdToRawFlow
|
||||
|
@ -80,26 +72,16 @@ val subsNav = BottomNavItem(
|
|||
label = "订阅", icon = SafeR.ic_link, route = "subscription"
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SubsManagePage() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val vm = hiltViewModel<SubsManageVm>()
|
||||
val homeVm = hiltViewModel<HomePageVm>()
|
||||
val subItems by subsItemsFlow.collectAsState()
|
||||
val subsIdToRaw by subsIdToRawFlow.collectAsState()
|
||||
|
||||
val intent by homeVm.intentFlow.collectAsState()
|
||||
LaunchedEffect(key1 = intent, block = {
|
||||
val importUrl = getImportUrl(intent)
|
||||
if (importUrl != null) {
|
||||
|
||||
homeVm.intentFlow.value = null
|
||||
}
|
||||
})
|
||||
|
||||
val orderSubItems = remember {
|
||||
mutableStateOf(subItems)
|
||||
}
|
||||
|
@ -141,9 +123,9 @@ fun SubsManagePage() {
|
|||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(backgroundColor = Color(0xfff8f9f9), title = {
|
||||
TopAppBar(title = {
|
||||
Text(
|
||||
text = "订阅", color = Color.Black
|
||||
text = "订阅",
|
||||
)
|
||||
})
|
||||
},
|
||||
|
@ -190,8 +172,6 @@ fun SubsManagePage() {
|
|||
.clickable {
|
||||
navController.navigate(SubsPageDestination(subItem.id))
|
||||
},
|
||||
elevation = 0.dp,
|
||||
border = BorderStroke(1.dp, Color(0xfff6f6f6)),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
) {
|
||||
SubsItemCard(
|
||||
|
@ -217,38 +197,34 @@ fun SubsManagePage() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
menuSubItem?.let { menuSubItemVal ->
|
||||
|
||||
Dialog(onDismissRequest = { menuSubItem = null }) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(200.dp)
|
||||
.wrapContentHeight()
|
||||
.background(Color.White)
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
if (menuSubItemVal.updateUrl != null) {
|
||||
Text(text = "复制链接", modifier = Modifier
|
||||
Column {
|
||||
if (menuSubItemVal.updateUrl != null) {
|
||||
Text(text = "复制链接", modifier = Modifier
|
||||
.clickable {
|
||||
menuSubItem = null
|
||||
ClipboardUtils.copyText(menuSubItemVal.updateUrl)
|
||||
ToastUtils.showShort("复制成功")
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
}
|
||||
Text(text = "删除订阅", modifier = Modifier
|
||||
.clickable {
|
||||
deleteSubItem = menuSubItemVal
|
||||
menuSubItem = null
|
||||
ClipboardUtils.copyText(menuSubItemVal.updateUrl)
|
||||
ToastUtils.showShort("复制成功")
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp))
|
||||
.padding(16.dp), color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
|
||||
Text(text = "删除订阅", color = Color.Red, modifier = Modifier
|
||||
.clickable {
|
||||
deleteSubItem = menuSubItemVal
|
||||
menuSubItem = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +238,7 @@ fun SubsManagePage() {
|
|||
deleteSubItem = null
|
||||
deleteSubItemVal.removeAssets()
|
||||
}) {
|
||||
Text(text = "是", color = Color.Red)
|
||||
Text(text = "是", color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
@ -276,28 +252,29 @@ fun SubsManagePage() {
|
|||
|
||||
if (showAddDialog) {
|
||||
Dialog(onDismissRequest = { showAddDialog = false }) {
|
||||
Column(
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(250.dp)
|
||||
.background(Color.White)
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(text = "导入默认订阅", modifier = Modifier
|
||||
.clickable {
|
||||
showAddDialog = false
|
||||
vm.addSubsFromUrl(DEFAULT_SUBS_UPDATE_URL)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp))
|
||||
Column {
|
||||
Text(text = "导入默认订阅", modifier = Modifier
|
||||
.clickable {
|
||||
showAddDialog = false
|
||||
vm.addSubsFromUrl(DEFAULT_SUBS_UPDATE_URL)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
|
||||
Text(text = "导入其它订阅", modifier = Modifier
|
||||
.clickable {
|
||||
showAddDialog = false
|
||||
showAddLinkDialog = true
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp))
|
||||
Text(text = "导入其它订阅", modifier = Modifier
|
||||
.clickable {
|
||||
showAddDialog = false
|
||||
showAddLinkDialog = true
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -310,42 +287,29 @@ fun SubsManagePage() {
|
|||
}
|
||||
}
|
||||
if (showAddLinkDialog) {
|
||||
Dialog(onDismissRequest = { showAddLinkDialog = false }) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.width(300.dp)
|
||||
.background(Color.White)
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Text(text = "请输入订阅链接", fontSize = 20.sp)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
OutlinedTextField(
|
||||
value = link,
|
||||
onValueChange = { link = it.trim() },
|
||||
maxLines = 2,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
if (!URLUtil.isNetworkUrl(link)) {
|
||||
ToastUtils.showShort("非法链接")
|
||||
return@TextButton
|
||||
}
|
||||
if (subItems.any { s -> s.updateUrl == link }) {
|
||||
ToastUtils.showShort("链接已存在")
|
||||
return@TextButton
|
||||
}
|
||||
showAddLinkDialog = false
|
||||
vm.addSubsFromUrl(url = link)
|
||||
}) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
AlertDialog(title = { Text(text = "请输入订阅链接") }, text = {
|
||||
OutlinedTextField(
|
||||
value = link,
|
||||
onValueChange = { link = it.trim() },
|
||||
maxLines = 2,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}, onDismissRequest = { showAddLinkDialog = false }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (!URLUtil.isNetworkUrl(link)) {
|
||||
ToastUtils.showShort("非法链接")
|
||||
return@TextButton
|
||||
}
|
||||
if (subItems.any { s -> s.updateUrl == link }) {
|
||||
ToastUtils.showShort("链接已存在")
|
||||
return@TextButton
|
||||
}
|
||||
showAddLinkDialog = false
|
||||
vm.addSubsFromUrl(url = link)
|
||||
}) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class SubsManageVm @Inject constructor() : ViewModel() {
|
||||
|
||||
fun addSubsFromUrl(url: String) = viewModelScope.launchTry {
|
||||
fun addSubsFromUrl(url: String) = viewModelScope.launchTry(Dispatchers.IO) {
|
||||
|
||||
if (refreshingFlow.value) return@launchTry
|
||||
if (!URLUtil.isNetworkUrl(url)) {
|
||||
|
|
|
@ -5,26 +5,26 @@ import android.net.Uri
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
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.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -34,11 +34,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -217,83 +215,74 @@ fun SubsPage(
|
|||
var source by remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
Dialog(onDismissRequest = { showAddDlg = false }) {
|
||||
Column(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 300.dp),
|
||||
) {
|
||||
Text(text = "添加APP规则", fontSize = 18.sp, modifier = Modifier.padding(10.dp))
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
maxLines = 8,
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则\n若APP规则已经存在则追加") },
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 10.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
val newAppRaw = try {
|
||||
SubscriptionRaw.parseAppRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则${e.message}")
|
||||
AlertDialog(title = { Text(text = "添加APP规则") }, text = {
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
maxLines = 8,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则\n若APP规则已经存在则追加") },
|
||||
)
|
||||
}, onDismissRequest = { showAddDlg = false }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val newAppRaw = try {
|
||||
SubscriptionRaw.parseAppRaw(source)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则${e.message}")
|
||||
return@TextButton
|
||||
}
|
||||
if (newAppRaw.groups.any { s -> s.name.isBlank() }) {
|
||||
ToastUtils.showShort("不允许添加空白名规则组,请先命名")
|
||||
return@TextButton
|
||||
}
|
||||
val oldAppRawIndex = subsRaw.apps.indexOfFirst { a -> a.id == newAppRaw.id }
|
||||
val oldAppRaw = subsRaw.apps.getOrNull(oldAppRawIndex)
|
||||
if (oldAppRaw != null) {
|
||||
// check same group name
|
||||
newAppRaw.groups.forEach { g ->
|
||||
if (oldAppRaw.groups.any { g0 -> g0.name == g.name }) {
|
||||
ToastUtils.showShort("已经存在同名规则[${g.name}]\n请修改名称后再添加")
|
||||
return@TextButton
|
||||
}
|
||||
if (newAppRaw.groups.any { s -> s.name.isBlank() }) {
|
||||
ToastUtils.showShort("不允许添加空白名规则组,请先命名")
|
||||
return@TextButton
|
||||
}
|
||||
val oldAppRawIndex = subsRaw.apps.indexOfFirst { a -> a.id == newAppRaw.id }
|
||||
val oldAppRaw = subsRaw.apps.getOrNull(oldAppRawIndex)
|
||||
if (oldAppRaw != null) {
|
||||
// check same group name
|
||||
newAppRaw.groups.forEach { g ->
|
||||
if (oldAppRaw.groups.any { g0 -> g0.name == g.name }) {
|
||||
ToastUtils.showShort("已经存在同名规则[${g.name}]\n请修改名称后再添加")
|
||||
return@TextButton
|
||||
}
|
||||
}
|
||||
}
|
||||
val newApps = if (oldAppRaw != null) {
|
||||
val oldKey = oldAppRaw.groups.maxBy { g -> g.key }.key + 1
|
||||
val finalAppRaw =
|
||||
newAppRaw.copy(groups = oldAppRaw.groups + newAppRaw.groups.mapIndexed { i, g ->
|
||||
g.copy(
|
||||
key = oldKey + i
|
||||
)
|
||||
})
|
||||
subsRaw.apps.toMutableList().apply {
|
||||
set(oldAppRawIndex, finalAppRaw)
|
||||
}
|
||||
} else {
|
||||
subsRaw.apps.toMutableList().apply {
|
||||
add(newAppRaw)
|
||||
}
|
||||
}
|
||||
vm.viewModelScope.launchTry {
|
||||
subsItemVal.subsFile.writeText(
|
||||
SubscriptionRaw.stringify(
|
||||
subsRaw.copy(
|
||||
apps = newApps, version = subsRaw.version + 1
|
||||
)
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
showAddDlg = false
|
||||
ToastUtils.showShort("添加成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
}
|
||||
val newApps = if (oldAppRaw != null) {
|
||||
val oldKey = oldAppRaw.groups.maxBy { g -> g.key }.key + 1
|
||||
val finalAppRaw =
|
||||
newAppRaw.copy(groups = oldAppRaw.groups + newAppRaw.groups.mapIndexed { i, g ->
|
||||
g.copy(
|
||||
key = oldKey + i
|
||||
)
|
||||
})
|
||||
subsRaw.apps.toMutableList().apply {
|
||||
set(oldAppRawIndex, finalAppRaw)
|
||||
}
|
||||
} else {
|
||||
subsRaw.apps.toMutableList().apply {
|
||||
add(newAppRaw)
|
||||
}
|
||||
}
|
||||
vm.viewModelScope.launchTry {
|
||||
subsItemVal.subsFile.writeText(
|
||||
SubscriptionRaw.stringify(
|
||||
subsRaw.copy(
|
||||
apps = newApps, version = subsRaw.version + 1
|
||||
)
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
showAddDlg = false
|
||||
ToastUtils.showShort("添加成功")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
}
|
||||
}, dismissButton = {
|
||||
TextButton(onClick = { showAddDlg = false }) {
|
||||
Text(text = "取消")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val editAppRawVal = editAppRaw
|
||||
|
@ -301,100 +290,92 @@ fun SubsPage(
|
|||
var source by remember {
|
||||
mutableStateOf(Singleton.json.encodeToString(editAppRawVal))
|
||||
}
|
||||
Dialog(onDismissRequest = { editAppRaw = null }) {
|
||||
Column(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 300.dp),
|
||||
) {
|
||||
Text(text = "编辑本地APP规则", fontSize = 18.sp, modifier = Modifier.padding(10.dp))
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
maxLines = 8,
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则") },
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 10.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
val newAppRaw = SubscriptionRaw.parseAppRaw(source)
|
||||
if (newAppRaw.id != editAppRawVal.id) {
|
||||
ToastUtils.showShort("不允许修改规则id")
|
||||
return@TextButton
|
||||
}
|
||||
val oldAppRawIndex =
|
||||
subsRaw.apps.indexOfFirst { a -> a.id == editAppRawVal.id }
|
||||
vm.viewModelScope.launchTry {
|
||||
subsItemVal.subsFile.writeText(
|
||||
SubscriptionRaw.stringify(
|
||||
subsRaw.copy(
|
||||
apps = subsRaw.apps.toMutableList().apply {
|
||||
set(oldAppRawIndex, newAppRaw)
|
||||
}, version = subsRaw.version + 1
|
||||
)
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
editAppRaw = null
|
||||
ToastUtils.showShort("更新成功")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则${e.message}")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
AlertDialog(title = { Text(text = "编辑本地APP规则") }, text = {
|
||||
OutlinedTextField(
|
||||
value = source,
|
||||
onValueChange = { source = it },
|
||||
maxLines = 8,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text(text = "请输入规则") },
|
||||
)
|
||||
}, onDismissRequest = { editAppRaw = null }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
val newAppRaw = SubscriptionRaw.parseAppRaw(source)
|
||||
if (newAppRaw.id != editAppRawVal.id) {
|
||||
ToastUtils.showShort("不允许修改规则id")
|
||||
return@TextButton
|
||||
}
|
||||
val oldAppRawIndex = subsRaw.apps.indexOfFirst { a -> a.id == editAppRawVal.id }
|
||||
vm.viewModelScope.launchTry {
|
||||
subsItemVal.subsFile.writeText(
|
||||
SubscriptionRaw.stringify(
|
||||
subsRaw.copy(
|
||||
apps = subsRaw.apps.toMutableList().apply {
|
||||
set(oldAppRawIndex, newAppRaw)
|
||||
}, version = subsRaw.version + 1
|
||||
)
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
editAppRaw = null
|
||||
ToastUtils.showShort("更新成功")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d(e)
|
||||
ToastUtils.showShort("非法规则${e.message}")
|
||||
}
|
||||
}, enabled = source.isNotEmpty()) {
|
||||
Text(text = "添加")
|
||||
}
|
||||
}
|
||||
}, dismissButton = {
|
||||
TextButton(onClick = { editAppRaw = null }) {
|
||||
Text(text = "取消")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
val menuAppRawVal = menuAppRaw
|
||||
if (menuAppRawVal != null && subsItemVal != null && subsRaw != null) {
|
||||
Dialog(onDismissRequest = { menuAppRaw = null }) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.width(200.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(text = "复制", modifier = Modifier
|
||||
.clickable {
|
||||
ClipboardUtils.copyText(
|
||||
Singleton.json.encodeToString(
|
||||
menuAppRawVal
|
||||
)
|
||||
)
|
||||
ToastUtils.showShort("复制成功")
|
||||
menuAppRaw = null
|
||||
}
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth())
|
||||
Text(text = "删除", color = Color.Red, modifier = Modifier
|
||||
.clickable {
|
||||
// 也许需要二次确认
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(
|
||||
Column {
|
||||
Text(text = "复制", modifier = Modifier
|
||||
.clickable {
|
||||
ClipboardUtils.copyText(
|
||||
Singleton.json.encodeToString(
|
||||
subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id })
|
||||
menuAppRawVal
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id)
|
||||
ToastUtils.showShort("删除成功")
|
||||
ToastUtils.showShort("复制成功")
|
||||
menuAppRaw = null
|
||||
}
|
||||
menuAppRaw = null
|
||||
}
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth())
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
Text(text = "删除", modifier = Modifier
|
||||
.clickable {
|
||||
// 也许需要二次确认
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
subsItemVal.subsFile.writeText(
|
||||
Singleton.json.encodeToString(
|
||||
subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id })
|
||||
)
|
||||
)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id)
|
||||
ToastUtils.showShort("删除成功")
|
||||
}
|
||||
menuAppRaw = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp), color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.OutlinedButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
|
@ -2,23 +2,20 @@ package li.songe.gkd.ui.component
|
|||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import li.songe.gkd.icon.ArrowIcon
|
||||
|
||||
@Composable
|
||||
fun SettingItem(
|
||||
|
@ -40,10 +37,4 @@ fun SettingItem(
|
|||
Text(text = title, fontSize = 18.sp)
|
||||
Icon(imageVector = imageVector, contentDescription = title)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewSettingItem() {
|
||||
SettingItem(title = "你好", onClick = {})
|
||||
}
|
|
@ -1,49 +1,34 @@
|
|||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import li.songe.gkd.util.SafeR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SimpleTopAppBar(
|
||||
@DrawableRes iconId: Int = SafeR.ic_back,
|
||||
onClickIcon: (() -> Unit)? = null,
|
||||
actions: @Composable() (RowScope.() -> Unit) = {},
|
||||
actions: @Composable (RowScope.() -> Unit) = {},
|
||||
title: String,
|
||||
) {
|
||||
TopAppBar(backgroundColor = Color(0xfff8f9f9), navigationIcon = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
onClickIcon?.invoke()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(30.dp)
|
||||
)
|
||||
}
|
||||
TopAppBar(navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
onClickIcon?.invoke()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(30.dp),
|
||||
)
|
||||
}
|
||||
}, title = { Text(text = title) }, actions = actions
|
||||
)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SnapshotCard() {
|
||||
Row {
|
||||
Text(text = "06-02 20:47:48")
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(text = "酷安")
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(text = "查看")
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(text = "分享")
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(text = "删除")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewSnapshotCard() {
|
||||
SnapshotCard()
|
||||
}
|
|
@ -1,23 +1,20 @@
|
|||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.blankj.utilcode.util.BarUtils
|
||||
import com.blankj.utilcode.util.ConvertUtils
|
||||
|
||||
@Composable
|
||||
fun StatusBar(color: Color = Color.Transparent) {
|
||||
fun StatusBar() {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(statusBarHeight)
|
||||
.fillMaxWidth()
|
||||
.background(color)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
|
@ -10,31 +10,23 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Switch
|
||||
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.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import li.songe.gkd.data.SubsItem
|
||||
import li.songe.gkd.data.SubscriptionRaw
|
||||
import li.songe.gkd.util.formatTimeAgo
|
||||
import li.songe.gkd.util.safeRemoteBaseUrls
|
||||
|
||||
val safeRemoteBaseUrls = arrayOf(
|
||||
"https://registry.npmmirror.com/@gkd-kit/",
|
||||
"https://cdn.jsdelivr.net/npm/@gkd-kit/",
|
||||
"https://unpkg.com/@gkd-kit/",
|
||||
"https://github.com/gkd-kit/",
|
||||
"https://raw.githubusercontent.com/gkd-kit/"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SubsItemCard(
|
||||
|
@ -133,17 +125,3 @@ fun SubsItemCard(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewSubscriptionItemCard() {
|
||||
Surface(modifier = Modifier.width(400.dp)) {
|
||||
SubsItemCard(
|
||||
SubsItem(
|
||||
id = 0,
|
||||
order = 1,
|
||||
updateUrl = "https://registry.npmmirror.com/@gkd-kit/subscription/latest/files",
|
||||
), subscriptionRaw = null, index = 1
|
||||
)
|
||||
}
|
||||
}
|
|
@ -6,12 +6,11 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
|
@ -42,11 +41,3 @@ fun TextSwitch(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTextSwitch() {
|
||||
TextSwitch(
|
||||
name = "隐藏后台", desc = "在最近任务列表中隐藏", checked = true
|
||||
)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun UnDialog(
|
||||
title: String? = null,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package li.songe.gkd.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple200 = Color(0xFFf8f9f9)
|
||||
val Purple500 = Color(0xFFf2f3f4)
|
||||
val Purple700 = Color(0xFFe5e7e9)
|
||||
val Teal200 = Color(0xFF03DAC5)
|
|
@ -1,11 +0,0 @@
|
|||
package li.songe.gkd.ui.theme
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp)
|
||||
)
|
|
@ -1,24 +1,32 @@
|
|||
package li.songe.gkd.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import li.songe.gkd.util.map
|
||||
import li.songe.gkd.util.storeFlow
|
||||
|
||||
private val darkColorPalette = darkColors()
|
||||
|
||||
private val lightColorPalette = lightColors()
|
||||
val LightColorScheme = lightColorScheme()
|
||||
val DarkColorScheme = darkColorScheme()
|
||||
|
||||
@Composable
|
||||
fun AppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
|
||||
val colors = if (darkTheme) {
|
||||
darkColorPalette
|
||||
fun AppTheme(
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val enableDarkTheme by storeFlow.map(scope) { s -> s.enableDarkTheme }.collectAsState()
|
||||
val colorScheme = if (enableDarkTheme ?: useDarkTheme) {
|
||||
DarkColorScheme
|
||||
} else {
|
||||
lightColorPalette
|
||||
LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colors = colors, typography = Typography, shapes = Shapes, content = content
|
||||
colorScheme = colorScheme, content = content
|
||||
)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package li.songe.gkd.ui.theme
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
body1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
button = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.W500,
|
||||
fontSize = 14.sp
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
*/
|
||||
)
|
|
@ -6,3 +6,11 @@ const val FILE_UPLOAD_URL = "https://github-upload-assets.lisonge.workers.dev/"
|
|||
|
||||
const val DEFAULT_SUBS_UPDATE_URL =
|
||||
"https://registry.npmmirror.com/@gkd-kit/subscription/latest/files"
|
||||
|
||||
val safeRemoteBaseUrls = arrayOf(
|
||||
"https://registry.npmmirror.com/@gkd-kit/",
|
||||
"https://cdn.jsdelivr.net/npm/@gkd-kit/",
|
||||
"https://unpkg.com/@gkd-kit/",
|
||||
"https://github.com/gkd-kit/",
|
||||
"https://raw.githubusercontent.com/gkd-kit/"
|
||||
)
|
|
@ -1,78 +0,0 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import android.graphics.Path
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.RectShape
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
fun createDrawable(block: () -> Unit) {
|
||||
val s = materialIcon(name = "xx") {
|
||||
addPath(addPathNodes(""))
|
||||
}
|
||||
}
|
||||
|
||||
fun createVectorDrawable(block: () -> Unit) {
|
||||
val path = Path().apply {
|
||||
addPathNodes("").forEach {
|
||||
it.isQuad
|
||||
}
|
||||
}
|
||||
val shapeDrawable = ShapeDrawable(RectShape())
|
||||
shapeDrawable.apply {
|
||||
|
||||
addPathNodes("")[0].isCurve
|
||||
}
|
||||
// val r = Resources()
|
||||
// Drawable.createFromXml()
|
||||
}
|
||||
|
||||
|
||||
val x = createDrawable {
|
||||
// val p =
|
||||
vector {
|
||||
width = 24.dp
|
||||
height = 24.dp
|
||||
viewportWidth = 24F
|
||||
viewportHeight = 24F
|
||||
path {
|
||||
width
|
||||
fillColor = Color(0xFF000000)
|
||||
pathData = "M20,11H7.83l5.59,-5.59L12,4l-8,8l8,8l1.41,-1.41L7.83,13H20v-2z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface VectorType {
|
||||
var width: Dp
|
||||
var height: Dp
|
||||
var viewportWidth: Float
|
||||
var viewportHeight: Float
|
||||
}
|
||||
|
||||
fun vector(block: VectorType.() -> Unit) {}
|
||||
|
||||
interface PathType {
|
||||
var fillColor: Color
|
||||
var pathData: String
|
||||
}
|
||||
|
||||
fun path(block: PathType.() -> Unit) {}
|
||||
|
||||
fun testDrawable() {
|
||||
|
||||
val s2 = vector {
|
||||
width = 24.dp
|
||||
height = 24.dp
|
||||
viewportWidth = 24F
|
||||
viewportHeight = 24F
|
||||
path {
|
||||
width
|
||||
fillColor = Color(0xFF000000)
|
||||
pathData = "M20,11H7.83l5.59,-5.59L12,4l-8,8l8,8l1.41,-1.41L7.83,13H20v-2z"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -86,6 +86,7 @@ data class Store(
|
|||
val hideSnapshotStatusBar: Boolean = false,
|
||||
val enableShizuku: Boolean = false,
|
||||
val log2FileSwitch: Boolean = true,
|
||||
val enableDarkTheme: Boolean? = null,
|
||||
)
|
||||
|
||||
val storeFlow by lazy {
|
||||
|
|
|
@ -2,26 +2,19 @@ package li.songe.gkd.util
|
|||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.blankj.utilcode.util.AppUtils
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.onDownload
|
||||
|
@ -116,7 +109,14 @@ fun UpgradeDialog() {
|
|||
AlertDialog(title = {
|
||||
Text(text = "检测到新版本")
|
||||
}, text = {
|
||||
Text(text = "v${BuildConfig.VERSION_NAME} -> v${newVersionVal.versionName}\n\n${newVersionVal.changelog}".trimEnd())
|
||||
Text(
|
||||
text = "v${BuildConfig.VERSION_NAME} -> v${newVersionVal.versionName}\n\n${newVersionVal.changelog}\n${newVersionVal.changelog}".trimEnd(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 400.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
)
|
||||
|
||||
}, onDismissRequest = { }, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
newVersionFlow.value = null
|
||||
|
@ -135,42 +135,27 @@ fun UpgradeDialog() {
|
|||
downloadStatus?.let { downloadStatusVal ->
|
||||
when (downloadStatusVal) {
|
||||
is LoadStatus.Loading -> {
|
||||
Dialog(onDismissRequest = { }) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "下载新版本中,稍等片刻",
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
AlertDialog(
|
||||
title = { Text(text = "下载新版本中") },
|
||||
text = {
|
||||
LinearProgressIndicator(progress = downloadStatusVal.progress)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
downloadStatusFlow.value = LoadStatus.Failure(
|
||||
Exception("终止下载")
|
||||
)
|
||||
}) {
|
||||
Text(text = "终止下载", color = Color.Red)
|
||||
}
|
||||
},
|
||||
onDismissRequest = {},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
downloadStatusFlow.value = LoadStatus.Failure(
|
||||
Exception("终止下载")
|
||||
)
|
||||
}) {
|
||||
Text(text = "终止下载")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Failure -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "安装包下载失败") },
|
||||
title = { Text(text = "新版本下载失败") },
|
||||
text = {
|
||||
Text(text = downloadStatusVal.exception.let {
|
||||
it.message ?: it.toString()
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="AppTheme" parent="android:Theme.Material.NoActionBar"></style>
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="SplashScreenTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenAnimatedIcon">
|
||||
@drawable/ic_launcher_circle
|
||||
</item>
|
||||
<item name="windowSplashScreenBackground">@android:color/white</item>
|
||||
<item name="windowSplashScreenAnimationDuration">1000</item>
|
||||
<!-- postSplashScreenTheme must invoke installSplashScreen -->
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -1,17 +1,11 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light.NoActionBar"></style>
|
||||
|
||||
<style name="SplashScreenTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenAnimatedIcon">
|
||||
@drawable/ic_launcher_circle
|
||||
</item>
|
||||
<item name="windowSplashScreenBackground">@android:color/white</item>
|
||||
<item name="windowSplashScreenAnimationDuration">1000</item>
|
||||
<!-- postSplashScreenTheme must invoke installSplashScreen -->
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
|
|
|
@ -57,6 +57,7 @@ dependencyResolutionManagement {
|
|||
version("compose.compilerVersion", "1.5.3")
|
||||
library("compose.ui", "androidx.compose.ui:ui:1.5.1")
|
||||
library("compose.material", "androidx.compose.material:material:1.5.1")
|
||||
library("compose.material3", "androidx.compose.material3:material3:1.1.2")
|
||||
library("compose.preview", "androidx.compose.ui:ui-tooling-preview:1.5.1")
|
||||
library("compose.tooling", "androidx.compose.ui:ui-tooling:1.5.1")
|
||||
library("compose.junit4", "androidx.compose.ui:ui-test-junit4:1.5.1")
|
||||
|
|
Loading…
Reference in New Issue
Block a user