feat: Add seek overlay to native player

This commit is contained in:
PartyDonut 2025-10-14 18:26:17 +02:00
parent cfd4b4a5cc
commit 660298c083
2 changed files with 80 additions and 34 deletions

View file

@ -0,0 +1,80 @@
package nl.jknaapen.fladder.composables.controls
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.github.rabehx.iconsax.Iconsax
import io.github.rabehx.iconsax.outline.Refresh
import nl.jknaapen.fladder.utility.visible
import kotlin.math.absoluteValue
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@Composable
internal fun BoxScope.SeekOverlay(
modifier: Modifier = Modifier,
value: Long = 0L,
) {
val infiniteTransition = rememberInfiniteTransition()
val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 6000,
easing = FastOutLinearInEasing,
),
)
)
Box(
modifier = modifier
.align(Alignment.Center)
.visible(value != 0L)
.wrapContentSize()
.background(
color = Color.Black.copy(alpha = 0.85f),
shape = CircleShape
)
.padding(12.dp),
contentAlignment = Alignment.Center,
) {
Icon(
Iconsax.Outline.Refresh,
modifier = Modifier
.size(65.dp)
.scale(scaleX = if (value < 0) 1f else -1f, scaleY = 1f)
.rotate(-rotation),
contentDescription = "SkipLogoRotating",
tint = Color.White,
)
Text(
value.absoluteValue.toDuration(DurationUnit.MILLISECONDS).inWholeSeconds.toString(),
modifier = Modifier
.align(alignment = Alignment.Center),
color = Color.White
)
}
}

View file

@ -1,17 +1,11 @@
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:markdown_widget/widget/markdown.dart';
import 'package:path_provider/path_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/update_provider.dart';
import 'package:fladder/screens/settings/settings_list_tile.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/media/external_urls.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
@ -93,8 +87,6 @@ class UpdateInformation extends StatelessWidget {
@override
Widget build(BuildContext context) {
final apkDownload =
releaseInfo.preferredDownloads.entries.where((entry) => entry.value.toLowerCase().endsWith('.apk')).firstOrNull;
return ExpansionTile(
backgroundColor:
releaseInfo.isNewerThanCurrent ? context.colors.primaryContainer : context.colors.surfaceContainer,
@ -115,32 +107,6 @@ class UpdateInformation extends StatelessWidget {
),
),
),
if (apkDownload != null)
FilledButton(
onPressed: () async {
try {
final response = await http.get(Uri.parse(apkDownload.value));
final tempDir = await getTemporaryDirectory();
final apkPath = '${tempDir.path}/update.apk';
if (response.statusCode == 200) {
final file = File(apkPath);
await file.writeAsBytes(response.bodyBytes);
launchUrl(context, file.path);
} else {
throw Exception('Failed to download APK: ${response.statusCode}');
}
} catch (e) {
if (context.mounted) {
fladderSnackbar(context, title: 'Failed to download update: $e');
}
}
},
child: const Text(
"Install",
),
),
...releaseInfo.preferredDownloads.entries.map(
(entry) {
return FilledButton(