chore: Implement translation callbacks to flutter

This commit is contained in:
PartyDonut 2025-10-17 18:43:00 +02:00
parent e902e2034a
commit 29917bb59f
16 changed files with 681 additions and 43 deletions

View file

@ -3,6 +3,7 @@ package nl.jknaapen.fladder
import NativeVideoActivity import NativeVideoActivity
import PlayerSettingsPigeon import PlayerSettingsPigeon
import StartResult import StartResult
import TranslationsPigeon
import VideoPlayerApi import VideoPlayerApi
import VideoPlayerControlsCallback import VideoPlayerControlsCallback
import VideoPlayerListenerCallback import VideoPlayerListenerCallback
@ -12,6 +13,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import com.ryanheise.audioservice.AudioServiceFragmentActivity import com.ryanheise.audioservice.AudioServiceFragmentActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import nl.jknaapen.fladder.objects.PlayerSettingsObject import nl.jknaapen.fladder.objects.PlayerSettingsObject
import nl.jknaapen.fladder.objects.TranslationsMessenger
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.leanBackEnabled import nl.jknaapen.fladder.utility.leanBackEnabled
@ -37,6 +39,9 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
videoPlayerHost.videoPlayerControls = videoPlayerHost.videoPlayerControls =
VideoPlayerControlsCallback(flutterEngine.dartExecutor.binaryMessenger) VideoPlayerControlsCallback(flutterEngine.dartExecutor.binaryMessenger)
TranslationsMessenger.translation =
TranslationsPigeon(flutterEngine.dartExecutor.binaryMessenger)
PlayerSettingsPigeon.setUp( PlayerSettingsPigeon.setUp(
flutterEngine.dartExecutor.binaryMessenger, flutterEngine.dartExecutor.binaryMessenger,
api = PlayerSettingsObject api = PlayerSettingsObject
@ -54,9 +59,9 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
StartResult(resultValue = "Cancelled") StartResult(resultValue = "Cancelled")
} }
callback?.invoke(Result.success(startResult))
VideoPlayerObject.implementation.player?.stop() VideoPlayerObject.implementation.player?.stop()
VideoPlayerObject.implementation.player?.release() VideoPlayerObject.implementation.player?.release()
callback?.invoke(Result.success(startResult))
} }
} }

View file

@ -0,0 +1,217 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object TranslationsPigeonPigeonUtils {
fun createConnectionError(channelName: String): FlutterError {
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "") }
}
private open class TranslationsPigeonPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
class TranslationsPigeon(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
companion object {
/** The codec used by TranslationsPigeon. */
val codec: MessageCodec<Any?> by lazy {
TranslationsPigeonPigeonCodec()
}
}
fun next(callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.next$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun nextVideo(callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextVideo$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun close(callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.close$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun skip(nameArg: String, callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.skip$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(nameArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun subtitles(callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.subtitles$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun off(callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.off$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun chapters(countArg: Long, callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.chapters$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(countArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun nextUpInSeconds(secondsArg: Long, callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextUpInSeconds$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(secondsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
fun endsAt(timeArg: String, callback: (Result<String>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.endsAt$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(timeArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")))
} else {
val output = it[0] as String
callback(Result.success(output))
}
} else {
callback(Result.failure(TranslationsPigeonPigeonUtils.createConnectionError(channelName)))
}
}
}
}

View file

@ -66,6 +66,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastCoerceIn
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.capitalize import nl.jknaapen.fladder.utility.capitalize
import nl.jknaapen.fladder.utility.formatTime import nl.jknaapen.fladder.utility.formatTime
@ -122,12 +124,14 @@ internal fun ProgressBar(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Translate({ Localized.endsAt(endTimeString ?: "", it) }, endTimeString) { translation ->
val progressBarTopLabel = listOf( val progressBarTopLabel = listOf(
playableData?.currentItem?.subTitle, playableData?.currentItem?.subTitle,
endTimeString, translation,
) )
val label = progressBarTopLabel.joinToString(separator = " - ") val label = progressBarTopLabel.filterNot { it.isNullOrBlank() }
.joinToString(separator = " - ")
if (label.isNotBlank()) { if (label.isNotBlank()) {
Text( Text(
text = label, text = label,
@ -137,6 +141,7 @@ internal fun ProgressBar(
), ),
) )
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))

View file

@ -34,7 +34,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.PlayerSettingsObject import nl.jknaapen.fladder.objects.PlayerSettingsObject
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.defaultSelected import nl.jknaapen.fladder.utility.defaultSelected
import nl.jknaapen.fladder.utility.leanBackEnabled import nl.jknaapen.fladder.utility.leanBackEnabled
@ -135,9 +137,15 @@ internal fun BoxScope.SegmentSkipOverlay(
} }
} }
} }
activeSegment?.let { activeSegment?.let { segment ->
Translate({ cb ->
Localized.skip(
segment.name.lowercase(),
cb
)
}) { translation ->
Text( Text(
"Skip ${it.name.lowercase()}", translation,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold) style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
) )
} }
@ -145,6 +153,7 @@ internal fun BoxScope.SegmentSkipOverlay(
} }
} }
} }
}
private val MediaSegmentType.toSegment: SegmentType private val MediaSegmentType.toSegment: SegmentType
get() = when (this) { get() = when (this) {

View file

@ -17,6 +17,8 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.clearAudioTrack import nl.jknaapen.fladder.utility.clearAudioTrack
import nl.jknaapen.fladder.utility.setInternalAudioTrack import nl.jknaapen.fladder.utility.setInternalAudioTrack
@ -82,7 +84,9 @@ fun AudioPicker(
}, },
selected = selectedOff selected = selectedOff
) { ) {
Text("Off") Translate(Localized::off) {
Text(it)
}
} }
} }

View file

@ -34,6 +34,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.highlightOnFocus import nl.jknaapen.fladder.utility.highlightOnFocus
@ -78,11 +80,14 @@ internal fun ChapterSelectionSheet(
.wrapContentHeight(), .wrapContentHeight(),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Translate({ Localized.chapters(chapters.size.toLong(), it) }) {
Text( Text(
"Chapters", it,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = Color.White color = Color.White
) )
}
LazyRow( LazyRow(
state = lazyListState, state = lazyListState,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)

View file

@ -18,6 +18,8 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.clearSubtitleTrack import nl.jknaapen.fladder.utility.clearSubtitleTrack
import nl.jknaapen.fladder.utility.setInternalSubtitleTrack import nl.jknaapen.fladder.utility.setInternalSubtitleTrack
@ -79,9 +81,9 @@ fun SubtitlePicker(
}, },
selected = selectedOff selected = selectedOff
) { ) {
Text( Translate(Localized::off) {
text = "Off", Text(it)
) }
} }
} }
internalSubTracks.forEachIndexed { index, subtitle -> internalSubTracks.forEachIndexed { index, subtitle ->

View file

@ -45,7 +45,9 @@ import io.github.rabehx.iconsax.filled.Next
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import nl.jknaapen.fladder.composables.controls.CustomButton import nl.jknaapen.fladder.composables.controls.CustomButton
import nl.jknaapen.fladder.objects.Localized
import nl.jknaapen.fladder.objects.PlayerSettingsObject import nl.jknaapen.fladder.objects.PlayerSettingsObject
import nl.jknaapen.fladder.objects.Translate
import nl.jknaapen.fladder.objects.VideoPlayerObject import nl.jknaapen.fladder.objects.VideoPlayerObject
import nl.jknaapen.fladder.utility.conditional import nl.jknaapen.fladder.utility.conditional
import nl.jknaapen.fladder.utility.highlightOnFocus import nl.jknaapen.fladder.utility.highlightOnFocus
@ -168,14 +170,25 @@ internal fun NextUpOverlay(
Column( Column(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Translate(
{
Localized.nextUpInSeconds(
timeUntilNextVideo.toLong(),
callback = it
)
},
key = timeUntilNextVideo,
) { ) {
Text( Text(
"Next-up in $timeUntilNextVideo seconds", it,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )
}
Box( Box(
modifier = Modifier modifier = Modifier
.align(alignment = Alignment.CenterHorizontally) .align(alignment = Alignment.CenterHorizontally)
@ -199,7 +212,9 @@ internal fun NextUpOverlay(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text("Next") Translate(Localized::next) { value ->
Text(value)
}
Icon(Iconsax.Filled.Next, contentDescription = "Play next video") Icon(Iconsax.Filled.Next, contentDescription = "Play next video")
} }
} }
@ -213,7 +228,9 @@ internal fun NextUpOverlay(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text("Close") Translate(Localized::close) {
Text(it)
}
Icon(Iconsax.Filled.CloseCircle, contentDescription = "Close Icon") Icon(Iconsax.Filled.CloseCircle, contentDescription = "Close Icon")
} }
} }

View file

@ -0,0 +1,33 @@
package nl.jknaapen.fladder.objects
import TranslationsPigeon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
val Localized
get() = TranslationsMessenger.translation
internal object TranslationsMessenger {
lateinit var translation: TranslationsPigeon
}
@Composable
internal fun Translate(
callback: ((Result<String>) -> Unit) -> Unit,
key: Any? = Unit,
content: @Composable (String) -> Unit
) {
var value by remember { mutableStateOf<String?>(null) }
LaunchedEffect(key) {
callback { result ->
value = result.getOrNull()
}
}
content(value.orEmpty())
}

View file

@ -14,7 +14,6 @@ import nl.jknaapen.fladder.VideoPlayerActivity
import nl.jknaapen.fladder.messengers.VideoPlayerImplementation import nl.jknaapen.fladder.messengers.VideoPlayerImplementation
import nl.jknaapen.fladder.utility.InternalTrack import nl.jknaapen.fladder.utility.InternalTrack
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.toJavaInstant import kotlin.time.toJavaInstant
@ -43,9 +42,8 @@ object VideoPlayerObject {
val remainingMs = (dur - pos).coerceAtLeast(0L) val remainingMs = (dur - pos).coerceAtLeast(0L)
val endInstant = startInstant.toJavaInstant().plusMillis(remainingMs) val endInstant = startInstant.toJavaInstant().plusMillis(remainingMs)
val endZoned = endInstant.atZone(zone) val endZoned = endInstant.atZone(zone)
val formatter = DateTimeFormatter.ofPattern("hh:mm a")
"ends at ${endZoned.format(formatter)}" endZoned.toOffsetDateTime().toString()
} }
val currentSubtitleTrackIndex = val currentSubtitleTrackIndex =

View file

@ -1348,5 +1348,13 @@
"mediaTunnelingTitle": "Media tunneling", "mediaTunnelingTitle": "Media tunneling",
"mediaTunnelingDesc": "Enable media tunneling for native player", "mediaTunnelingDesc": "Enable media tunneling for native player",
"clientSettingsUseSystemIMETitle": "Use system keyboard", "clientSettingsUseSystemIMETitle": "Use system keyboard",
"clientSettingsUseSystemIMEDesc": "Use the built-in keyboard provided by your system" "clientSettingsUseSystemIMEDesc": "Use the built-in keyboard provided by your system",
"nextUpInCount": "Next-up in {seconds}",
"@nextUpInCount": {
"placeholders": {
"seconds": {
"type": "int"
}
}
}
} }

View file

@ -0,0 +1,262 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
if (empty) {
return <Object?>[];
}
if (error == null) {
return <Object?>[result];
}
return <Object?>[error.code, error.message, error.details];
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
default:
return super.readValueOfType(type, buffer);
}
}
}
abstract class TranslationsPigeon {
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
String next();
String nextVideo();
String close();
String skip(String name);
String subtitles();
String off();
String chapters(int count);
String nextUpInSeconds(int seconds);
String endsAt(String time);
static void setUp(TranslationsPigeon? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.next$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
try {
final String output = api.next();
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextVideo$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
try {
final String output = api.nextVideo();
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.close$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
try {
final String output = api.close();
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.skip$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.skip was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_name = (args[0] as String?);
assert(arg_name != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.skip was null, expected non-null String.');
try {
final String output = api.skip(arg_name!);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.subtitles$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
try {
final String output = api.subtitles();
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.off$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
try {
final String output = api.off();
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.chapters$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.chapters was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_count = (args[0] as int?);
assert(arg_count != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.chapters was null, expected non-null int.');
try {
final String output = api.chapters(arg_count!);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextUpInSeconds$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextUpInSeconds was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_seconds = (args[0] as int?);
assert(arg_seconds != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.nextUpInSeconds was null, expected non-null int.');
try {
final String output = api.nextUpInSeconds(arg_seconds!);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.endsAt$messageChannelSuffix', pigeonChannelCodec,
binaryMessenger: binaryMessenger);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.endsAt was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_time = (args[0] as String?);
assert(arg_time != null,
'Argument for dev.flutter.pigeon.nl_jknaapen_fladder.settings.TranslationsPigeon.endsAt was null, expected non-null String.');
try {
final String output = api.endsAt(arg_time!);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
}
}

View file

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -100,6 +102,7 @@ Future<void> _playVideo(
} }
if (context.mounted) { if (context.mounted) {
log("Finished refreshing");
await context.refreshData(); await context.refreshData();
} }

View file

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/l10n/generated/app_localizations.dart'; import 'package:fladder/l10n/generated/app_localizations.dart';
import 'package:fladder/providers/sync/background_download_provider.dart'; import 'package:fladder/providers/sync/background_download_provider.dart';
import 'package:fladder/src/translations_pigeon.g.dart' as messenger;
///Only use for base translations, under normal circumstances ALWAYS use the widgets provided context ///Only use for base translations, under normal circumstances ALWAYS use the widgets provided context
final localizationContextProvider = StateProvider<BuildContext?>((ref) => null); final localizationContextProvider = StateProvider<BuildContext?>((ref) => null);
@ -26,6 +27,7 @@ class LocalizationContextWrapper extends ConsumerStatefulWidget {
} }
class _LocalizationContextWrapperState extends ConsumerState<LocalizationContextWrapper> { class _LocalizationContextWrapperState extends ConsumerState<LocalizationContextWrapper> {
_TranslationsMessgener? _messenger;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -41,6 +43,11 @@ class _LocalizationContextWrapperState extends ConsumerState<LocalizationContext
} }
void updateLanguageContext() { void updateLanguageContext() {
if (_messenger == null) {
_messenger = _TranslationsMessgener(context: context);
messenger.TranslationsPigeon.setUp(_messenger);
}
WidgetsBinding.instance.addPostFrameCallback((value) { WidgetsBinding.instance.addPostFrameCallback((value) {
ref.read(localizationContextProvider.notifier).update((cb) => context); ref.read(localizationContextProvider.notifier).update((cb) => context);
ref.read(backgroundDownloaderProvider.notifier).updateTranslations(context); ref.read(backgroundDownloaderProvider.notifier).updateTranslations(context);
@ -58,3 +65,36 @@ extension LocaleDisplayCodeExtension on Locale {
: languageCode.toUpperCase(); : languageCode.toUpperCase();
} }
} }
class _TranslationsMessgener extends messenger.TranslationsPigeon {
_TranslationsMessgener({required this.context});
final BuildContext context;
@override
String chapters(int count) => context.localized.chapter(count);
@override
String close() => context.localized.close;
@override
String endsAt(String time) => context.localized.endsAt(DateTime.parse(time));
@override
String next() => context.localized.nextVideo;
@override
String nextUpInSeconds(int seconds) => context.localized.nextUpInCount(seconds);
@override
String nextVideo() => context.localized.nextVideo;
@override
String off() => context.localized.off;
@override
String skip(String name) => context.localized.skipButtonLabel(name);
@override
String subtitles() => context.localized.subtitles;
}

View file

@ -34,9 +34,9 @@ class NativePlayer extends BasePlayer implements VideoPlayerListenerCallback {
Future<void> loadVideo(String url, bool play) async => player.open(url, play); Future<void> loadVideo(String url, bool play) async => player.open(url, play);
@override @override
Future<void> open(BuildContext newContext) async { Future<StartResult> open(BuildContext newContext) async {
nativeActivityStarted = true; nativeActivityStarted = true;
NativeVideoActivity().launchActivity(); return NativeVideoActivity().launchActivity();
} }
@override @override

View file

@ -0,0 +1,30 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/translations_pigeon.g.dart',
dartOptions: DartOptions(),
kotlinOut: 'android/app/src/main/kotlin/nl/jknaapen/fladder/api/TranslationsPigeon.g.kt',
kotlinOptions: KotlinOptions(
includeErrorClass: false,
),
dartPackageName: 'nl_jknaapen_fladder.settings',
),
)
@FlutterApi()
abstract class TranslationsPigeon {
String next();
String nextVideo();
String close();
String skip(String name);
String subtitles();
String off();
String chapters(int count);
String nextUpInSeconds(int seconds);
String endsAt(String time);
}