From caf01685bd6a359ddbe39da812ab532707997c64 Mon Sep 17 00:00:00 2001 From: office-pc nix root Date: Fri, 31 Jan 2025 14:54:20 +0800 Subject: [PATCH] add settings to force the TV ui, can be useful for some tv boxes fix #304 --- lib/l10n/app_en.arb | 4 ++- lib/settings/models/db/settings.dart | 1 + lib/settings/states/settings.dart | 5 +++ lib/settings/views/screens/appearance.dart | 7 ++++ lib/settings/views/tv/screens/settings.dart | 19 +++++++---- lib/utils.dart | 5 ++- lib/videos/models/video.dart | 3 +- lib/videos/models/video.freezed.dart | 36 ++++++++++++++++----- lib/videos/models/video.g.dart | 2 ++ 9 files changed, 64 insertions(+), 18 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1914ba19..50a9e8de 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1383,5 +1383,7 @@ "premieresIn": "Premieres in {formattedDuration}", "screenControls": "Screen controls", "screenControlsExplanation": "When watching a video in full screen, Vertically dragging from the left or the right will adjust the brightness or volume respectively", - "retry": "Retry" + "retry": "Retry", + "forceTvUi": "Force TV interface", + "forceTvUiExplanation": "Force the interface to be the TV experience, can be useful for some devices that do not have the leanback system config. App restart required" } diff --git a/lib/settings/models/db/settings.dart b/lib/settings/models/db/settings.dart index 2fca4547..ba4fbcbd 100644 --- a/lib/settings/models/db/settings.dart +++ b/lib/settings/models/db/settings.dart @@ -41,6 +41,7 @@ const dearrowSettingName = 'dearrow'; const dearrowThumbnailsSettingName = "dearrow-thumbnails"; const fullScreenOnLandscapeSettingName = "fullscreen-on-landscape"; const screenControlsSettingName = "screen-controls"; +const forceTvUiSettingName = "force-tv-ui"; const onOpenSettingName = "on-open"; diff --git a/lib/settings/states/settings.dart b/lib/settings/states/settings.dart index cc925efb..611a6a5b 100644 --- a/lib/settings/states/settings.dart +++ b/lib/settings/states/settings.dart @@ -327,6 +327,8 @@ class SettingsCubit extends Cubit { setFillFullscreen(bool b) async => await _set(fillFullScreen, b); + setForceTvUi(bool b) async => await _set(forceTvUiSettingName, b); + _setLocale(String? s) async { await _set(localeSettingName, s); await fileDb.setLocale(s); @@ -563,6 +565,9 @@ class SettingsState with _$SettingsState { bool get screenControls => (_get(screenControlsSettingName)?.value ?? 'true') == 'true'; + bool get forceTvUi => + (_get(forceTvUiSettingName)?.value ?? 'false') == 'true'; + List get appLayout { var savedLayout = _get(appLayoutSettingName)?.value; // String? savedLayout; diff --git a/lib/settings/views/screens/appearance.dart b/lib/settings/views/screens/appearance.dart index f96fe58f..83aab07a 100644 --- a/lib/settings/views/screens/appearance.dart +++ b/lib/settings/views/screens/appearance.dart @@ -115,6 +115,13 @@ class AppearanceSettingsScreen extends StatelessWidget { context, state.navigationBarLabelBehavior)), onPressed: (ctx) => customizeNavigationLabel(ctx), ), + SettingsTile.switchTile( + leading: const Icon(Icons.tv), + initialValue: state.forceTvUi, + onToggle: cubit.setForceTvUi, + title: Text(locals.forceTvUi), + description: Text(locals.forceTvUiExplanation), + ), ], ), ], diff --git a/lib/settings/views/tv/screens/settings.dart b/lib/settings/views/tv/screens/settings.dart index 123b4866..44d72145 100644 --- a/lib/settings/views/tv/screens/settings.dart +++ b/lib/settings/views/tv/screens/settings.dart @@ -1,14 +1,14 @@ import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:clipious/app/states/app.dart'; import 'package:clipious/extensions.dart'; import 'package:clipious/router.dart'; import 'package:clipious/utils.dart'; import 'package:clipious/utils/views/tv/components/tv_button.dart'; import 'package:clipious/utils/views/tv/components/tv_overscan.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:locale_names/locale_names.dart'; import 'package:logging/logging.dart'; @@ -340,6 +340,13 @@ class TVSettingsScreen extends StatelessWidget { trailing: Switch( onChanged: (value) {}, value: state.blackBackground), ), + SettingsTile( + title: locals.forceTvUi, + description: locals.forceTvUiExplanation, + onSelected: (context) => cubit.setForceTvUi(!state.forceTvUi), + trailing: + Switch(onChanged: (value) {}, value: state.forceTvUi), + ), SettingsTitle(title: locals.about), SettingsTile( title: '${locals.name}: ${state.packageInfo.appName}', @@ -505,7 +512,7 @@ class SettingsTile extends StatelessWidget { enabled: enabled ?? true, leading: leading, trailing: trailing, - selectedTileColor: colors.secondaryContainer.withOpacity(0.5), + selectedTileColor: colors.secondaryContainer.withValues(alpha: 0.5), selected: hasFocus, title: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -515,7 +522,7 @@ class SettingsTile extends StatelessWidget { style: textTheme.headlineSmall!.copyWith( color: enabled ?? true ? colors.primary - : colors.primary.withOpacity(0.5)), + : colors.primary.withValues(alpha: 0.5)), ), description != null ? Text( diff --git a/lib/utils.dart b/lib/utils.dart index b27be2b5..c2957a5b 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:clipious/settings/models/db/settings.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -203,9 +204,11 @@ DeviceType getDeviceType() { } Future isDeviceTv() async { + final forceTv = db.getSettings(forceTvUiSettingName); DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - return androidInfo.systemFeatures.contains('android.software.leanback'); + return forceTv?.value == 'true' || + androidInfo.systemFeatures.contains('android.software.leanback'); } int getGridCount(BuildContext context) { diff --git a/lib/videos/models/video.dart b/lib/videos/models/video.dart index 9535bf18..04decabe 100644 --- a/lib/videos/models/video.dart +++ b/lib/videos/models/video.dart @@ -43,8 +43,7 @@ class Video with _$Video implements ShareLinks, IdedVideo { const factory Video( {required String videoId, int? viewCount, - // not used in the code and causes issues - // @JsonKey(fromJson: _parsePublished) int? published, + @JsonKey(fromJson: _parsePublished) int? published, int? index, String? indexId, String? publishedText, diff --git a/lib/videos/models/video.freezed.dart b/lib/videos/models/video.freezed.dart index 69a2f711..7af5a1fc 100644 --- a/lib/videos/models/video.freezed.dart +++ b/lib/videos/models/video.freezed.dart @@ -21,9 +21,9 @@ Video _$VideoFromJson(Map json) { /// @nodoc mixin _$Video { String get videoId => throw _privateConstructorUsedError; - int? get viewCount => - throw _privateConstructorUsedError; // not used in the code and causes issues -// @JsonKey(fromJson: _parsePublished) int? published, + int? get viewCount => throw _privateConstructorUsedError; + @JsonKey(fromJson: _parsePublished) + int? get published => throw _privateConstructorUsedError; int? get index => throw _privateConstructorUsedError; String? get indexId => throw _privateConstructorUsedError; String? get publishedText => throw _privateConstructorUsedError; @@ -88,6 +88,7 @@ abstract class $VideoCopyWith<$Res> { $Res call( {String videoId, int? viewCount, + @JsonKey(fromJson: _parsePublished) int? published, int? index, String? indexId, String? publishedText, @@ -149,6 +150,7 @@ class _$VideoCopyWithImpl<$Res, $Val extends Video> $Res call({ Object? videoId = null, Object? viewCount = freezed, + Object? published = freezed, Object? index = freezed, Object? indexId = freezed, Object? publishedText = freezed, @@ -199,6 +201,10 @@ class _$VideoCopyWithImpl<$Res, $Val extends Video> ? _value.viewCount : viewCount // ignore: cast_nullable_to_non_nullable as int?, + published: freezed == published + ? _value.published + : published // ignore: cast_nullable_to_non_nullable + as int?, index: freezed == index ? _value.index : index // ignore: cast_nullable_to_non_nullable @@ -373,6 +379,7 @@ abstract class _$$VideoImplCopyWith<$Res> implements $VideoCopyWith<$Res> { $Res call( {String videoId, int? viewCount, + @JsonKey(fromJson: _parsePublished) int? published, int? index, String? indexId, String? publishedText, @@ -432,6 +439,7 @@ class __$$VideoImplCopyWithImpl<$Res> $Res call({ Object? videoId = null, Object? viewCount = freezed, + Object? published = freezed, Object? index = freezed, Object? indexId = freezed, Object? publishedText = freezed, @@ -482,6 +490,10 @@ class __$$VideoImplCopyWithImpl<$Res> ? _value.viewCount : viewCount // ignore: cast_nullable_to_non_nullable as int?, + published: freezed == published + ? _value.published + : published // ignore: cast_nullable_to_non_nullable + as int?, index: freezed == index ? _value.index : index // ignore: cast_nullable_to_non_nullable @@ -652,6 +664,7 @@ class _$VideoImpl extends _Video { const _$VideoImpl( {required this.videoId, this.viewCount, + @JsonKey(fromJson: _parsePublished) this.published, this.index, this.indexId, this.publishedText, @@ -715,8 +728,9 @@ class _$VideoImpl extends _Video { final String videoId; @override final int? viewCount; -// not used in the code and causes issues -// @JsonKey(fromJson: _parsePublished) int? published, + @override + @JsonKey(fromJson: _parsePublished) + final int? published; @override final int? index; @override @@ -872,7 +886,7 @@ class _$VideoImpl extends _Video { @override String toString() { - return 'Video(videoId: $videoId, viewCount: $viewCount, index: $index, indexId: $indexId, publishedText: $publishedText, isUpcoming: $isUpcoming, premiereTimestamp: $premiereTimestamp, dashUrl: $dashUrl, description: $description, descriptionHtml: $descriptionHtml, keywords: $keywords, likeCount: $likeCount, dislikeCount: $dislikeCount, paid: $paid, premium: $premium, isFamilyFriendly: $isFamilyFriendly, allowedRegions: $allowedRegions, genre: $genre, genreUrl: $genreUrl, authorThumbnails: $authorThumbnails, subCountText: $subCountText, allowRatings: $allowRatings, rating: $rating, isListed: $isListed, liveNow: $liveNow, hlsUrl: $hlsUrl, adaptiveFormats: $adaptiveFormats, formatStreams: $formatStreams, captions: $captions, recommendedVideos: $recommendedVideos, title: $title, lengthSeconds: $lengthSeconds, author: $author, authorId: $authorId, authorUrl: $authorUrl, videoThumbnails: $videoThumbnails, filtered: $filtered, matchedFilters: $matchedFilters, filterHide: $filterHide, deArrowed: $deArrowed, deArrowThumbnailUrl: $deArrowThumbnailUrl, viewCountText: $viewCountText)'; + return 'Video(videoId: $videoId, viewCount: $viewCount, published: $published, index: $index, indexId: $indexId, publishedText: $publishedText, isUpcoming: $isUpcoming, premiereTimestamp: $premiereTimestamp, dashUrl: $dashUrl, description: $description, descriptionHtml: $descriptionHtml, keywords: $keywords, likeCount: $likeCount, dislikeCount: $dislikeCount, paid: $paid, premium: $premium, isFamilyFriendly: $isFamilyFriendly, allowedRegions: $allowedRegions, genre: $genre, genreUrl: $genreUrl, authorThumbnails: $authorThumbnails, subCountText: $subCountText, allowRatings: $allowRatings, rating: $rating, isListed: $isListed, liveNow: $liveNow, hlsUrl: $hlsUrl, adaptiveFormats: $adaptiveFormats, formatStreams: $formatStreams, captions: $captions, recommendedVideos: $recommendedVideos, title: $title, lengthSeconds: $lengthSeconds, author: $author, authorId: $authorId, authorUrl: $authorUrl, videoThumbnails: $videoThumbnails, filtered: $filtered, matchedFilters: $matchedFilters, filterHide: $filterHide, deArrowed: $deArrowed, deArrowThumbnailUrl: $deArrowThumbnailUrl, viewCountText: $viewCountText)'; } @override @@ -883,6 +897,8 @@ class _$VideoImpl extends _Video { (identical(other.videoId, videoId) || other.videoId == videoId) && (identical(other.viewCount, viewCount) || other.viewCount == viewCount) && + (identical(other.published, published) || + other.published == published) && (identical(other.index, index) || other.index == index) && (identical(other.indexId, indexId) || other.indexId == indexId) && (identical(other.publishedText, publishedText) || @@ -958,6 +974,7 @@ class _$VideoImpl extends _Video { runtimeType, videoId, viewCount, + published, index, indexId, publishedText, @@ -1020,6 +1037,7 @@ abstract class _Video extends Video implements ShareLinks, IdedVideo { const factory _Video( {required final String videoId, final int? viewCount, + @JsonKey(fromJson: _parsePublished) final int? published, final int? index, final String? indexId, final String? publishedText, @@ -1072,8 +1090,10 @@ abstract class _Video extends Video implements ShareLinks, IdedVideo { @override String get videoId; @override - int? get viewCount; // not used in the code and causes issues -// @JsonKey(fromJson: _parsePublished) int? published, + int? get viewCount; + @override + @JsonKey(fromJson: _parsePublished) + int? get published; @override int? get index; @override diff --git a/lib/videos/models/video.g.dart b/lib/videos/models/video.g.dart index 36d1b66f..3e72f982 100644 --- a/lib/videos/models/video.g.dart +++ b/lib/videos/models/video.g.dart @@ -9,6 +9,7 @@ part of 'video.dart'; _$VideoImpl _$$VideoImplFromJson(Map json) => _$VideoImpl( videoId: json['videoId'] as String, viewCount: (json['viewCount'] as num?)?.toInt(), + published: _parsePublished(json['published']), index: (json['index'] as num?)?.toInt(), indexId: json['indexId'] as String?, publishedText: json['publishedText'] as String?, @@ -72,6 +73,7 @@ Map _$$VideoImplToJson(_$VideoImpl instance) => { 'videoId': instance.videoId, 'viewCount': instance.viewCount, + 'published': instance.published, 'index': instance.index, 'indexId': instance.indexId, 'publishedText': instance.publishedText,