Merge branch 'bugfix/fix-small-bugs' into develop

This commit is contained in:
PartyDonut 2024-10-11 16:51:26 +02:00
commit 3845abdde8
69 changed files with 602 additions and 360 deletions

8
.vscode/tasks.json vendored
View file

@ -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": ""
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
icons/macos_icon.afphoto Normal file

Binary file not shown.

View 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

View file

@ -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": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 648 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -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(),
),

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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",
);

View file

@ -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;

View file

@ -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;

View file

@ -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?> {

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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));

View file

@ -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,

View file

@ -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;
});
},
),
],
),

View file

@ -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),

View file

@ -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),

View file

@ -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),

View file

@ -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();

View file

@ -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)';

View 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;
}

View file

@ -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(

View file

@ -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;
}
}
}

View file

@ -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),

View file

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 218 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 995 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

@ -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:

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After