Merge branch 'bugfix/fix-small-bugs' into develop
8
.vscode/tasks.json
vendored
|
|
@ -71,15 +71,13 @@
|
|||
"detail": ""
|
||||
},
|
||||
{
|
||||
"type": "flutter",
|
||||
"type": "dart",
|
||||
"command": "dart",
|
||||
"args": [
|
||||
"run",
|
||||
"flutter_launcher_icons"
|
||||
"icons_launcher:create"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"label": "flutter: flutter create launcher icons",
|
||||
"label": "dart: generate launcher icons",
|
||||
"detail": ""
|
||||
}
|
||||
],
|
||||
|
|
|
|||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 897 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
icons/fladder_macos_icon.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
icons/fladder_notification_icon.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
icons/macos_icon.afphoto
Normal file
|
|
@ -5,19 +5,20 @@ icons_launcher:
|
|||
adaptive_foreground_image: "icons/fladder_icon.png"
|
||||
adaptive_background_color: "#3a2101"
|
||||
adaptive_monochrome_image: "icons/fladder_adaptive_icon.png"
|
||||
notification_image: icons/fladder_notification_icon.png
|
||||
enable: true
|
||||
ios:
|
||||
image_path: "icons/fladder_icon.png"
|
||||
image_path: "icons/fladder_store_icon.jpg"
|
||||
enable: true
|
||||
windows:
|
||||
image_path: "icons/fladder_icon_desktop.png"
|
||||
enable: true
|
||||
macos:
|
||||
image_path: "icons/fladder_icon_desktop.png"
|
||||
image_path: "icons/fladder_macos_icon.png"
|
||||
enable: true
|
||||
linux:
|
||||
image_path: "icons/fladder_icon_desktop.png"
|
||||
enable: true
|
||||
web:
|
||||
favicon_path: "icons/fladder_icon.png"
|
||||
favicon_path: "icons/fladder_icon_desktop.png"
|
||||
enable: true
|
||||
|
|
|
|||
|
|
@ -2,117 +2,115 @@
|
|||
"images": [
|
||||
{
|
||||
"filename": "Icon-App-20x20@2x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
"size": "20x20",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@3x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@1x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
"size": "20x20",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@2x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
"size": "29x29",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@3x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
"size": "29x29",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-38x38@2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "38x38",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-38x38@3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "38x38",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@2x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
"size": "40x40",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@3x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "40x40"
|
||||
"size": "40x40",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-60x60@2x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
"size": "60x60",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-60x60@3x.png",
|
||||
"idiom": "iphone",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
"size": "60x60",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-20x20@2x.png",
|
||||
"idiom": "ipad",
|
||||
"filename": "Icon-App-64x64@2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
"size": "64x64",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
"filename": "Icon-App-64x64@3x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"size": "64x64",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-29x29@2x.png",
|
||||
"idiom": "ipad",
|
||||
"filename": "Icon-App-68x68@2x.png",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-40x40@2x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-76x76@1x.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "76x76"
|
||||
"size": "68x68",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-76x76@2x.png",
|
||||
"idiom": "ipad",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "76x76"
|
||||
"size": "76x76",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-83.5x83.5@2x.png",
|
||||
"idiom": "ipad",
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"size": "83.5x83.5"
|
||||
"size": "83.5x83.5",
|
||||
"platform": "ios"
|
||||
},
|
||||
{
|
||||
"filename": "Icon-App-1024x1024@1x.png",
|
||||
"idiom": "ios-marketing",
|
||||
"idiom": "universal",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
"size": "1024x1024",
|
||||
"platform": "ios"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 648 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 967 B After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 13 KiB |
|
|
@ -1,14 +1,16 @@
|
|||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/util/application_info.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/util/application_info.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
void main() async {
|
||||
_setupLogging();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -22,8 +24,14 @@ void main() async {
|
|||
ProviderScope(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
||||
applicationInfoProvider.overrideWith((ref) => ApplicationInfo(
|
||||
name: packageInfo.appName, version: packageInfo.version, os: defaultTargetPlatform.name.capitalize())),
|
||||
applicationInfoProvider.overrideWith(
|
||||
(ref) => ApplicationInfo(
|
||||
name: packageInfo.appName,
|
||||
buildNumber: packageInfo.buildNumber,
|
||||
version: packageInfo.version,
|
||||
os: defaultTargetPlatform.name.capitalize(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const Main(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -133,6 +133,16 @@
|
|||
"editMetadata": "Edit metadata",
|
||||
"empty": "Empty",
|
||||
"enabled": "Enabled",
|
||||
"endsAt": "ends at {date}",
|
||||
"@endsAt": {
|
||||
"description": "endsAt",
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "DateTime",
|
||||
"format": "jm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{Episodes} one{Episode} }",
|
||||
"@episode": {
|
||||
"description": "episode",
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"editMetadata": "Editar metadatos",
|
||||
"empty": "Vacío",
|
||||
"enabled": "Habilitado",
|
||||
"endsAt": "termina el {date}",
|
||||
"episode": "{count, plural, other{Episodios} one{Episodio}}",
|
||||
"@episode": {
|
||||
"description": "episodio",
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"editMetadata": "Modifier les métadonnées",
|
||||
"empty": "Vide",
|
||||
"enabled": "Activé",
|
||||
"endsAt": "se termine à {date}",
|
||||
"episode": "{count, plural, other{Épisodes} one{Épisode}}",
|
||||
"@episode": {
|
||||
"description": "épisode",
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"editMetadata": "メタデータを編集",
|
||||
"empty": "空",
|
||||
"enabled": "有効",
|
||||
"endsAt": "{date}に終了",
|
||||
"episode": "{count, plural, other{エピソード} one{エピソード}}",
|
||||
"@episode": {
|
||||
"description": "エピソード",
|
||||
|
|
@ -273,7 +274,7 @@
|
|||
"metadataRefreshDefault": "新しいファイルと更新されたファイルをスキャン",
|
||||
"metadataRefreshFull": "すべてのメタデータを置き換える",
|
||||
"metadataRefreshValidation": "欠落しているメタデータを検索",
|
||||
"minutes": "分",
|
||||
"minutes": "{count}分",
|
||||
"@minutes": {
|
||||
"description": "分",
|
||||
"placeholders": {
|
||||
|
|
@ -466,7 +467,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"seconds": "秒",
|
||||
"seconds": "{count}秒",
|
||||
"@seconds": {
|
||||
"description": "秒",
|
||||
"placeholders": {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"editMetadata": "Metadata bewerken",
|
||||
"empty": "Leeg",
|
||||
"enabled": "Ingeschakeld",
|
||||
"endsAt": "eindigt om {date}",
|
||||
"episode": "{count, plural, other{Afleveringen} one{Aflevering}}",
|
||||
"@episode": {
|
||||
"description": "aflevering",
|
||||
|
|
@ -364,7 +365,7 @@
|
|||
}
|
||||
},
|
||||
"playLabel": "Afspelen",
|
||||
"playVideos": "Video's afspelen",
|
||||
"playVideos": "Video''s afspelen",
|
||||
"played": "Gespeeld",
|
||||
"quickConnectAction": "Voer snelverbind code in voor",
|
||||
"quickConnectInputACode": "Voer een code in",
|
||||
|
|
@ -543,14 +544,14 @@
|
|||
"showDetails": "Toon details",
|
||||
"showEmpty": "Toon leeg",
|
||||
"shuffleGallery": "Galerij shuffle",
|
||||
"shuffleVideos": "Video's shuffle",
|
||||
"shuffleVideos": "Video''s shuffle",
|
||||
"somethingWentWrong": "Er is iets misgegaan",
|
||||
"somethingWentWrongPasswordCheck": "Er is iets misgegaan, controleer uw wachtwoord",
|
||||
"sortBy": "Sorteer op",
|
||||
"sortName": "Naam",
|
||||
"sortOrder": "Sorteervolgorde",
|
||||
"start": "Start",
|
||||
"studio": "{count, plural, other{Studio's} one{Studio}}",
|
||||
"studio": "{count, plural, other{Studio''s} one{Studio}}",
|
||||
"@studio": {
|
||||
"description": "studio",
|
||||
"placeholders": {
|
||||
|
|
@ -644,7 +645,7 @@
|
|||
"videoScalingFitHeight": "Pas hoogte aan",
|
||||
"videoScalingFitWidth": "Pas breedte aan",
|
||||
"videoScalingScaleDown": "Schaal omlaag",
|
||||
"viewPhotos": "Foto's bekijken",
|
||||
"viewPhotos": "Foto''s bekijken",
|
||||
"watchOn": "Kijk op",
|
||||
"writer": "{count, plural, other{Schrijvers} one{Schrijver}}",
|
||||
"@writer": {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"editMetadata": "编辑元数据",
|
||||
"empty": "空",
|
||||
"enabled": "启用",
|
||||
"endsAt": "结束于 {date}",
|
||||
"episode": "{count, plural, other{集} one{集}}",
|
||||
"@episode": {
|
||||
"description": "集",
|
||||
|
|
@ -273,7 +274,7 @@
|
|||
"metadataRefreshDefault": "扫描新文件和更新的文件",
|
||||
"metadataRefreshFull": "替换所有元数据",
|
||||
"metadataRefreshValidation": "搜索缺失的元数据",
|
||||
"minutes": "分钟",
|
||||
"minutes": "{count}分钟",
|
||||
"@minutes": {
|
||||
"description": "分钟",
|
||||
"placeholders": {
|
||||
|
|
@ -466,7 +467,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"seconds": "秒",
|
||||
"seconds": "{count}秒",
|
||||
"@seconds": {
|
||||
"description": "秒",
|
||||
"placeholders": {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ void main() async {
|
|||
|
||||
final applicationInfo = ApplicationInfo(
|
||||
name: packageInfo.appName.capitalize(),
|
||||
version: "${packageInfo.version}(${packageInfo.buildNumber})",
|
||||
version: packageInfo.version,
|
||||
buildNumber: packageInfo.buildNumber,
|
||||
os: !kIsWeb ? defaultTargetPlatform.name.capitalize() : "${defaultTargetPlatform.name.capitalize()} Web",
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/models/boxset_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/playlist_model.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/details_screens/book_detail_screen.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/models/boxset_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/folder_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/models/items/person_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/playlist_model.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/details_screens/book_detail_screen.dart';
|
||||
import 'package:fladder/screens/details_screens/details_screens.dart';
|
||||
import 'package:fladder/screens/details_screens/episode_detail_screen.dart';
|
||||
import 'package:fladder/screens/details_screens/season_detail_screen.dart';
|
||||
import 'package:fladder/screens/library_search/library_search_screen.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
part 'item_base_model.mapper.dart';
|
||||
|
||||
|
|
@ -62,8 +63,6 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
required this.jellyType,
|
||||
});
|
||||
|
||||
String get title => name;
|
||||
|
||||
ItemBaseModel? setProgress(double progress) {
|
||||
return copyWith(userData: userData.copyWith(progress: progress));
|
||||
}
|
||||
|
|
@ -98,6 +97,8 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
_ => null,
|
||||
};
|
||||
|
||||
String get title => name;
|
||||
|
||||
///Used for retrieving the correct id when fetching queue
|
||||
String get streamId => id;
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
|
||||
bool get unWatched => !userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0;
|
||||
|
||||
String? detailedName(BuildContext context) => null;
|
||||
String? detailedName(BuildContext context) => "$name${overview.yearAired != null ? " (${overview.yearAired})" : ""}";
|
||||
|
||||
String? get subText => null;
|
||||
String? subTextShort(BuildContext context) => null;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
|
||||
import 'package:fladder/util/humanize_duration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
|
|
@ -13,8 +14,7 @@ import 'package:fladder/models/items/item_stream_model.dart';
|
|||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/screens/details_screens/movie_detail_screen.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:fladder/util/humanize_duration.dart';
|
||||
|
||||
part 'movie_model.mapper.dart';
|
||||
|
||||
|
|
@ -68,10 +68,6 @@ class MovieModel extends ItemStreamModel with MovieModelMappable {
|
|||
@override
|
||||
bool get identifiable => true;
|
||||
|
||||
@override
|
||||
String? label(BuildContext context) =>
|
||||
overview.yearAired == null ? overview.runTime.humanize : "$name (${overview.yearAired})";
|
||||
|
||||
@override
|
||||
ImageData? get bannerImage => images?.backDrop?.firstOrNull ?? images?.primary ?? getPosters?.primary;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ part 'client_settings_model.g.dart';
|
|||
|
||||
@freezed
|
||||
class ClientSettingsModel with _$ClientSettingsModel {
|
||||
const ClientSettingsModel._();
|
||||
factory ClientSettingsModel({
|
||||
String? syncPath,
|
||||
@Default(Vector2(x: 0, y: 0)) Vector2 position,
|
||||
|
|
@ -33,6 +34,14 @@ class ClientSettingsModel with _$ClientSettingsModel {
|
|||
}) = _ClientSettingsModel;
|
||||
|
||||
factory ClientSettingsModel.fromJson(Map<String, dynamic> json) => _$ClientSettingsModelFromJson(json);
|
||||
|
||||
Brightness statusBarBrightness(BuildContext context) {
|
||||
return switch (themeMode) {
|
||||
ThemeMode.dark => Brightness.light,
|
||||
ThemeMode.light => Brightness.dark,
|
||||
_ => MediaQuery.of(context).platformBrightness == Brightness.dark ? Brightness.light : Brightness.dark,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LocaleConvert implements JsonConverter<Locale?, String?> {
|
||||
|
|
|
|||
|
|
@ -294,9 +294,8 @@ class __$$ClientSettingsModelImplCopyWithImpl<$Res>
|
|||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ClientSettingsModelImpl
|
||||
with DiagnosticableTreeMixin
|
||||
implements _ClientSettingsModel {
|
||||
class _$ClientSettingsModelImpl extends _ClientSettingsModel
|
||||
with DiagnosticableTreeMixin {
|
||||
_$ClientSettingsModelImpl(
|
||||
{this.syncPath,
|
||||
this.position = const Vector2(x: 0, y: 0),
|
||||
|
|
@ -313,7 +312,8 @@ class _$ClientSettingsModelImpl
|
|||
this.posterSize = 1.0,
|
||||
this.pinchPosterZoom = false,
|
||||
this.mouseDragSupport = false,
|
||||
this.libraryPageSize});
|
||||
this.libraryPageSize})
|
||||
: super._();
|
||||
|
||||
factory _$ClientSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ClientSettingsModelImplFromJson(json);
|
||||
|
|
@ -464,7 +464,7 @@ class _$ClientSettingsModelImpl
|
|||
}
|
||||
}
|
||||
|
||||
abstract class _ClientSettingsModel implements ClientSettingsModel {
|
||||
abstract class _ClientSettingsModel extends ClientSettingsModel {
|
||||
factory _ClientSettingsModel(
|
||||
{final String? syncPath,
|
||||
final Vector2 position,
|
||||
|
|
@ -482,6 +482,7 @@ abstract class _ClientSettingsModel implements ClientSettingsModel {
|
|||
final bool pinchPosterZoom,
|
||||
final bool mouseDragSupport,
|
||||
final int? libraryPageSize}) = _$ClientSettingsModelImpl;
|
||||
_ClientSettingsModel._() : super._();
|
||||
|
||||
factory _ClientSettingsModel.fromJson(Map<String, dynamic> json) =
|
||||
_$ClientSettingsModelImpl.fromJson;
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class BookDetailsProviderNotifier extends StateNotifier<BookProviderModel> {
|
|||
|
||||
Future<Response?> fetchDetails(BookModel book) async {
|
||||
state = state.copyWith(
|
||||
parentModel: () => book,
|
||||
parentModel: () => state.book ?? book,
|
||||
);
|
||||
String bookId = state.book?.id ?? book.id;
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ class BookDetailsProviderNotifier extends StateNotifier<BookProviderModel> {
|
|||
final parentModel = parentResponse.bodyOrThrow;
|
||||
final getViews = await api.usersUserIdViewsGet();
|
||||
|
||||
//Hacky solution more false positives so good enough for now.
|
||||
//Hacky solution for determining parent views
|
||||
final parentIsView =
|
||||
getViews.body?.items?.firstWhereOrNull((element) => element.name == parentResponse.body?.name) != null;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ class MovieDetails extends _$MovieDetails {
|
|||
|
||||
Future<Response?> fetchDetails(ItemBaseModel item) async {
|
||||
try {
|
||||
if (item is MovieModel && state == null) {
|
||||
state = item;
|
||||
if (item is MovieModel) {
|
||||
state = state ?? item;
|
||||
}
|
||||
MovieModel? newState;
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: item.id);
|
||||
if (response.body == null) return null;
|
||||
state = response.bodyOrThrow as MovieModel;
|
||||
newState = (response.bodyOrThrow as MovieModel).copyWith(related: state?.related);
|
||||
final related = await ref.read(relatedUtilityProvider).relatedContent(item.id);
|
||||
state = state?.copyWith(related: related.body);
|
||||
state = newState.copyWith(related: related.body);
|
||||
return null;
|
||||
} catch (e) {
|
||||
_tryToCreateOfflineState(item);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'movies_details_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$movieDetailsHash() => r'e5ab0af7fab9eb7a8ea50a873e8875bb572bd240';
|
||||
String _$movieDetailsHash() => r'da07dcdb6e1955119df64f8a6a5634216435982c';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -27,16 +27,12 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
|||
Future<Response?> fetchDetails(ItemBaseModel seriesModel) async {
|
||||
try {
|
||||
if (seriesModel is SeriesModel) {
|
||||
state = seriesModel;
|
||||
state = state ?? seriesModel;
|
||||
}
|
||||
SeriesModel? newState;
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: seriesModel.id);
|
||||
if (response.body == null) {
|
||||
state = seriesModel as SeriesModel;
|
||||
return null;
|
||||
}
|
||||
if (response.body == null) return null;
|
||||
newState = response.bodyOrThrow as SeriesModel;
|
||||
|
||||
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id);
|
||||
newState = newState.copyWith(seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/screens/details_screens/components/label_title_item.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
class MediaStreamInformation extends ConsumerWidget {
|
||||
final MediaStreamsModel mediaStream;
|
||||
|
|
@ -27,8 +27,8 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: Text(e.prettyName),
|
||||
onTap: () {},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
@ -42,7 +42,8 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: textWidget(context, selected: mediaStream.currentAudioStream == e, label: e.displayTitle),
|
||||
child: textWidget(context,
|
||||
selected: mediaStream.currentAudioStream?.index == e.index, label: e.displayTitle),
|
||||
onTap: () => onAudioIndexChanged?.call(e.index),
|
||||
),
|
||||
)
|
||||
|
|
@ -57,7 +58,8 @@ class MediaStreamInformation extends ConsumerWidget {
|
|||
(e) => PopupMenuItem(
|
||||
value: e,
|
||||
padding: EdgeInsets.zero,
|
||||
child: textWidget(context, selected: mediaStream.currentSubStream == e, label: e.displayTitle),
|
||||
child: textWidget(context,
|
||||
selected: mediaStream.currentSubStream?.index == e.index, label: e.displayTitle),
|
||||
onTap: () => onSubIndexChanged?.call(e.index),
|
||||
),
|
||||
)
|
||||
|
|
@ -100,7 +102,7 @@ class _StreamOptionSelect<T> extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = Theme.of(context).textTheme.titleMedium;
|
||||
const padding = EdgeInsets.all(6.0);
|
||||
const padding = EdgeInsets.all(6);
|
||||
final itemList = itemBuilder(context);
|
||||
return LabelTitleItem(
|
||||
title: label,
|
||||
|
|
@ -110,6 +112,7 @@ class _StreamOptionSelect<T> extends StatelessWidget {
|
|||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
enabled: itemList.length > 1,
|
||||
itemBuilder: itemBuilder,
|
||||
menuPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
padding: padding,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
|||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:fladder/util/theme_extensions.dart';
|
||||
import 'package:fladder/util/widget_extensions.dart';
|
||||
import 'package:fladder/widgets/shared/selectable_icon_button.dart';
|
||||
|
||||
|
|
@ -55,12 +56,21 @@ class _SeasonDetailScreenState extends ConsumerState<SeasonDetailScreen> {
|
|||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.35),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.25),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceAround,
|
||||
runAlignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.67,
|
||||
child: Card(
|
||||
child: FladderImage(image: details.getPosters?.primary),
|
||||
),
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 600,
|
||||
|
|
@ -89,9 +99,6 @@ class _SeasonDetailScreenState extends ConsumerState<SeasonDetailScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
child: Card(child: FladderImage(image: details.getPosters?.primary))),
|
||||
],
|
||||
).padding(padding),
|
||||
Row(
|
||||
|
|
@ -123,36 +130,40 @@ class _SeasonDetailScreenState extends ConsumerState<SeasonDetailScreen> {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(200)),
|
||||
child: SegmentedButton(
|
||||
style: const ButtonStyle(
|
||||
elevation: WidgetStatePropertyAll(5),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
showSelectedIcon: true,
|
||||
segments: EpisodeDetailsViewType.values
|
||||
.map(
|
||||
(e) => ButtonSegment(
|
||||
value: e,
|
||||
icon: Icon(e.icon),
|
||||
label: SizedBox(
|
||||
height: 50,
|
||||
child: Center(
|
||||
child: Text(
|
||||
e.name.capitalize(),
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
selected: viewOptions,
|
||||
onSelectionChanged: (newOptions) {
|
||||
setState(() {
|
||||
viewOptions = newOptions;
|
||||
});
|
||||
},
|
||||
SegmentedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((state) {
|
||||
if (state.contains(WidgetState.selected)) {
|
||||
return context.colors.primaryContainer;
|
||||
}
|
||||
return context.colors.surfaceContainer;
|
||||
}),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 8, horizontal: 16)),
|
||||
elevation: const WidgetStatePropertyAll(5),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
showSelectedIcon: true,
|
||||
segments: EpisodeDetailsViewType.values
|
||||
.map(
|
||||
(e) => ButtonSegment(
|
||||
value: e,
|
||||
icon: Icon(e.icon),
|
||||
label: SizedBox(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Text(
|
||||
e.name.capitalize(),
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
selected: viewOptions,
|
||||
onSelectionChanged: (newOptions) {
|
||||
setState(() {
|
||||
viewOptions = newOptions;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -139,11 +139,12 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
|||
label: Text(context.localized.about),
|
||||
subLabel: const Text("Fladder"),
|
||||
suffix: Opacity(
|
||||
opacity: 1,
|
||||
child: FladderIconOutlined(
|
||||
size: 24,
|
||||
color: context.colors.onSurfaceVariant,
|
||||
)),
|
||||
opacity: 1,
|
||||
child: FladderIconOutlined(
|
||||
size: 24,
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: () => showAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const FladderIcon(size: 85),
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
onEnter: (event) => setState(() => hover = true),
|
||||
onExit: (event) => setState(() => hover = false),
|
||||
child: Card(
|
||||
elevation: 8,
|
||||
color: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.2),
|
||||
elevation: 6,
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
width: 1.0,
|
||||
|
|
@ -191,6 +191,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
child: Card(
|
||||
color: Colors.transparent,
|
||||
elevation: 3,
|
||||
shadowColor: Colors.transparent,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 7.5,
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary.withOpacity(0.5),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/playback/direct_playback_model.dart';
|
||||
|
|
@ -20,15 +24,14 @@ import 'package:fladder/util/string_extensions.dart';
|
|||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
import 'package:fladder/widgets/shared/spaced_list_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
Future<void> showVideoPlayerOptions(BuildContext context) {
|
||||
Future<void> showVideoPlayerOptions(BuildContext context, Function() minimizePlayer) {
|
||||
return showBottomSheetPill(
|
||||
context: context,
|
||||
content: (context, scrollController) {
|
||||
return VideoOptions(
|
||||
controller: scrollController,
|
||||
minimizePlayer: minimizePlayer,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -36,7 +39,8 @@ Future<void> showVideoPlayerOptions(BuildContext context) {
|
|||
|
||||
class VideoOptions extends ConsumerStatefulWidget {
|
||||
final ScrollController controller;
|
||||
const VideoOptions({required this.controller, super.key});
|
||||
final Function() minimizePlayer;
|
||||
const VideoOptions({required this.controller, required this.minimizePlayer, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _VideoOptionsMobileState();
|
||||
|
|
@ -67,16 +71,10 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (currentItem?.title.isNotEmpty == true)
|
||||
Text(
|
||||
currentItem?.title ?? "",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
if (currentItem?.detailedName(context)?.isNotEmpty == true)
|
||||
Text(
|
||||
currentItem?.detailedName(context) ?? "",
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)
|
||||
Text(
|
||||
currentItem?.title ?? "",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
|
|
@ -173,86 +171,6 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
],
|
||||
),
|
||||
),
|
||||
// ListTile(
|
||||
// title: const Text("Playback settings"),
|
||||
// onTap: () => setState(() => page = 1),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget itemOptions() {
|
||||
final currentItem = ref.watch(playBackModel.select((value) => value?.item));
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
navTitle("${currentItem?.title} \n${currentItem?.detailedName}"),
|
||||
if (currentItem != null) ...{
|
||||
if (currentItem.type == FladderItemType.episode)
|
||||
ListTile(
|
||||
onTap: () {
|
||||
//Pop twice once for sheet once for player
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
(this as EpisodeModel).parentBaseModel.navigateTo(context);
|
||||
},
|
||||
title: const Text("Open show"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
//Pop twice once for sheet once for player
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
await currentItem.navigateTo(context);
|
||||
},
|
||||
title: const Text("Show details"),
|
||||
),
|
||||
if (currentItem.type != FladderItemType.boxset)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await addItemToCollection(context, [currentItem]);
|
||||
if (context.mounted) {
|
||||
context.refreshData();
|
||||
}
|
||||
},
|
||||
title: const Text("Add to collection"),
|
||||
),
|
||||
if (currentItem.type != FladderItemType.playlist)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await addItemToPlaylist(context, [currentItem]);
|
||||
if (context.mounted) {
|
||||
context.refreshData();
|
||||
}
|
||||
},
|
||||
title: const Text("Add to playlist"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
final favourite = !(currentItem.userData.isFavourite == true);
|
||||
ref.read(userProvider.notifier).setAsFavorite(favourite, currentItem.id);
|
||||
final newUserData = currentItem.userData;
|
||||
final playbackModel = switch (ref.read(playBackModel)) {
|
||||
DirectPlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)),
|
||||
TranscodePlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)),
|
||||
OfflinePlaybackModel value => value.copyWith(item: currentItem.copyWith(userData: newUserData)),
|
||||
_ => null
|
||||
};
|
||||
if (playbackModel != null) {
|
||||
ref.read(playBackModel.notifier).update((state) => playbackModel);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: Text(currentItem.userData.isFavourite == true ? "Remove from favorites" : "Add to favourites"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showInfoScreen(context, currentItem);
|
||||
},
|
||||
title: const Text('Media info'),
|
||||
),
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -264,7 +182,7 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
shrinkWrap: true,
|
||||
controller: widget.controller,
|
||||
children: [
|
||||
navTitle("Playback Settings"),
|
||||
navTitle("Playback Settings", null),
|
||||
if (playbackState?.queue.isNotEmpty == true)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.video_collection_rounded),
|
||||
|
|
@ -294,7 +212,7 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
duration: const Duration(milliseconds: 250),
|
||||
child: switch (page) {
|
||||
1 => playbackSettings(),
|
||||
2 => itemOptions(),
|
||||
2 => itemInfo(currentItem, context),
|
||||
_ => mainPage(),
|
||||
},
|
||||
),
|
||||
|
|
@ -304,7 +222,79 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget navTitle(String title) {
|
||||
ListView itemInfo(ItemBaseModel? currentItem, BuildContext context) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
navTitle(currentItem?.title, currentItem?.subTextShort(context)),
|
||||
if (currentItem != null) ...{
|
||||
if (currentItem.type == FladderItemType.episode)
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
widget.minimizePlayer();
|
||||
(this as EpisodeModel).parentBaseModel.navigateTo(context);
|
||||
},
|
||||
title: const Text("Open show"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
widget.minimizePlayer();
|
||||
await currentItem.navigateTo(context);
|
||||
},
|
||||
title: const Text("Show details"),
|
||||
),
|
||||
if (currentItem.type != FladderItemType.boxset)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await addItemToCollection(context, [currentItem]);
|
||||
if (context.mounted) {
|
||||
context.refreshData();
|
||||
}
|
||||
},
|
||||
title: const Text("Add to collection"),
|
||||
),
|
||||
if (currentItem.type != FladderItemType.playlist)
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await addItemToPlaylist(context, [currentItem]);
|
||||
if (context.mounted) {
|
||||
context.refreshData();
|
||||
}
|
||||
},
|
||||
title: const Text("Add to playlist"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final response = await ref
|
||||
.read(userProvider.notifier)
|
||||
.setAsFavorite(!(currentItem.userData.isFavourite == true), currentItem.id);
|
||||
final newItem = currentItem.copyWith(userData: response?.body);
|
||||
final playbackModel = switch (ref.read(playBackModel)) {
|
||||
DirectPlaybackModel value => value.copyWith(item: newItem),
|
||||
TranscodePlaybackModel value => value.copyWith(item: newItem),
|
||||
OfflinePlaybackModel value => value.copyWith(item: newItem),
|
||||
_ => null
|
||||
};
|
||||
ref.read(playBackModel.notifier).update((state) => playbackModel);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: Text(currentItem.userData.isFavourite == true ? "Remove from favorites" : "Add to favourites"),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showInfoScreen(context, currentItem);
|
||||
},
|
||||
title: const Text('Media info'),
|
||||
),
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget navTitle(String? title, String? subText) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
|
|
@ -314,10 +304,20 @@ class _VideoOptionsMobileState extends ConsumerState<VideoOptions> {
|
|||
onPressed: () => setState(() => page = 0),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
)
|
||||
Column(
|
||||
children: [
|
||||
if (title != null)
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
if (subText != null)
|
||||
Text(
|
||||
subText,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/media_playback_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/screens/shared/default_titlebar.dart';
|
||||
|
|
@ -19,15 +29,8 @@ import 'package:fladder/screens/video_player/components/video_volume_slider.dart
|
|||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class DesktopControls extends ConsumerStatefulWidget {
|
||||
const DesktopControls({super.key});
|
||||
|
|
@ -45,24 +48,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
late final double topPadding = MediaQuery.of(context).viewPadding.top;
|
||||
late final double bottomPadding = MediaQuery.of(context).viewPadding.bottom;
|
||||
|
||||
Future<void> clear() async {
|
||||
toggleOverlay(value: true);
|
||||
if (!AdaptiveLayout.of(context).isDesktop) {
|
||||
ScreenBrightness().resetScreenBrightness();
|
||||
} else {
|
||||
disableFullscreen();
|
||||
}
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
void resetTimer() => timer.reset();
|
||||
|
||||
Future<void> closePlayer() async {
|
||||
clear();
|
||||
ref.read(videoPlayerProvider).stop();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mediaPlayback = ref.watch(mediaPlaybackProvider);
|
||||
|
|
@ -229,36 +214,38 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
child: Container(
|
||||
alignment: Alignment.topCenter,
|
||||
height: 80,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
clear();
|
||||
ref
|
||||
.read(mediaPlaybackProvider.notifier)
|
||||
.update((state) => state.copyWith(state: VideoPlayerState.minimized));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(
|
||||
IconsaxOutline.arrow_down_1,
|
||||
size: 24,
|
||||
child: Column(
|
||||
children: [
|
||||
if (AdaptiveLayout.of(context).isDesktop)
|
||||
const Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: DefaultTitleBar(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (!AdaptiveLayout.of(context).isDesktop)
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem?.title ?? "",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => minimizePlayer(context),
|
||||
icon: const Icon(
|
||||
IconsaxOutline.arrow_down_1,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const Flexible(child: Align(alignment: Alignment.topRight, child: DefaultTitleBar()))
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(
|
||||
currentItem?.title ?? "",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -296,7 +283,8 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => showVideoPlayerOptions(context), icon: const Icon(IconsaxOutline.more)),
|
||||
onPressed: () => showVideoPlayerOptions(context, () => minimizePlayer(context)),
|
||||
icon: const Icon(IconsaxOutline.more)),
|
||||
if (AdaptiveLayout.layoutOf(context) == LayoutState.tablet) ...[
|
||||
IconButton(
|
||||
onPressed: () => showSubSelection(context),
|
||||
|
|
@ -416,7 +404,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
final List<String?> details = [
|
||||
if (AdaptiveLayout.of(context).isDesktop) item?.label(context),
|
||||
mediaPlayback.duration.inMinutes > 1
|
||||
? 'ends at ${DateFormat('HH:mm').format(DateTime.now().add(mediaPlayback.duration - mediaPlayback.position))}'
|
||||
? context.localized.endsAt(DateTime.now().add(mediaPlayback.duration - mediaPlayback.position))
|
||||
: null
|
||||
];
|
||||
return Column(
|
||||
|
|
@ -606,10 +594,40 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
));
|
||||
}
|
||||
|
||||
void minimizePlayer(BuildContext context) {
|
||||
clearOverlaySettings();
|
||||
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(state: VideoPlayerState.minimized));
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Future<void> clearOverlaySettings() async {
|
||||
toggleOverlay(value: true);
|
||||
if (!AdaptiveLayout.of(context).isDesktop) {
|
||||
ScreenBrightness().resetScreenBrightness();
|
||||
} else {
|
||||
disableFullscreen();
|
||||
}
|
||||
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: ref.read(clientSettingsProvider.select((value) => value.statusBarBrightness(context))),
|
||||
));
|
||||
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
void resetTimer() => timer.reset();
|
||||
|
||||
Future<void> closePlayer() async {
|
||||
clearOverlaySettings();
|
||||
ref.read(videoPlayerProvider).stop();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Future<void> disableFullscreen() async {
|
||||
resetTimer();
|
||||
final isFullScreen = await windowManager.isFullScreen();
|
||||
|
|
|
|||
|
|
@ -1,37 +1,30 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'application_info.freezed.dart';
|
||||
|
||||
final applicationInfoProvider = StateProvider<ApplicationInfo>((ref) {
|
||||
return ApplicationInfo(
|
||||
name: "",
|
||||
version: "",
|
||||
buildNumber: "",
|
||||
os: "",
|
||||
);
|
||||
});
|
||||
|
||||
class ApplicationInfo {
|
||||
final String name;
|
||||
final String version;
|
||||
final String os;
|
||||
ApplicationInfo({
|
||||
required this.name,
|
||||
required this.version,
|
||||
required this.os,
|
||||
});
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
class ApplicationInfo with _$ApplicationInfo {
|
||||
const ApplicationInfo._();
|
||||
|
||||
ApplicationInfo copyWith({
|
||||
String? name,
|
||||
String? version,
|
||||
String? os,
|
||||
}) {
|
||||
return ApplicationInfo(
|
||||
name: name ?? this.name,
|
||||
version: version ?? this.version,
|
||||
os: os ?? this.os,
|
||||
);
|
||||
}
|
||||
factory ApplicationInfo({
|
||||
required String name,
|
||||
required String version,
|
||||
required String buildNumber,
|
||||
required String os,
|
||||
}) = _ApplicationInfo;
|
||||
|
||||
String get versionAndPlatform => "$version ($os)";
|
||||
String get versionAndPlatform => "$version ($os)\n#$buildNumber";
|
||||
|
||||
@override
|
||||
String toString() => 'ApplicationInfo(name: $name, version: $version, os: $os)';
|
||||
|
|
|
|||
187
lib/util/application_info.freezed.dart
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'application_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ApplicationInfo {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get version => throw _privateConstructorUsedError;
|
||||
String get buildNumber => throw _privateConstructorUsedError;
|
||||
String get os => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ApplicationInfoCopyWith<ApplicationInfo> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ApplicationInfoCopyWith<$Res> {
|
||||
factory $ApplicationInfoCopyWith(
|
||||
ApplicationInfo value, $Res Function(ApplicationInfo) then) =
|
||||
_$ApplicationInfoCopyWithImpl<$Res, ApplicationInfo>;
|
||||
@useResult
|
||||
$Res call({String name, String version, String buildNumber, String os});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ApplicationInfoCopyWithImpl<$Res, $Val extends ApplicationInfo>
|
||||
implements $ApplicationInfoCopyWith<$Res> {
|
||||
_$ApplicationInfoCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? version = null,
|
||||
Object? buildNumber = null,
|
||||
Object? os = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
version: null == version
|
||||
? _value.version
|
||||
: version // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
buildNumber: null == buildNumber
|
||||
? _value.buildNumber
|
||||
: buildNumber // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
os: null == os
|
||||
? _value.os
|
||||
: os // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ApplicationInfoImplCopyWith<$Res>
|
||||
implements $ApplicationInfoCopyWith<$Res> {
|
||||
factory _$$ApplicationInfoImplCopyWith(_$ApplicationInfoImpl value,
|
||||
$Res Function(_$ApplicationInfoImpl) then) =
|
||||
__$$ApplicationInfoImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String version, String buildNumber, String os});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ApplicationInfoImplCopyWithImpl<$Res>
|
||||
extends _$ApplicationInfoCopyWithImpl<$Res, _$ApplicationInfoImpl>
|
||||
implements _$$ApplicationInfoImplCopyWith<$Res> {
|
||||
__$$ApplicationInfoImplCopyWithImpl(
|
||||
_$ApplicationInfoImpl _value, $Res Function(_$ApplicationInfoImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? version = null,
|
||||
Object? buildNumber = null,
|
||||
Object? os = null,
|
||||
}) {
|
||||
return _then(_$ApplicationInfoImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
version: null == version
|
||||
? _value.version
|
||||
: version // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
buildNumber: null == buildNumber
|
||||
? _value.buildNumber
|
||||
: buildNumber // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
os: null == os
|
||||
? _value.os
|
||||
: os // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ApplicationInfoImpl extends _ApplicationInfo {
|
||||
_$ApplicationInfoImpl(
|
||||
{required this.name,
|
||||
required this.version,
|
||||
required this.buildNumber,
|
||||
required this.os})
|
||||
: super._();
|
||||
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String version;
|
||||
@override
|
||||
final String buildNumber;
|
||||
@override
|
||||
final String os;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ApplicationInfoImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.version, version) || other.version == version) &&
|
||||
(identical(other.buildNumber, buildNumber) ||
|
||||
other.buildNumber == buildNumber) &&
|
||||
(identical(other.os, os) || other.os == os));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, version, buildNumber, os);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ApplicationInfoImplCopyWith<_$ApplicationInfoImpl> get copyWith =>
|
||||
__$$ApplicationInfoImplCopyWithImpl<_$ApplicationInfoImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ApplicationInfo extends ApplicationInfo {
|
||||
factory _ApplicationInfo(
|
||||
{required final String name,
|
||||
required final String version,
|
||||
required final String buildNumber,
|
||||
required final String os}) = _$ApplicationInfoImpl;
|
||||
_ApplicationInfo._() : super._();
|
||||
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String get version;
|
||||
@override
|
||||
String get buildNumber;
|
||||
@override
|
||||
String get os;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ApplicationInfoImplCopyWith<_$ApplicationInfoImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
|
|
@ -48,6 +50,17 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final views = ref.watch(viewsProvider.select((value) => value.views));
|
||||
ref.listen(
|
||||
clientSettingsProvider,
|
||||
(previous, next) {
|
||||
if (previous != next) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: next.statusBarBrightness(context),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return switch (AdaptiveLayout.layoutOf(context)) {
|
||||
LayoutState.phone => MediaQuery.removePadding(
|
||||
context: widget.parentContext,
|
||||
|
|
@ -126,7 +139,7 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
|
|||
},
|
||||
icon: const Icon(IconsaxBold.menu),
|
||||
),
|
||||
if (AdaptiveLayout.of(context).isDesktop) ...[
|
||||
if (AdaptiveLayout.of(context).size == ScreenLayout.dual) ...[
|
||||
const SizedBox(height: 8),
|
||||
AnimatedFadeSize(
|
||||
child: AnimatedSwitcher(
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class NestedNavigationDrawer extends ConsumerWidget {
|
|||
),
|
||||
...views.map((library) => DrawerListButton(
|
||||
label: library.name,
|
||||
selected: checkLibrary(context, library.id),
|
||||
selected: context.router.currentUrl.contains(library.id),
|
||||
actions: [
|
||||
ItemActionButton(
|
||||
label: Text(context.localized.scanLibrary),
|
||||
|
|
@ -151,13 +151,4 @@ class NestedNavigationDrawer extends ConsumerWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool checkLibrary(BuildContext context, String id) {
|
||||
try {
|
||||
return context.router.current.name == LibrarySearchRoute().routeName &&
|
||||
(context.routeData.queryParams.isNotEmpty && context.routeData.queryParams.getString('parentId') == id);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
|
|||
extendBody: true,
|
||||
floatingActionButtonLocation:
|
||||
playerState == VideoPlayerState.minimized ? FloatingActionButtonLocation.centerFloat : null,
|
||||
floatingActionButton: AdaptiveLayout.of(context).layout == LayoutState.phone
|
||||
floatingActionButton: AdaptiveLayout.of(context).size == ScreenLayout.single
|
||||
? switch (playerState) {
|
||||
VideoPlayerState.minimized => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ AudioServiceConfig get audioServiceConfig => const AudioServiceConfig(
|
|||
androidNotificationChannelName: 'Video playback',
|
||||
androidNotificationOngoing: true,
|
||||
androidStopForegroundOnPause: true,
|
||||
// androidNotificationIcon: "mipmap/ic_notification_icon",
|
||||
rewindInterval: Duration(seconds: 10),
|
||||
fastForwardInterval: Duration(seconds: 15),
|
||||
androidNotificationChannelDescription: "Playback",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 995 B |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -854,10 +854,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: icons_launcher
|
||||
sha256: "9b514ffed6ed69b232fd2bf34c44878c8526be71fc74129a658f35c04c9d4a9d"
|
||||
sha256: a7c83fbc837dc6f81944ef35c3756f533bb2aba32fcca5cbcdb2dbcd877d5ae9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
version: "3.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ dependencies:
|
|||
# Icons
|
||||
cupertino_icons: ^1.0.2
|
||||
ficonsax: ^0.0.3
|
||||
icons_launcher: ^2.1.7
|
||||
icons_launcher: ^3.0.0
|
||||
|
||||
# Network and HTTP
|
||||
chopper: ^7.0.4
|
||||
|
|
|
|||
BIN
web/favicon.png
|
Before Width: | Height: | Size: 722 B After Width: | Height: | Size: 2.5 KiB |