mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-15 10:15:58 -07:00
chore: Update flutter -> 3.27.0 and packages (#201)
This commit is contained in:
commit
5b781cf642
109 changed files with 4317 additions and 6229 deletions
2
.fvmrc
2
.fvmrc
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"flutter": "3.24.3"
|
"flutter": "3.27.0"
|
||||||
}
|
}
|
||||||
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
|
|
@ -20,6 +20,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version_name: ${{ steps.fetch.outputs.version_name }}
|
version_name: ${{ steps.fetch.outputs.version_name }}
|
||||||
|
flutter_version: ${{ steps.fetch.outputs.flutter_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
@ -27,8 +28,13 @@ jobs:
|
||||||
- name: Fetch version name
|
- name: Fetch version name
|
||||||
id: fetch
|
id: fetch
|
||||||
run: |
|
run: |
|
||||||
|
# Extract version_name from pubspec.yaml
|
||||||
VERSION_NAME=$(grep '^version:' pubspec.yaml | cut -d ':' -f2 | cut -d '+' -f1 | tr -d ' ')
|
VERSION_NAME=$(grep '^version:' pubspec.yaml | cut -d ':' -f2 | cut -d '+' -f1 | tr -d ' ')
|
||||||
echo "version_name=${VERSION_NAME}" >> "$GITHUB_OUTPUT"
|
echo "version_name=${VERSION_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Extract flutter_version from .fvmrc
|
||||||
|
FLUTTER_VERSION=$(jq -r '.flutter' .fvmrc)
|
||||||
|
echo "flutter_version=${FLUTTER_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
|
|
@ -67,7 +73,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||||
|
|
@ -104,7 +110,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
||||||
|
|
@ -133,7 +139,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to specify the cache path
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to specify the cache path
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
||||||
|
|
@ -169,7 +175,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
||||||
|
|
@ -201,7 +207,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
||||||
|
|
@ -274,7 +280,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{needs.fetch-info.outputs.flutter-version}}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path
|
||||||
|
|
|
||||||
13
.github/workflows/checks.yaml
vendored
13
.github/workflows/checks.yaml
vendored
|
|
@ -14,11 +14,18 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
|
- name: Fetch version info
|
||||||
|
run: |
|
||||||
|
# Extract flutter_version from .fvmrc
|
||||||
|
FLUTTER_VERSION=$(jq -r '.flutter' .fvmrc)
|
||||||
|
echo "FLUTTER_VERSION=${FLUTTER_VERSION}" >> "$GITHUB_ENV"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
uses: subosito/flutter-action@v2.16.0
|
uses: subosito/flutter-action@v2.16.0
|
||||||
with:
|
with:
|
||||||
channel: ${{ vars.FLUTTER_CHANNEL }}
|
channel: ${{ vars.FLUTTER_CHANNEL }}
|
||||||
flutter-version: ${{ vars.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
cache: true
|
||||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||||
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||||
|
|
@ -26,10 +33,10 @@ jobs:
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Setup dart
|
- name: Setup Dart
|
||||||
uses: dart-lang/setup-dart@v1
|
uses: dart-lang/setup-dart@v1
|
||||||
|
|
||||||
- name: Analyze Dart
|
- name: Analyze Dart
|
||||||
uses: invertase/github-action-dart-analyzer@v3
|
uses: invertase/github-action-dart-analyzer@v3
|
||||||
with:
|
with:
|
||||||
custom-lint: true
|
custom-lint: true
|
||||||
|
|
|
||||||
34
.vscode/settings.json
vendored
34
.vscode/settings.json
vendored
|
|
@ -1,19 +1,19 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"ficonsax",
|
"ficonsax",
|
||||||
"jellyfin",
|
"jellyfin",
|
||||||
"Jellyfin",
|
"Jellyfin",
|
||||||
"LTRB",
|
"LTRB",
|
||||||
"LTWH",
|
"LTWH",
|
||||||
"outro"
|
"outro"
|
||||||
],
|
],
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.24.3",
|
"dart.flutterSdkPath": ".fvm/versions/3.27.0",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
"files.watcherExclude": {
|
"files.watcherExclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
"editor.detectIndentation": true,
|
"editor.detectIndentation": true,
|
||||||
"dart.lineLength": 120
|
"dart.lineLength": 120
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart' as json;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ abstract class JellyfinOpenApi extends ChopperService {
|
||||||
ErrorConverter? errorConverter,
|
ErrorConverter? errorConverter,
|
||||||
Converter? converter,
|
Converter? converter,
|
||||||
Uri? baseUrl,
|
Uri? baseUrl,
|
||||||
Iterable<dynamic>? interceptors,
|
List<Interceptor>? interceptors,
|
||||||
}) {
|
}) {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
return _$JellyfinOpenApi(client);
|
return _$JellyfinOpenApi(client);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
@ -276,7 +275,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
||||||
dragDevices: {
|
dragDevices: {
|
||||||
...scrollBehaviour.dragDevices,
|
...scrollBehaviour.dragDevices,
|
||||||
mouseDrag ? PointerDeviceKind.mouse : null,
|
mouseDrag ? PointerDeviceKind.mouse : null,
|
||||||
}.whereNotNull().toSet(),
|
}.nonNulls.toSet(),
|
||||||
),
|
),
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class ErrorLogModel {
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
stackTrace,
|
stackTrace,
|
||||||
].whereNotNull().join();
|
].nonNulls.join();
|
||||||
|
|
||||||
String get clipBoard => [_label, content].toString();
|
String get clipBoard => [_label, content].toString();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:fladder/models/items/series_model.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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.enums.swagger.dart';
|
||||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jelly;
|
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jelly;
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
|
import 'package:fladder/models/items/series_model.dart';
|
||||||
import 'package:fladder/providers/image_provider.dart';
|
import 'package:fladder/providers/image_provider.dart';
|
||||||
|
|
||||||
class EditItemsProvider {
|
class EditItemsProvider {
|
||||||
|
|
@ -118,7 +118,7 @@ class ItemEditingModel {
|
||||||
"OfficialRating": {
|
"OfficialRating": {
|
||||||
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
||||||
?..add(json?["OfficialRating"] as String?))
|
?..add(json?["OfficialRating"] as String?))
|
||||||
?.whereNotNull()
|
?.nonNulls
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[])
|
[])
|
||||||
element: (editedJson?["OfficialRating"] as String?) == element
|
element: (editedJson?["OfficialRating"] as String?) == element
|
||||||
|
|
@ -126,7 +126,7 @@ class ItemEditingModel {
|
||||||
"CustomRating": {
|
"CustomRating": {
|
||||||
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
||||||
?..add(json?["CustomRating"] as String?))
|
?..add(json?["CustomRating"] as String?))
|
||||||
?.whereNotNull()
|
?.nonNulls
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[])
|
[])
|
||||||
element: (editedJson?["CustomRating"] as String?) == element
|
element: (editedJson?["CustomRating"] as String?) == element
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ class AudioStreamModel extends StreamModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
String get title =>
|
String get title =>
|
||||||
[name, language, codec, channelLayout].whereNotNull().where((element) => element.isNotEmpty).join(' - ');
|
[name, language, codec, channelLayout].nonNulls.where((element) => element.isNotEmpty).join(' - ');
|
||||||
|
|
||||||
AudioStreamModel.no({
|
AudioStreamModel.no({
|
||||||
super.name = 'Off',
|
super.name = 'Off',
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ class PlaybackModelHelper {
|
||||||
trickPlay: syncedItem.trickPlayModel,
|
trickPlay: syncedItem.trickPlayModel,
|
||||||
mediaSegments: syncedItem.mediaSegments,
|
mediaSegments: syncedItem.mediaSegments,
|
||||||
media: Media(url: syncedItem.videoFile.path),
|
media: Media(url: syncedItem.videoFile.path),
|
||||||
queue: itemQueue.whereNotNull().toList(),
|
queue: itemQueue.nonNulls.toList(),
|
||||||
syncedQueue: children,
|
syncedQueue: children,
|
||||||
mediaStreams: item.streamModel ?? syncedItemModel.streamModel,
|
mediaStreams: item.streamModel ?? syncedItemModel.streamModel,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
|
@ -9,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
|
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
|
||||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||||
|
import 'package:fladder/util/color_extensions.dart';
|
||||||
|
|
||||||
class SubtitleSettingsModel {
|
class SubtitleSettingsModel {
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
|
|
@ -58,11 +58,11 @@ class SubtitleSettingsModel {
|
||||||
? [
|
? [
|
||||||
Shadow(
|
Shadow(
|
||||||
blurRadius: 16,
|
blurRadius: 16,
|
||||||
color: Colors.black.withOpacity(shadow),
|
color: Colors.black.withValues(alpha: shadow),
|
||||||
),
|
),
|
||||||
Shadow(
|
Shadow(
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
color: Colors.black.withOpacity(shadow),
|
color: Colors.black.withValues(alpha: shadow),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -92,10 +92,10 @@ class SubtitleSettingsModel {
|
||||||
'fontSize': fontSize,
|
'fontSize': fontSize,
|
||||||
'fontWeight': fontWeight.value,
|
'fontWeight': fontWeight.value,
|
||||||
'verticalOffset': verticalOffset,
|
'verticalOffset': verticalOffset,
|
||||||
'color': color.value,
|
'color': color.toMap,
|
||||||
'outlineColor': outlineColor.value,
|
'outlineColor': outlineColor.toMap,
|
||||||
'outlineSize': outlineSize,
|
'outlineSize': outlineSize,
|
||||||
'backGroundColor': backGroundColor.value,
|
'backGroundColor': backGroundColor.toMap,
|
||||||
'shadow': shadow,
|
'shadow': shadow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +109,10 @@ class SubtitleSettingsModel {
|
||||||
fontSize: map['fontSize'] as double?,
|
fontSize: map['fontSize'] as double?,
|
||||||
fontWeight: FontWeight.values.firstWhereOrNull((element) => element.index == map['fontWeight'] as int?),
|
fontWeight: FontWeight.values.firstWhereOrNull((element) => element.index == map['fontWeight'] as int?),
|
||||||
verticalOffset: map['verticalOffset'] as double?,
|
verticalOffset: map['verticalOffset'] as double?,
|
||||||
color: map['color'] != null ? Color(map['color'] as int) : null,
|
color: colorFromJson(map['color']),
|
||||||
outlineColor: map['outlineColor'] != null ? Color(map['outlineColor'] as int) : null,
|
outlineColor: colorFromJson(map['outlineColor']),
|
||||||
outlineSize: map['outlineSize'] as double?,
|
outlineSize: map['outlineSize'] as double?,
|
||||||
backGroundColor: map['backGroundColor'] != null ? Color(map['backGroundColor'] as int) : null,
|
backGroundColor: colorFromJson(map['backGroundColor']),
|
||||||
shadow: map['shadow'] as double?,
|
shadow: map['shadow'] as double?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||||
import 'package:fladder/providers/auth_provider.dart';
|
import 'package:fladder/providers/auth_provider.dart';
|
||||||
import 'package:fladder/providers/service_provider.dart';
|
import 'package:fladder/providers/service_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'api_provider.g.dart';
|
part 'api_provider.g.dart';
|
||||||
|
|
||||||
|
|
@ -26,37 +27,40 @@ class JellyApi extends _$JellyApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JellyRequest implements RequestInterceptor {
|
class JellyRequest implements Interceptor {
|
||||||
JellyRequest(this.ref);
|
JellyRequest(this.ref);
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Request> onRequest(Request request) async {
|
FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) async {
|
||||||
if (request.method == HttpMethod.Post) {
|
|
||||||
chopperLogger.info('Performed a POST request');
|
|
||||||
}
|
|
||||||
|
|
||||||
final serverUrl = Uri.parse(ref.read(userProvider)?.server ?? ref.read(authProvider).tempCredentials.server);
|
final serverUrl = Uri.parse(ref.read(userProvider)?.server ?? ref.read(authProvider).tempCredentials.server);
|
||||||
|
|
||||||
//Use current logged in user otherwise use the authprovider
|
//Use current logged in user otherwise use the authprovider
|
||||||
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).tempCredentials;
|
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).tempCredentials;
|
||||||
var headers = loginModel.header(ref);
|
var headers = loginModel.header(ref);
|
||||||
|
|
||||||
return request.copyWith(
|
final Response<BodyType> response = await chain.proceed(
|
||||||
baseUri: serverUrl,
|
applyHeaders(
|
||||||
headers: request.headers..addAll(headers),
|
chain.request.copyWith(
|
||||||
|
baseUri: serverUrl,
|
||||||
|
),
|
||||||
|
headers),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JellyResponse implements ResponseInterceptor {
|
class JellyResponse implements Interceptor {
|
||||||
JellyResponse(this.ref);
|
JellyResponse(this.ref);
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Response<dynamic>> onResponse(Response<dynamic> response) {
|
FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) async {
|
||||||
|
final Response<BodyType> response = await chain.proceed(chain.request);
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
log('x- ${response.base.statusCode} - ${response.base.reasonPhrase} - ${response.error} - ${response.base.request?.method} ${response.base.request?.url.toString()}');
|
log('x- ${response.base.statusCode} - ${response.base.reasonPhrase} - ${response.error} - ${response.base.request?.method} ${response.base.request?.url.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,4 @@ final jellyApiProvider =
|
||||||
|
|
||||||
typedef _$JellyApi = AutoDisposeNotifier<JellyService>;
|
typedef _$JellyApi = AutoDisposeNotifier<JellyService>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
import 'package:fladder/providers/service_provider.dart';
|
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||||
import 'package:fladder/models/book_model.dart';
|
import 'package:fladder/models/book_model.dart';
|
||||||
import 'package:fladder/providers/api_provider.dart';
|
import 'package:fladder/providers/api_provider.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:fladder/providers/service_provider.dart';
|
||||||
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
|
|
||||||
class BookViewerModel {
|
class BookViewerModel {
|
||||||
final BookModel? book;
|
final BookModel? book;
|
||||||
|
|
@ -75,7 +76,7 @@ class BookViewerNotifier extends StateNotifier<BookViewerModel> {
|
||||||
await bookFile.writeAsBytes(response.bodyBytes);
|
await bookFile.writeAsBytes(response.bodyBytes);
|
||||||
|
|
||||||
final inputStream = InputFileStream(bookFile.path);
|
final inputStream = InputFileStream(bookFile.path);
|
||||||
final archive = ZipDecoder().decodeBuffer(inputStream);
|
final archive = ZipDecoder().decodeStream(inputStream);
|
||||||
|
|
||||||
final List<String> imagesPath = [];
|
final List<String> imagesPath = [];
|
||||||
for (var file in archive.files) {
|
for (var file in archive.files) {
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,4 @@ final serverDiscoveryProvider = AutoDisposeStreamNotifierProvider<
|
||||||
|
|
||||||
typedef _$ServerDiscovery = AutoDisposeStreamNotifier<List<DiscoveryInfo>>;
|
typedef _$ServerDiscovery = AutoDisposeStreamNotifier<List<DiscoveryInfo>>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class BookProviderModel {
|
||||||
ImagesData? get cover => parentModel?.getPosters ?? book?.getPosters;
|
ImagesData? get cover => parentModel?.getPosters ?? book?.getPosters;
|
||||||
|
|
||||||
List<BookModel> get allBooks {
|
List<BookModel> get allBooks {
|
||||||
if (chapters.isEmpty) return [book].whereNotNull().toList();
|
if (chapters.isEmpty) return [book].nonNulls.toList();
|
||||||
return chapters;
|
return chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ class BookDetailsProviderNotifier extends StateNotifier<BookProviderModel> {
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
parentModel: !parentIsView ? () => parentResponse.bodyOrThrow : null,
|
parentModel: !parentIsView ? () => parentResponse.bodyOrThrow : null,
|
||||||
chapters: (siblingsResponse?.body?.items ?? [openedBook]).whereType<BookModel>().whereNotNull().toList(),
|
chapters: (siblingsResponse?.body?.items ?? [openedBook]).whereType<BookModel>().nonNulls.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class EpisodeDetailsProvider extends StateNotifier<EpisodeDetailModel> {
|
||||||
.map(
|
.map(
|
||||||
(e) => e.createItemModel(ref),
|
(e) => e.createItemModel(ref),
|
||||||
)
|
)
|
||||||
.whereNotNull()
|
.nonNulls
|
||||||
.whereType<EpisodeModel>()
|
.whereType<EpisodeModel>()
|
||||||
.toList();
|
.toList();
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,8 @@ class MovieDetailsProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin MovieDetailsRef on AutoDisposeNotifierProviderRef<MovieModel?> {
|
mixin MovieDetailsRef on AutoDisposeNotifierProviderRef<MovieModel?> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
String get arg;
|
String get arg;
|
||||||
|
|
@ -171,4 +173,4 @@ class _MovieDetailsProviderElement
|
||||||
String get arg => (origin as MovieDetailsProvider).arg;
|
String get arg => (origin as MovieDetailsProvider).arg;
|
||||||
}
|
}
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/providers/service_provider.dart';
|
import 'package:fladder/providers/service_provider.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
|
|
@ -68,7 +67,7 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||||
.map(
|
.map(
|
||||||
(e) => e.createItemModel(ref),
|
(e) => e.createItemModel(ref),
|
||||||
)
|
)
|
||||||
.whereNotNull()
|
.nonNulls
|
||||||
.toList();
|
.toList();
|
||||||
state = seriesModel.copyWith(
|
state = seriesModel.copyWith(
|
||||||
availableEpisodes: allChildren.whereType<EpisodeModel>().toList(),
|
availableEpisodes: allChildren.whereType<EpisodeModel>().toList(),
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,8 @@ class LibraryFiltersProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin LibraryFiltersRef
|
mixin LibraryFiltersRef
|
||||||
on AutoDisposeNotifierProviderRef<List<LibraryFiltersModel>> {
|
on AutoDisposeNotifierProviderRef<List<LibraryFiltersModel>> {
|
||||||
/// The parameter `ids` of this provider.
|
/// The parameter `ids` of this provider.
|
||||||
|
|
@ -171,4 +173,4 @@ class _LibraryFiltersProviderElement extends AutoDisposeNotifierProviderElement<
|
||||||
List<String> get ids => (origin as LibraryFiltersProvider).ids;
|
List<String> get ids => (origin as LibraryFiltersProvider).ids;
|
||||||
}
|
}
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,10 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
newLastIndices[viewModel.id] = (lastIndices ?? 0) + libraryItems.items.length;
|
newLastIndices[viewModel.id] = (lastIndices ?? 0) + libraryItems.items.length;
|
||||||
}
|
}
|
||||||
return libraryItems;
|
return libraryItems;
|
||||||
}).whereNotNull(),
|
}).nonNulls,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<ItemBaseModel> newPosters = results.whereNotNull().expand((element) => element.items).toList();
|
List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList();
|
||||||
if (state.views.included.length > 1) {
|
if (state.views.included.length > 1) {
|
||||||
if (state.sortingOption == SortingOptions.random) {
|
if (state.sortingOption == SortingOptions.random) {
|
||||||
newPosters = newPosters.random();
|
newPosters = newPosters.random();
|
||||||
|
|
@ -220,7 +220,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
var tempState = state.copyWith();
|
var tempState = state.copyWith();
|
||||||
final genres = mappedList
|
final genres = mappedList
|
||||||
.expand((element) => element?.genres ?? <NameGuidPair>[])
|
.expand((element) => element?.genres ?? <NameGuidPair>[])
|
||||||
.whereNotNull()
|
.nonNulls
|
||||||
.sorted((a, b) => a.name!.toLowerCase().compareTo(b.name!.toLowerCase()));
|
.sorted((a, b) => a.name!.toLowerCase().compareTo(b.name!.toLowerCase()));
|
||||||
final tags = mappedList
|
final tags = mappedList
|
||||||
.expand((element) => element?.tags ?? <String>[])
|
.expand((element) => element?.tags ?? <String>[])
|
||||||
|
|
@ -437,7 +437,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
final response = await api.collectionsCollectionIdItemsDelete(
|
final response = await api.collectionsCollectionIdItemsDelete(
|
||||||
collectionId: state.nestedCurrentItem?.id, ids: state.selectedPosters.map((e) => e.id).toList());
|
collectionId: state.nestedCurrentItem?.id, ids: state.selectedPosters.map((e) => e.id).toList());
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
removeFromPosters([state.nestedCurrentItem?.id].whereNotNull().toList());
|
removeFromPosters([state.nestedCurrentItem?.id].nonNulls.toList());
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
@ -445,9 +445,9 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
Future<Response> removeSelectedFromPlaylist() async {
|
Future<Response> removeSelectedFromPlaylist() async {
|
||||||
final response = await api.playlistsPlaylistIdItemsDelete(
|
final response = await api.playlistsPlaylistIdItemsDelete(
|
||||||
playlistId: state.nestedCurrentItem?.id,
|
playlistId: state.nestedCurrentItem?.id,
|
||||||
entryIds: state.selectedPosters.map((e) => e.playlistId).whereNotNull().toList());
|
entryIds: state.selectedPosters.map((e) => e.playlistId).nonNulls.toList());
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
removeFromPosters([state.nestedCurrentItem?.id].whereNotNull().toList());
|
removeFromPosters([state.nestedCurrentItem?.id].nonNulls.toList());
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +463,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
|
|
||||||
Future<Response> removeFromPlaylist({required List<ItemBaseModel> items}) async {
|
Future<Response> removeFromPlaylist({required List<ItemBaseModel> items}) async {
|
||||||
final response = await api.playlistsPlaylistIdItemsDelete(
|
final response = await api.playlistsPlaylistIdItemsDelete(
|
||||||
playlistId: state.nestedCurrentItem?.id, entryIds: items.map((e) => e.playlistId).whereNotNull().toList());
|
playlistId: state.nestedCurrentItem?.id, entryIds: items.map((e) => e.playlistId).nonNulls.toList());
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
removeFromPosters(items.map((e) => e.id).toList());
|
removeFromPosters(items.map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
|
|
@ -489,7 +489,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
|
|
||||||
void updateUserDataMain(UserData? userData) {
|
void updateUserDataMain(UserData? userData) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
folderOverwrite: [state.folderOverwrite.lastOrNull?.copyWith(userData: userData)].whereNotNull().toList(),
|
folderOverwrite: [state.folderOverwrite.lastOrNull?.copyWith(userData: userData)].nonNulls.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -529,10 +529,10 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
||||||
limit: limit,
|
limit: limit,
|
||||||
);
|
);
|
||||||
return libraryItems;
|
return libraryItems;
|
||||||
}).whereNotNull(),
|
}).nonNulls,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<ItemBaseModel> newPosters = results.whereNotNull().expand((element) => element.items).toList();
|
List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList();
|
||||||
if (state.views.included.length > 1) {
|
if (state.views.included.length > 1) {
|
||||||
if (shuffle || state.sortingOption == SortingOptions.random) {
|
if (shuffle || state.sortingOption == SortingOptions.random) {
|
||||||
newPosters = newPosters.random();
|
newPosters = newPosters.random();
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,4 @@ final sessionInfoProvider =
|
||||||
|
|
||||||
typedef _$SessionInfo = AutoDisposeNotifier<SessionInfoModel>;
|
typedef _$SessionInfo = AutoDisposeNotifier<SessionInfoModel>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
||||||
|
|
@ -124,15 +125,15 @@ class BookViewerSettingsNotifier extends StateNotifier<BookViewerSettingsModel>
|
||||||
screenBrightness: () => value,
|
screenBrightness: () => value,
|
||||||
);
|
);
|
||||||
if (state.screenBrightness != null) {
|
if (state.screenBrightness != null) {
|
||||||
ScreenBrightness().setScreenBrightness(state.screenBrightness!);
|
ScreenBrightness().setApplicationScreenBrightness(state.screenBrightness!);
|
||||||
} else {
|
} else {
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSavedBrightness() {
|
void setSavedBrightness() {
|
||||||
if (state.screenBrightness != null) {
|
if (state.screenBrightness != null) {
|
||||||
ScreenBrightness().setScreenBrightness(state.screenBrightness!);
|
ScreenBrightness().setApplicationScreenBrightness(state.screenBrightness!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class SubtitleSettingsNotifier extends StateNotifier<SubtitleSettingsModel> {
|
||||||
void setFontWeight(FontWeight? value) => state = state.copyWith(fontWeight: value);
|
void setFontWeight(FontWeight? value) => state = state.copyWith(fontWeight: value);
|
||||||
|
|
||||||
SubtitleSettingsModel setBackGroundOpacity(double value) =>
|
SubtitleSettingsModel setBackGroundOpacity(double value) =>
|
||||||
state = state.copyWith(backGroundColor: state.backGroundColor.withOpacity(value));
|
state = state.copyWith(backGroundColor: state.backGroundColor.withValues(alpha: value));
|
||||||
|
|
||||||
SubtitleSettingsModel setShadowIntensity(double value) => state = state.copyWith(shadow: value);
|
SubtitleSettingsModel setShadowIntensity(double value) => state = state.copyWith(shadow: value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,15 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
|
||||||
screenBrightness: value,
|
screenBrightness: value,
|
||||||
);
|
);
|
||||||
if (state.screenBrightness != null) {
|
if (state.screenBrightness != null) {
|
||||||
ScreenBrightness().setScreenBrightness(state.screenBrightness!);
|
ScreenBrightness().setApplicationScreenBrightness(state.screenBrightness!);
|
||||||
} else {
|
} else {
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSavedBrightness() {
|
void setSavedBrightness() {
|
||||||
if (state.screenBrightness != null) {
|
if (state.screenBrightness != null) {
|
||||||
ScreenBrightness().setScreenBrightness(state.screenBrightness!);
|
ScreenBrightness().setApplicationScreenBrightness(state.screenBrightness!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'background_download_provider.g.dart';
|
part 'background_download_provider.g.dart';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
FileDownloader backgroundDownloader(BackgroundDownloaderRef ref) {
|
FileDownloader backgroundDownloader(Ref ref) {
|
||||||
return FileDownloader()
|
return FileDownloader()
|
||||||
..trackTasks()
|
..trackTasks()
|
||||||
..configureNotification(
|
..configureNotification(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ final backgroundDownloaderProvider = Provider<FileDownloader>.internal(
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
typedef BackgroundDownloaderRef = ProviderRef<FileDownloader>;
|
typedef BackgroundDownloaderRef = ProviderRef<FileDownloader>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,8 @@ class SyncChildrenProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin SyncChildrenRef on AutoDisposeNotifierProviderRef<List<SyncedItem>> {
|
mixin SyncChildrenRef on AutoDisposeNotifierProviderRef<List<SyncedItem>> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
@ -302,6 +304,8 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin SyncDownloadStatusRef on AutoDisposeNotifierProviderRef<DownloadStream?> {
|
mixin SyncDownloadStatusRef on AutoDisposeNotifierProviderRef<DownloadStream?> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
@ -446,6 +450,8 @@ class SyncStatusesProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin SyncStatusesRef on AutoDisposeAsyncNotifierProviderRef<SyncStatus> {
|
mixin SyncStatusesRef on AutoDisposeAsyncNotifierProviderRef<SyncStatus> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
@ -586,6 +592,8 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
mixin SyncSizeRef on AutoDisposeNotifierProviderRef<int?> {
|
mixin SyncSizeRef on AutoDisposeNotifierProviderRef<int?> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
@ -600,4 +608,4 @@ class _SyncSizeProviderElement
|
||||||
SyncedItem get arg => (origin as SyncSizeProvider).arg;
|
SyncedItem get arg => (origin as SyncSizeProvider).arg;
|
||||||
}
|
}
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
return data.copyWith(
|
return data.copyWith(
|
||||||
primary: () => primary,
|
primary: () => primary,
|
||||||
logo: () => logo,
|
logo: () => logo,
|
||||||
backDrop: () => backdrops.whereNotNull().toList(),
|
backDrop: () => backdrops.nonNulls.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,7 +381,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
imageUrl: path.joinAll(["Chapters", fileName]),
|
imageUrl: path.joinAll(["Chapters", fileName]),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
return saveChapters.whereNotNull().toList();
|
return saveChapters.nonNulls.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ImageData?> urlDataToFileData(ImageData? data, Directory directory, String fileName) async {
|
Future<ImageData?> urlDataToFileData(ImageData? data, Directory directory, String fileName) async {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import 'package:fladder/jellyfin/enum_models.dart';
|
import 'package:fladder/jellyfin/enum_models.dart';
|
||||||
|
|
@ -14,7 +15,7 @@ import 'package:fladder/providers/sync_provider.dart';
|
||||||
part 'user_provider.g.dart';
|
part 'user_provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
bool showSyncButtonProvider(ShowSyncButtonProviderRef ref) {
|
bool showSyncButtonProvider(Ref ref) {
|
||||||
final userCanSync = ref.watch(userProvider.select((value) => value?.canDownload ?? false));
|
final userCanSync = ref.watch(userProvider.select((value) => value?.canDownload ?? false));
|
||||||
final hasSyncedItems = ref.watch(syncProvider.select((value) => value.items.isNotEmpty));
|
final hasSyncedItems = ref.watch(syncProvider.select((value) => value.items.isNotEmpty));
|
||||||
return userCanSync || hasSyncedItems;
|
return userCanSync || hasSyncedItems;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
|
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
|
||||||
String _$userHash() => r'e83369c0d569d5a862aa1b92f3f0a45a9d1fe446';
|
String _$userHash() => r'e83369c0d569d5a862aa1b92f3f0a45a9d1fe446';
|
||||||
|
|
||||||
|
|
@ -37,4 +39,4 @@ final userProvider = NotifierProvider<User, AccountModel?>.internal(
|
||||||
|
|
||||||
typedef _$User = Notifier<AccountModel?>;
|
typedef _$User = Notifier<AccountModel?>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class _DetailsScreenState extends ConsumerState<DetailsScreen> {
|
||||||
tag: widget.id,
|
tag: widget.id,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(1.0),
|
color: Theme.of(context).colorScheme.surface.withValues(alpha: 1.0),
|
||||||
),
|
),
|
||||||
//Small offset to match detailscaffold
|
//Small offset to match detailscaffold
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
|
|
@ -150,9 +150,9 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
overlayColor.withOpacity(1),
|
overlayColor.withValues(alpha: 1),
|
||||||
overlayColor.withOpacity(0.65),
|
overlayColor.withValues(alpha: 0.65),
|
||||||
overlayColor.withOpacity(0),
|
overlayColor.withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -198,9 +198,9 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
overlayColor.withOpacity(0),
|
overlayColor.withValues(alpha: 0),
|
||||||
overlayColor.withOpacity(0.65),
|
overlayColor.withValues(alpha: 0.65),
|
||||||
overlayColor.withOpacity(1),
|
overlayColor.withValues(alpha: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -236,7 +236,7 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.7),
|
color: Colors.black.withValues(alpha: 0.7),
|
||||||
borderRadius: BorderRadius.circular(60),
|
borderRadius: BorderRadius.circular(60),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:extended_image/extended_image.dart';
|
import 'package:extended_image/extended_image.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/providers/settings/book_viewer_settings_provider.dart';
|
import 'package:fladder/providers/settings/book_viewer_settings_provider.dart';
|
||||||
import 'package:fladder/screens/book_viewer/book_viewer_controls.dart';
|
import 'package:fladder/screens/book_viewer/book_viewer_controls.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class BookViewerReader extends ConsumerWidget {
|
class BookViewerReader extends ConsumerWidget {
|
||||||
final int index;
|
final int index;
|
||||||
|
|
@ -112,7 +114,6 @@ class BookViewerReader extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
File(pages[index - 1]),
|
File(pages[index - 1]),
|
||||||
enableMemoryCache: true,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ class CrashScreen extends ConsumerWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
color: e.color.withOpacity(0.1),
|
color: e.color.withValues(alpha: 0.1),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 12),
|
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
|
|
@ -90,7 +90,7 @@ class CrashScreen extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Card(
|
child: Card(
|
||||||
color: e.color.withOpacity(0.2),
|
color: e.color.withValues(alpha: 0.2),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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.enums.swagger.dart';
|
||||||
|
|
@ -179,7 +178,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||||
posters: view.recentlyAdded,
|
posters: view.recentlyAdded,
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
].whereNotNull().toList().addInBetween(const SliverToBoxAdapter(child: SizedBox(height: 16))),
|
].nonNulls.toList().addInBetween(const SliverToBoxAdapter(child: SizedBox(height: 16))),
|
||||||
const DefautlSliverBottomPadding(),
|
const DefautlSliverBottomPadding(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class _BookDetailScreenState extends ConsumerState<BookDetailScreen> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.8),
|
||||||
onRefresh: () async => await ref.read(provider.notifier).fetchDetails(widget.item),
|
onRefresh: () async => await ref.read(provider.notifier).fetchDetails(widget.item),
|
||||||
backDrops: details.cover,
|
backDrops: details.cover,
|
||||||
content: (padding) => details.book != null
|
content: (padding) => details.book != null
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class LabelTitleItem extends ConsumerWidget {
|
class LabelTitleItem extends ConsumerWidget {
|
||||||
|
|
@ -32,7 +31,7 @@ class LabelTitleItem extends ConsumerWidget {
|
||||||
label!,
|
label!,
|
||||||
)
|
)
|
||||||
: content!,
|
: content!,
|
||||||
].whereNotNull().toList(),
|
].nonNulls.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class EmptyItem extends ConsumerWidget {
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: Colors.white.withOpacity(0.10),
|
color: Colors.white.withValues(alpha: 0.10),
|
||||||
),
|
),
|
||||||
borderRadius: FladderTheme.defaultShape.borderRadius,
|
borderRadius: FladderTheme.defaultShape.borderRadius,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
import 'package:fladder/providers/items/person_details_provider.dart';
|
import 'package:fladder/providers/items/person_details_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
|
|
@ -11,9 +16,6 @@ import 'package:fladder/util/list_extensions.dart';
|
||||||
import 'package:fladder/util/string_extensions.dart';
|
import 'package:fladder/util/string_extensions.dart';
|
||||||
import 'package:fladder/util/widget_extensions.dart';
|
import 'package:fladder/util/widget_extensions.dart';
|
||||||
import 'package:fladder/widgets/shared/selectable_icon_button.dart';
|
import 'package:fladder/widgets/shared/selectable_icon_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class PersonDetailScreen extends ConsumerStatefulWidget {
|
class PersonDetailScreen extends ConsumerStatefulWidget {
|
||||||
final Person person;
|
final Person person;
|
||||||
|
|
@ -37,6 +39,7 @@ class _PersonDetailScreenState extends ConsumerState<PersonDetailScreen> {
|
||||||
backDrops: [...?details?.movies, ...?details?.series].random().firstOrNull?.images,
|
backDrops: [...?details?.movies, ...?details?.series].random().firstOrNull?.images,
|
||||||
content: (padding) => Column(
|
content: (padding) => Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: MediaQuery.of(context).size.height / 6),
|
SizedBox(height: MediaQuery.of(context).size.height / 6),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -89,10 +92,6 @@ class _PersonDetailScreenState extends ConsumerState<PersonDetailScreen> {
|
||||||
Text("Birthday: ${DateFormat.yMEd().format(details?.dateOfBirth ?? DateTime.now()).toString()}"),
|
Text("Birthday: ${DateFormat.yMEd().format(details?.dateOfBirth ?? DateTime.now()).toString()}"),
|
||||||
if (details?.age != null) Text("Age: ${details?.age}"),
|
if (details?.age != null) Text("Age: ${details?.age}"),
|
||||||
if (details?.birthPlace.isEmpty == false) Text("Born in ${details?.birthPlace.join(",")}"),
|
if (details?.birthPlace.isEmpty == false) Text("Born in ${details?.birthPlace.join(",")}"),
|
||||||
if (details?.overview.externalUrls?.isNotEmpty ?? false)
|
|
||||||
ExternalUrlsRow(
|
|
||||||
urls: details?.overview.externalUrls,
|
|
||||||
).padding(padding),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -102,7 +101,11 @@ class _PersonDetailScreenState extends ConsumerState<PersonDetailScreen> {
|
||||||
if (details?.movies.isNotEmpty ?? false)
|
if (details?.movies.isNotEmpty ?? false)
|
||||||
PosterRow(contentPadding: padding, posters: details?.movies ?? [], label: "Movies"),
|
PosterRow(contentPadding: padding, posters: details?.movies ?? [], label: "Movies"),
|
||||||
if (details?.series.isNotEmpty ?? false)
|
if (details?.series.isNotEmpty ?? false)
|
||||||
PosterRow(contentPadding: padding, posters: details?.series ?? [], label: "Series")
|
PosterRow(contentPadding: padding, posters: details?.series ?? [], label: "Series"),
|
||||||
|
if (details?.overview.externalUrls?.isNotEmpty ?? false)
|
||||||
|
ExternalUrlsRow(
|
||||||
|
urls: details?.overview.externalUrls,
|
||||||
|
).padding(padding),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:ficonsax/ficonsax.dart';
|
import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
|
@ -76,7 +75,7 @@ class HomeScreen extends ConsumerWidget {
|
||||||
child: AutoRouter(
|
child: AutoRouter(
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return NavigationScaffold(
|
return NavigationScaffold(
|
||||||
destinations: destinations.whereNotNull().toList(),
|
destinations: destinations.nonNulls.toList(),
|
||||||
currentRouteName: context.router.current.name,
|
currentRouteName: context.router.current.name,
|
||||||
nestedChild: child,
|
nestedChild: child,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -515,7 +515,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
||||||
),
|
),
|
||||||
if (librarySearchResults.fetchingItems) ...[
|
if (librarySearchResults.fetchingItems) ...[
|
||||||
Container(
|
Container(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
|
||||||
tooltip: context.localized.defaultFilterForLibrary,
|
tooltip: context.localized.defaultFilterForLibrary,
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
filter.isFavourite ? Colors.yellowAccent.shade700.withOpacity(0.5) : null,
|
filter.isFavourite
|
||||||
|
? Colors.yellowAccent.shade700.withValues(alpha: 0.5)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
|
|
@ -107,6 +109,8 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
|
WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
|
||||||
|
iconColor:
|
||||||
|
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class LoginEditUser extends ConsumerWidget {
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
|
iconColor: Colors.white,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.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.enums.swagger.dart';
|
||||||
import 'package:fladder/models/item_editing_model.dart';
|
import 'package:fladder/models/item_editing_model.dart';
|
||||||
import 'package:fladder/providers/edit_item_provider.dart';
|
import 'package:fladder/providers/edit_item_provider.dart';
|
||||||
|
|
@ -6,8 +10,6 @@ import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||||
import 'package:fladder/screens/shared/file_picker.dart';
|
import 'package:fladder/screens/shared/file_picker.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class EditImageContent extends ConsumerStatefulWidget {
|
class EditImageContent extends ConsumerStatefulWidget {
|
||||||
final ImageType type;
|
final ImageType type;
|
||||||
|
|
@ -39,7 +41,8 @@ class _EditImageContentState extends ConsumerState<EditImageContent> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final posterSize = MediaQuery.sizeOf(context).width /
|
final posterSize = MediaQuery.sizeOf(context).width /
|
||||||
(AdaptiveLayout.poster(context).gridRatio * ref.watch(clientSettingsProvider.select((value) => value.posterSize)));
|
(AdaptiveLayout.poster(context).gridRatio *
|
||||||
|
ref.watch(clientSettingsProvider.select((value) => value.posterSize)));
|
||||||
final decimal = posterSize - posterSize.toInt();
|
final decimal = posterSize - posterSize.toInt();
|
||||||
final includeAllImages = ref.watch(editItemProvider.select((value) => value.includeAllImages));
|
final includeAllImages = ref.watch(editItemProvider.select((value) => value.includeAllImages));
|
||||||
final images = ref.watch(editItemProvider.select((value) => switch (widget.type) {
|
final images = ref.watch(editItemProvider.select((value) => switch (widget.type) {
|
||||||
|
|
@ -85,7 +88,8 @@ class _EditImageContentState extends ConsumerState<EditImageContent> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected ? Theme.of(context).colorScheme.primary : Colors.transparent,
|
color: selected ? Theme.of(context).colorScheme.primary : Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(color: Colors.transparent, width: 4, strokeAlign: BorderSide.strokeAlignInside),
|
border:
|
||||||
|
Border.all(color: Colors.transparent, width: 4, strokeAlign: BorderSide.strokeAlignInside),
|
||||||
),
|
),
|
||||||
child: Card(
|
child: Card(
|
||||||
color: selected ? Theme.of(context).colorScheme.onPrimary : null,
|
color: selected ? Theme.of(context).colorScheme.onPrimary : null,
|
||||||
|
|
@ -108,6 +112,7 @@ class _EditImageContentState extends ConsumerState<EditImageContent> {
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onError,
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onError,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
|
|
@ -121,6 +126,7 @@ class _EditImageContentState extends ConsumerState<EditImageContent> {
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onError,
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onError,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref.read(editItemProvider.notifier).deleteImage(widget.type, image);
|
await ref.read(editItemProvider.notifier).deleteImage(widget.type, image);
|
||||||
|
|
@ -230,7 +236,8 @@ class _EditImageContentState extends ConsumerState<EditImageContent> {
|
||||||
children: [...serverImageCards, ...imageCards],
|
children: [...serverImageCards, ...imageCards],
|
||||||
),
|
),
|
||||||
if (loading) const Center(child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round)),
|
if (loading) const Center(child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round)),
|
||||||
if (!loading && [...serverImageCards, ...imageCards].isEmpty) Center(child: Text("No ${widget.type.value}s found"))
|
if (!loading && [...serverImageCards, ...imageCards].isEmpty)
|
||||||
|
Center(child: Text("No ${widget.type.value}s found"))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -136,10 +136,10 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final gradient = [
|
final gradient = [
|
||||||
Colors.black.withOpacity(0.6),
|
Colors.black.withValues(alpha: 0.6),
|
||||||
Colors.black.withOpacity(0.3),
|
Colors.black.withValues(alpha: 0.3),
|
||||||
Colors.black.withOpacity(0.1),
|
Colors.black.withValues(alpha: 0.1),
|
||||||
Colors.black.withOpacity(0.0),
|
Colors.black.withValues(alpha: 0.0),
|
||||||
];
|
];
|
||||||
|
|
||||||
final padding = MediaQuery.of(context).padding;
|
final padding = MediaQuery.of(context).padding;
|
||||||
|
|
@ -188,9 +188,12 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium
|
.titleMedium
|
||||||
?.copyWith(fontWeight: FontWeight.bold, shadows: [
|
?.copyWith(fontWeight: FontWeight.bold, shadows: [
|
||||||
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.7)),
|
BoxShadow(
|
||||||
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.4)),
|
blurRadius: 1, spreadRadius: 1, color: Colors.black.withValues(alpha: 0.7)),
|
||||||
BoxShadow(blurRadius: 20, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
|
BoxShadow(
|
||||||
|
blurRadius: 4, spreadRadius: 4, color: Colors.black.withValues(alpha: 0.4)),
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 20, spreadRadius: 6, color: Colors.black.withValues(alpha: 0.2)),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -384,8 +384,8 @@ class _PhotoViewerScreenState extends ConsumerState<PhotoViewerScreen> with Widg
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Colors.black.withOpacity(0.5),
|
Colors.black.withValues(alpha: 0.5),
|
||||||
Colors.black.withOpacity(0),
|
Colors.black.withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,8 @@ class _SimpleVideoPlayerState extends ConsumerState<SimpleVideoPlayer> with Wind
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
player.lastState.playing ? IconsaxBold.pause_circle : IconsaxBold.play_circle,
|
player.lastState.playing ? IconsaxBold.pause_circle : IconsaxBold.play_circle,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(blurRadius: 16, spreadRadius: 2, color: Colors.black.withOpacity(0.15))
|
BoxShadow(
|
||||||
|
blurRadius: 16, spreadRadius: 2, color: Colors.black.withValues(alpha: 0.15))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class SettingsListTile extends StatelessWidget {
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 125),
|
duration: const Duration(milliseconds: 125),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primaryContainer.withOpacity(selected ? 1 : 0),
|
color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: selected ? 1 : 0),
|
||||||
borderRadius: BorderRadius.circular(selected ? 5 : 20),
|
borderRadius: BorderRadius.circular(selected ? 5 : 20),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: IconButton.filledTonal(
|
child: IconButton.filledTonal(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.8),
|
||||||
),
|
),
|
||||||
onPressed: () => context.router.popBack(),
|
onPressed: () => context.router.popBack(),
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
|
|
@ -188,6 +188,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom().copyWith(
|
style: ElevatedButton.styleFrom().copyWith(
|
||||||
|
iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
||||||
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
|
||||||
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
|
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ Future<void> showDefaultAlertDialog(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
onPressed: () => accept.call(context),
|
onPressed: () => accept.call(context),
|
||||||
child: Text(acceptTitle ?? "Accept"),
|
child: Text(acceptTitle ?? "Accept"),
|
||||||
|
|
@ -61,6 +62,7 @@ Future<void> showDefaultActionDialog(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
onPressed: () => accept.call(context),
|
onPressed: () => accept.call(context),
|
||||||
child: Text(acceptTitle ?? "Accept"),
|
child: Text(acceptTitle ?? "Accept"),
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,13 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
final brightness = widget.brightness ?? Theme.of(context).brightness;
|
final brightness = widget.brightness ?? Theme.of(context).brightness;
|
||||||
final shadows = brightness == Brightness.dark
|
final shadows = brightness == Brightness.dark
|
||||||
? [
|
? [
|
||||||
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Theme.of(context).colorScheme.surface.withOpacity(1)),
|
BoxShadow(
|
||||||
BoxShadow(blurRadius: 8, spreadRadius: 2, color: Colors.black.withOpacity(0.2)),
|
blurRadius: 1, spreadRadius: 1, color: Theme.of(context).colorScheme.surface.withValues(alpha: 1)),
|
||||||
BoxShadow(blurRadius: 3, spreadRadius: 2, color: Colors.black.withOpacity(0.3)),
|
BoxShadow(blurRadius: 8, spreadRadius: 2, color: Colors.black.withValues(alpha: 0.2)),
|
||||||
|
BoxShadow(blurRadius: 3, spreadRadius: 2, color: Colors.black.withValues(alpha: 0.3)),
|
||||||
]
|
]
|
||||||
: <BoxShadow>[];
|
: <BoxShadow>[];
|
||||||
final iconColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.65);
|
final iconColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65);
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
child: switch (AdaptiveLayout.of(context).platform) {
|
child: switch (AdaptiveLayout.of(context).platform) {
|
||||||
|
|
@ -47,7 +48,7 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0),
|
color: Colors.black.withValues(alpha: 0),
|
||||||
child: DragToMoveArea(
|
child: DragToMoveArea(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|
@ -77,8 +78,8 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
return IconButton(
|
return IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
hoverColor: brightness == Brightness.light
|
hoverColor: brightness == Brightness.light
|
||||||
? Colors.black.withOpacity(0.1)
|
? Colors.black.withValues(alpha: 0.1)
|
||||||
: Colors.white.withOpacity(0.2),
|
: Colors.white.withValues(alpha: 0.2),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2))),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2))),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (isMinimized) {
|
if (isMinimized) {
|
||||||
|
|
@ -110,8 +111,8 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
return IconButton(
|
return IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
hoverColor: brightness == Brightness.light
|
hoverColor: brightness == Brightness.light
|
||||||
? Colors.black.withOpacity(0.1)
|
? Colors.black.withValues(alpha: 0.1)
|
||||||
: Colors.white.withOpacity(0.2),
|
: Colors.white.withValues(alpha: 0.2),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final padding = EdgeInsets.symmetric(horizontal: MediaQuery.sizeOf(context).width / 25);
|
final padding = EdgeInsets.symmetric(horizontal: MediaQuery.sizeOf(context).width / 25);
|
||||||
final backGroundColor = Theme.of(context).colorScheme.surface.withOpacity(0.8);
|
final backGroundColor = Theme.of(context).colorScheme.surface.withValues(alpha: 0.8);
|
||||||
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
|
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
|
||||||
final minHeight = 450.0.clamp(0, MediaQuery.sizeOf(context).height).toDouble();
|
final minHeight = 450.0.clamp(0, MediaQuery.sizeOf(context).height).toDouble();
|
||||||
final maxHeight = MediaQuery.sizeOf(context).height - 10;
|
final maxHeight = MediaQuery.sizeOf(context).height - 10;
|
||||||
|
|
@ -114,7 +114,7 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
||||||
Colors.white,
|
Colors.white,
|
||||||
Colors.white,
|
Colors.white,
|
||||||
Colors.white,
|
Colors.white,
|
||||||
Colors.white.withOpacity(0),
|
Colors.white.withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
).createShader(bounds),
|
).createShader(bounds),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
|
|
@ -145,10 +145,10 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.surface.withOpacity(0),
|
Theme.of(context).colorScheme.surface.withValues(alpha: 0),
|
||||||
Theme.of(context).colorScheme.surface.withOpacity(0.10),
|
Theme.of(context).colorScheme.surface.withValues(alpha: 0.10),
|
||||||
Theme.of(context).colorScheme.surface.withOpacity(0.35),
|
Theme.of(context).colorScheme.surface.withValues(alpha: 0.35),
|
||||||
Theme.of(context).colorScheme.surface.withOpacity(0.85),
|
Theme.of(context).colorScheme.surface.withValues(alpha: 0.85),
|
||||||
Theme.of(context).colorScheme.surface,
|
Theme.of(context).colorScheme.surface,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class _FilePickerBarState extends ConsumerState<FilePickerBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final offColor = Theme.of(context).colorScheme.secondaryContainer;
|
final offColor = Theme.of(context).colorScheme.secondaryContainer;
|
||||||
final onColor = Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.7);
|
final onColor = Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.7);
|
||||||
final contentColor = Theme.of(context).colorScheme.onSecondaryContainer;
|
final contentColor = Theme.of(context).colorScheme.onSecondaryContainer;
|
||||||
return DropTarget(
|
return DropTarget(
|
||||||
enable: !inputField,
|
enable: !inputField,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class FlatButton extends ConsumerWidget {
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
onSecondaryTapDown: onSecondaryTapDown,
|
onSecondaryTapDown: onSecondaryTapDown,
|
||||||
borderRadius: borderRadiusGeometry ?? BorderRadius.circular(10),
|
borderRadius: borderRadiusGeometry ?? BorderRadius.circular(10),
|
||||||
splashColor: splashColor ?? Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
splashColor: splashColor ?? Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
|
||||||
hoverColor: showFeedback ? null : Colors.transparent,
|
hoverColor: showFeedback ? null : Colors.transparent,
|
||||||
splashFactory: InkSparkle.splashFactory,
|
splashFactory: InkSparkle.splashFactory,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class IntInputField extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Card(
|
return Card(
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.25),
|
color: Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.25),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
|
|
@ -11,7 +12,6 @@ import 'package:fladder/util/fladder_image.dart';
|
||||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/themes_data.dart';
|
import 'package:fladder/util/themes_data.dart';
|
||||||
import 'package:fladder/widgets/shared/fladder_carousel.dart';
|
|
||||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||||
|
|
||||||
|
|
@ -31,132 +31,190 @@ class CarouselBanner extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
class _CarouselBannerState extends ConsumerState<CarouselBanner> {
|
||||||
|
final carouselController = CarouselController();
|
||||||
|
bool showControls = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ConstrainedBox(
|
return MouseRegion(
|
||||||
constraints: BoxConstraints(maxHeight: widget.maxHeight),
|
onEnter: (event) => setState(() => showControls = true),
|
||||||
child: LayoutBuilder(
|
onExit: (event) => setState(() => showControls = false),
|
||||||
builder: (context, constraints) {
|
child: ConstrainedBox(
|
||||||
final maxExtent = (constraints.maxHeight * 2.1).clamp(
|
constraints: BoxConstraints(maxHeight: widget.maxHeight),
|
||||||
250.0,
|
child: LayoutBuilder(
|
||||||
(MediaQuery.sizeOf(context).shortestSide * 0.75).clamp(251.0, double.maxFinite),
|
builder: (context, constraints) {
|
||||||
);
|
final maxExtent = (constraints.maxHeight * 2.1).clamp(
|
||||||
final border = BorderRadius.circular(18);
|
250.0,
|
||||||
return FladderCarousel(
|
(MediaQuery.sizeOf(context).shortestSide * 0.75).clamp(251.0, double.maxFinite),
|
||||||
elevation: 3,
|
);
|
||||||
shrinkExtent: 0,
|
final border = BorderRadius.circular(18);
|
||||||
shape: RoundedRectangleBorder(borderRadius: border),
|
final itemExtent = widget.items.length == 1 ? MediaQuery.sizeOf(context).width : maxExtent;
|
||||||
itemPadding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 4).copyWith(top: AdaptiveLayout.of(context).isDesktop ? 6 : 10),
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 4)
|
||||||
itemExtent: widget.items.length == 1 ? MediaQuery.sizeOf(context).width : maxExtent,
|
.copyWith(top: AdaptiveLayout.of(context).isDesktop ? 6 : 10),
|
||||||
children: [
|
child: Stack(
|
||||||
...widget.items.mapIndexed(
|
children: [
|
||||||
(index, item) => LayoutBuilder(builder: (context, constraints) {
|
CarouselView(
|
||||||
final opacity = (constraints.maxWidth / maxExtent);
|
elevation: 3,
|
||||||
return Stack(
|
shrinkExtent: 0,
|
||||||
clipBehavior: Clip.none,
|
controller: carouselController,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: border),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
enableSplash: false,
|
||||||
|
itemExtent: itemExtent,
|
||||||
children: [
|
children: [
|
||||||
FladderImage(image: item.bannerImage),
|
...widget.items.mapIndexed(
|
||||||
Opacity(
|
(index, item) => LayoutBuilder(builder: (context, constraints) {
|
||||||
opacity: opacity.clamp(0, 1),
|
final opacity = (constraints.maxWidth / maxExtent);
|
||||||
child: Stack(
|
return Stack(
|
||||||
children: [
|
clipBehavior: Clip.none,
|
||||||
Positioned.fill(
|
children: [
|
||||||
child: Container(
|
FladderImage(image: item.bannerImage),
|
||||||
decoration: BoxDecoration(
|
Opacity(
|
||||||
gradient: LinearGradient(
|
opacity: opacity.clamp(0, 1),
|
||||||
begin: Alignment.bottomLeft,
|
child: Stack(
|
||||||
end: Alignment.topCenter,
|
children: [
|
||||||
colors: [
|
Positioned.fill(
|
||||||
ThemesData.of(context).dark.colorScheme.primaryContainer.withOpacity(0.85),
|
child: Container(
|
||||||
Colors.transparent,
|
decoration: BoxDecoration(
|
||||||
],
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomLeft,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
ThemesData.of(context)
|
||||||
|
.dark
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer
|
||||||
|
.withValues(alpha: 0.85),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0).copyWith(right: constraints.maxWidth * 0.2),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.title,
|
||||||
|
maxLines: 2,
|
||||||
|
softWrap: item.title.length > 25,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
||||||
|
),
|
||||||
|
if (item.label(context) != null || item.subText != null)
|
||||||
|
Text(
|
||||||
|
item.label(context) ?? item.subText ?? "",
|
||||||
|
maxLines: 2,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white),
|
||||||
|
),
|
||||||
|
].addInBetween(const SizedBox(height: 4)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
FlatButton(
|
||||||
],
|
onTap: () => widget.items[index].navigateTo(context),
|
||||||
),
|
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
||||||
),
|
? null
|
||||||
Align(
|
: () {
|
||||||
alignment: Alignment.bottomLeft,
|
final poster = widget.items[index];
|
||||||
child: Padding(
|
showBottomSheetPill(
|
||||||
padding: const EdgeInsets.all(16.0).copyWith(right: constraints.maxWidth * 0.2),
|
context: context,
|
||||||
child: Column(
|
item: poster,
|
||||||
mainAxisSize: MainAxisSize.min,
|
content: (scrollContext, scrollController) => ListView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
shrinkWrap: true,
|
||||||
children: [
|
controller: scrollController,
|
||||||
Text(
|
children: poster
|
||||||
item.title,
|
.generateActions(context, ref)
|
||||||
maxLines: 2,
|
.listTileItems(scrollContext, useIcons: true),
|
||||||
softWrap: item.title.length > 25,
|
),
|
||||||
overflow: TextOverflow.fade,
|
);
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
},
|
||||||
|
onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
||||||
|
? null
|
||||||
|
: (details) async {
|
||||||
|
Offset localPosition = details.globalPosition;
|
||||||
|
RelativeRect position = RelativeRect.fromLTRB(localPosition.dx - 320,
|
||||||
|
localPosition.dy, localPosition.dx, localPosition.dy);
|
||||||
|
final poster = widget.items[index];
|
||||||
|
|
||||||
|
await showMenu(
|
||||||
|
context: context,
|
||||||
|
position: position,
|
||||||
|
items: poster.generateActions(context, ref).popupMenuItems(useIcons: true),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (item.label(context) != null || item.subText != null)
|
BannerPlayButton(item: widget.items[index]),
|
||||||
Text(
|
IgnorePointer(
|
||||||
item.label(context) ?? item.subText ?? "",
|
child: Container(
|
||||||
maxLines: 2,
|
decoration: BoxDecoration(
|
||||||
softWrap: false,
|
border: Border.all(
|
||||||
overflow: TextOverflow.fade,
|
color: Colors.white.withValues(alpha: 0.1),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white),
|
width: 1.0,
|
||||||
|
),
|
||||||
|
borderRadius: border),
|
||||||
),
|
),
|
||||||
].addInBetween(const SizedBox(height: 4)),
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer)
|
||||||
|
AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
opacity: showControls ? 1 : 0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: !showControls,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton.filledTonal(
|
||||||
|
onPressed: () {
|
||||||
|
final currentPos = carouselController.position;
|
||||||
|
carouselController.animateTo(currentPos.pixels - itemExtent,
|
||||||
|
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
|
||||||
|
},
|
||||||
|
icon: const Icon(IconsaxOutline.arrow_left_2),
|
||||||
|
),
|
||||||
|
IconButton.filledTonal(
|
||||||
|
onPressed: () {
|
||||||
|
final currentPos = carouselController.position;
|
||||||
|
carouselController.animateTo(currentPos.pixels + itemExtent,
|
||||||
|
curve: Curves.easeInOutCubic, duration: const Duration(milliseconds: 250));
|
||||||
|
},
|
||||||
|
icon: const Icon(IconsaxOutline.arrow_right_3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FlatButton(
|
),
|
||||||
onTap: () => widget.items[index].navigateTo(context),
|
],
|
||||||
onLongPress: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
),
|
||||||
? null
|
);
|
||||||
: () {
|
},
|
||||||
final poster = widget.items[index];
|
),
|
||||||
showBottomSheetPill(
|
|
||||||
context: context,
|
|
||||||
item: poster,
|
|
||||||
content: (scrollContext, scrollController) => ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
controller: scrollController,
|
|
||||||
children: poster
|
|
||||||
.generateActions(context, ref)
|
|
||||||
.listTileItems(scrollContext, useIcons: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSecondaryTapDown: AdaptiveLayout.of(context).inputDevice == InputDevice.touch
|
|
||||||
? null
|
|
||||||
: (details) async {
|
|
||||||
Offset localPosition = details.globalPosition;
|
|
||||||
RelativeRect position = RelativeRect.fromLTRB(
|
|
||||||
localPosition.dx - 320, localPosition.dy, localPosition.dx, localPosition.dy);
|
|
||||||
final poster = widget.items[index];
|
|
||||||
|
|
||||||
await showMenu(
|
|
||||||
context: context,
|
|
||||||
position: position,
|
|
||||||
items: poster.generateActions(context, ref).popupMenuItems(useIcons: true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
BannerPlayButton(item: widget.items[index]),
|
|
||||||
IgnorePointer(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.white.withOpacity(0.1),
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
borderRadius: border),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class ChapterRow extends ConsumerWidget {
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
color: Theme.of(context).cardColor.withOpacity(0.4),
|
color: Theme.of(context).cardColor.withValues(alpha: 0.4),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class ChipButton extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Card(
|
return Card(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.15),
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.15),
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
onTap: onPressed,
|
onTap: onPressed,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class MediaHeader extends ConsumerWidget {
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 30,
|
elevation: 30,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(150)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(150)),
|
||||||
shadowColor: Colors.black.withOpacity(0.3),
|
shadowColor: Colors.black.withValues(alpha: 0.3),
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ class MediaPlayButton extends ConsumerWidget {
|
||||||
ButtonStyle(
|
ButtonStyle(
|
||||||
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary),
|
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary),
|
||||||
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary),
|
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary),
|
||||||
|
iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary),
|
||||||
),
|
),
|
||||||
Theme.of(context).colorScheme.onPrimary,
|
Theme.of(context).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: Colors.white.withOpacity(0.10),
|
color: Colors.white.withValues(alpha: 0.10),
|
||||||
),
|
),
|
||||||
borderRadius: FladderTheme.defaultShape.borderRadius,
|
borderRadius: FladderTheme.defaultShape.borderRadius,
|
||||||
),
|
),
|
||||||
|
|
@ -133,7 +133,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
||||||
if (widget.selected == true)
|
if (widget.selected == true)
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.15),
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
||||||
borderRadius: FladderTheme.defaultShape.borderRadius,
|
borderRadius: FladderTheme.defaultShape.borderRadius,
|
||||||
),
|
),
|
||||||
|
|
@ -191,7 +191,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 7.5,
|
minHeight: 7.5,
|
||||||
backgroundColor: Theme.of(context).colorScheme.onPrimary.withOpacity(0.5),
|
backgroundColor: Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.5),
|
||||||
value: poster.userData.progress / 100,
|
value: poster.userData.progress / 100,
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
),
|
),
|
||||||
|
|
@ -213,7 +213,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
||||||
//Hover color overlay
|
//Hover color overlay
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.55),
|
color: Colors.black.withValues(alpha: 0.55),
|
||||||
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
|
||||||
borderRadius: FladderTheme.defaultShape.borderRadius,
|
borderRadius: FladderTheme.defaultShape.borderRadius,
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
backgroundColor: Colors.black.withOpacity(0.75),
|
backgroundColor: Colors.black.withValues(alpha: 0.75),
|
||||||
value: episode.userData.progress / 100,
|
value: episode.userData.progress / 100,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -260,7 +260,7 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
Icons.more_vert,
|
Icons.more_vert,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(color: Colors.black.withOpacity(0.45), blurRadius: 8.0),
|
Shadow(color: Colors.black.withValues(alpha: 0.45), blurRadius: 8.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 16.0),
|
const Shadow(color: Colors.black, blurRadius: 16.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 32.0),
|
const Shadow(color: Colors.black, blurRadius: 32.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 64.0),
|
const Shadow(color: Colors.black, blurRadius: 64.0),
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,12 @@ class _ExpandingOverviewState extends ConsumerState<ExpandingOverview> {
|
||||||
stops: const [0, 1],
|
stops: const [0, 1],
|
||||||
colors: [
|
colors: [
|
||||||
color,
|
color,
|
||||||
color.withOpacity(!canExpand
|
color.withValues(
|
||||||
? 1
|
alpha: !canExpand
|
||||||
: expanded
|
|
||||||
? 1
|
? 1
|
||||||
: 0),
|
: expanded
|
||||||
|
? 1
|
||||||
|
: 0),
|
||||||
],
|
],
|
||||||
).createShader(bounds),
|
).createShader(bounds),
|
||||||
child: HtmlWidget(
|
child: HtmlWidget(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as customtab;
|
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as customtab;
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart' as urllauncher;
|
import 'package:url_launcher/url_launcher.dart' as urilauncher;
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
|
|
@ -49,26 +49,22 @@ Future<void> launchUrl(BuildContext context, String link) async {
|
||||||
final Uri url = Uri.parse(link);
|
final Uri url = Uri.parse(link);
|
||||||
|
|
||||||
if (AdaptiveLayout.of(context).isDesktop) {
|
if (AdaptiveLayout.of(context).isDesktop) {
|
||||||
if (!await urllauncher.launchUrl(url, mode: LaunchMode.externalApplication)) {
|
if (!await urilauncher.launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||||
throw Exception('Could not launch $url');
|
throw Exception('Could not launch $url');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await customtab.launch(
|
await customtab.launchUrl(
|
||||||
link,
|
Uri.parse(link),
|
||||||
customTabsOption: customtab.CustomTabsOption(
|
customTabsOptions: customtab.CustomTabsOptions(
|
||||||
toolbarColor: Theme.of(context).primaryColor,
|
colorSchemes: customtab.CustomTabsColorSchemes.defaults(
|
||||||
enableDefaultShare: true,
|
toolbarColor: Theme.of(context).primaryColor,
|
||||||
enableUrlBarHiding: true,
|
),
|
||||||
showPageTitle: true,
|
urlBarHidingEnabled: true,
|
||||||
extraCustomTabs: const <String>[
|
shareState: customtab.CustomTabsShareState.browserDefault,
|
||||||
// ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox
|
showTitle: true,
|
||||||
'org.mozilla.firefox',
|
|
||||||
// ref. https://play.google.com/store/apps/details?id=com.microsoft.emmx
|
|
||||||
'com.microsoft.emmx',
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
safariVCOption: customtab.SafariViewControllerOption(
|
safariVCOptions: customtab.SafariViewControllerOptions(
|
||||||
preferredBarTintColor: Theme.of(context).primaryColor,
|
preferredBarTintColor: Theme.of(context).primaryColor,
|
||||||
preferredControlTintColor: Colors.white,
|
preferredControlTintColor: Colors.white,
|
||||||
barCollapsingEnabled: true,
|
barCollapsingEnabled: true,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:fladder/util/fladder_image.dart';
|
||||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/themes_data.dart';
|
import 'package:fladder/util/themes_data.dart';
|
||||||
import 'package:fladder/widgets/shared/fladder_carousel.dart';
|
|
||||||
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
||||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||||
|
|
@ -81,8 +80,6 @@ class _MediaBannerState extends ConsumerState<MediaBanner> {
|
||||||
timer.reset();
|
timer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
final controller = FladderCarouselController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final overlayColor = ThemesData.of(context).dark.colorScheme.primaryContainer;
|
final overlayColor = ThemesData.of(context).dark.colorScheme.primaryContainer;
|
||||||
|
|
@ -141,12 +138,13 @@ class _MediaBannerState extends ConsumerState<MediaBanner> {
|
||||||
foregroundDecoration: BoxDecoration(
|
foregroundDecoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.white.withOpacity(0.10), strokeAlign: BorderSide.strokeAlignInside),
|
color: Colors.white.withValues(alpha: 0.10),
|
||||||
|
strokeAlign: BorderSide.strokeAlignInside),
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.bottomLeft,
|
begin: Alignment.bottomLeft,
|
||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
overlayColor.withOpacity(0.85),
|
overlayColor.withValues(alpha: 0.85),
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -238,7 +236,7 @@ class _MediaBannerState extends ConsumerState<MediaBanner> {
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
shadows: shadows,
|
shadows: shadows,
|
||||||
color: Colors.white.withOpacity(0.75),
|
color: Colors.white.withValues(alpha: 0.75),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class PosterListItem extends ConsumerWidget {
|
||||||
height: 75 * ref.read(clientSettingsProvider.select((value) => value.posterSize)),
|
height: 75 * ref.read(clientSettingsProvider.select((value) => value.posterSize)),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(selected == true ? 0.25 : 0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: selected == true ? 0.25 : 0),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Card(
|
child: Card(
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.65),
|
color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.65),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -159,7 +159,7 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
Icons.more_vert,
|
Icons.more_vert,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(color: Colors.black.withOpacity(0.45), blurRadius: 8.0),
|
Shadow(color: Colors.black.withValues(alpha: 0.45), blurRadius: 8.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 16.0),
|
const Shadow(color: Colors.black, blurRadius: 16.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 32.0),
|
const Shadow(color: Colors.black, blurRadius: 32.0),
|
||||||
const Shadow(color: Colors.black, blurRadius: 64.0),
|
const Shadow(color: Colors.black, blurRadius: 64.0),
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class _OutlinedTextFieldState extends ConsumerState<OutlinedTextField> {
|
||||||
|
|
||||||
Color getColor() {
|
Color getColor() {
|
||||||
if (widget.errorText != null) return Theme.of(context).colorScheme.errorContainer;
|
if (widget.errorText != null) return Theme.of(context).colorScheme.errorContainer;
|
||||||
return Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.25);
|
return Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -112,31 +112,31 @@ class _OutlinedTextFieldState extends ConsumerState<OutlinedTextField> {
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0),
|
||||||
width: widget.borderWidth,
|
width: widget.borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0),
|
||||||
width: widget.borderWidth,
|
width: widget.borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0),
|
||||||
width: widget.borderWidth,
|
width: widget.borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0),
|
||||||
width: widget.borderWidth,
|
width: widget.borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedErrorBorder: OutlineInputBorder(
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0),
|
||||||
width: widget.borderWidth,
|
width: widget.borderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
value: combinedStream.progress,
|
value: combinedStream.progress,
|
||||||
strokeWidth: 8,
|
strokeWidth: 8,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.5),
|
||||||
strokeCap: StrokeCap.round,
|
strokeCap: StrokeCap.round,
|
||||||
color: combinedStream.status.color(context),
|
color: combinedStream.status.color(context),
|
||||||
),
|
),
|
||||||
|
|
@ -212,6 +212,7 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDefaultAlertDialog(
|
showDefaultAlertDialog(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
syncedItem: syncedItem,
|
syncedItem: syncedItem,
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.2),
|
color: Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.2),
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
child: Dismissible(
|
child: Dismissible(
|
||||||
background: Container(
|
background: Container(
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class SyncLabel extends ConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: status.color.withOpacity(0.15),
|
color: status.color.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
@ -109,7 +109,8 @@ class SyncSubtitle extends ConsumerWidget {
|
||||||
final children = syncItem.nestedChildren(ref);
|
final children = syncItem.nestedChildren(ref);
|
||||||
final syncStatus = ref.watch(syncStatusesProvider(syncItem)).value ?? SyncStatus.partially;
|
final syncStatus = ref.watch(syncStatusesProvider(syncItem)).value ?? SyncStatus.partially;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(color: syncStatus.color.withOpacity(0.15), borderRadius: BorderRadius.circular(10)),
|
decoration:
|
||||||
|
BoxDecoration(color: syncStatus.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10)),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: const Color.fromARGB(0, 208, 130, 130),
|
color: const Color.fromARGB(0, 208, 130, 130),
|
||||||
textStyle:
|
textStyle:
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class SyncStatusOverlay extends ConsumerWidget {
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
semanticContainer: false,
|
semanticContainer: false,
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withValues(alpha: 0.6),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
@ -44,7 +44,7 @@ class SyncStatusOverlay extends ConsumerWidget {
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
semanticContainer: false,
|
semanticContainer: false,
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withValues(alpha: 0.6),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ class _VideoPlayerNextWrapperState extends ConsumerState<VideoPlayerNextWrapper>
|
||||||
|
|
||||||
Future<void> clearOverlaySettings() async {
|
Future<void> clearOverlaySettings() async {
|
||||||
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
} else {
|
} else {
|
||||||
closeFullScreen();
|
closeFullScreen();
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ class _VideoPlayerNextWrapperState extends ConsumerState<VideoPlayerNextWrapper>
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerLowest.withOpacity(0.2),
|
color: Theme.of(context).colorScheme.surfaceContainerLowest.withValues(alpha: 0.2),
|
||||||
),
|
),
|
||||||
if (nextUp != null)
|
if (nextUp != null)
|
||||||
AnimatedAlign(
|
AnimatedAlign(
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ Future<void> showSubSelection(BuildContext context) {
|
||||||
final selected = playbackModel.mediaStreams?.defaultSubStreamIndex == subModel.index;
|
final selected = playbackModel.mediaStreams?.defaultSubStreamIndex == subModel.index;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(subModel.label(context)),
|
title: Text(subModel.label(context)),
|
||||||
tileColor: selected ? Theme.of(context).colorScheme.primary.withOpacity(0.3) : null,
|
tileColor: selected ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.3) : null,
|
||||||
subtitle: subModel.language.isNotEmpty
|
subtitle: subModel.language.isNotEmpty
|
||||||
? Opacity(opacity: 0.6, child: Text(subModel.language.capitalize()))
|
? Opacity(opacity: 0.6, child: Text(subModel.language.capitalize()))
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -434,7 +434,7 @@ Future<void> showAudioSelection(BuildContext context) {
|
||||||
final selected = playbackModel.mediaStreams?.defaultAudioStreamIndex == audioStream.index;
|
final selected = playbackModel.mediaStreams?.defaultAudioStreamIndex == audioStream.index;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(audioStream.label(context)),
|
title: Text(audioStream.label(context)),
|
||||||
tileColor: selected ? Theme.of(context).colorScheme.primary.withOpacity(0.3) : null,
|
tileColor: selected ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.3) : null,
|
||||||
subtitle: audioStream.language.isNotEmpty
|
subtitle: audioStream.language.isNotEmpty
|
||||||
? Opacity(opacity: 0.6, child: Text(audioStream.language.capitalize()))
|
? Opacity(opacity: 0.6, child: Text(audioStream.language.capitalize()))
|
||||||
: null,
|
: null,
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.85),
|
color: Colors.black.withValues(alpha: 0.85),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/chapters_model.dart';
|
import 'package:fladder/models/items/chapters_model.dart';
|
||||||
|
|
@ -163,8 +162,8 @@ class _ChapterProgressSliderState extends ConsumerState<VideoProgressBar> {
|
||||||
.clamp(1, constraints.maxWidth),
|
.clamp(1, constraints.maxWidth),
|
||||||
height: sliderHeight,
|
height: sliderHeight,
|
||||||
child: GappedContainerShape(
|
child: GappedContainerShape(
|
||||||
activeColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
activeColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
|
||||||
inActiveColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
inActiveColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
|
||||||
thumbPosition: bufferFraction,
|
thumbPosition: bufferFraction,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -196,7 +195,7 @@ class _ChapterProgressSliderState extends ConsumerState<VideoProgressBar> {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: activePosition
|
color: activePosition
|
||||||
? Theme.of(context).colorScheme.onPrimary
|
? Theme.of(context).colorScheme.onPrimary
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
height: constraints.maxHeight,
|
height: constraints.maxHeight,
|
||||||
width: sliderHeight - (activePosition ? 2 : 4),
|
width: sliderHeight - (activePosition ? 2 : 4),
|
||||||
|
|
@ -204,7 +203,7 @@ class _ChapterProgressSliderState extends ConsumerState<VideoProgressBar> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).whereNotNull(),
|
).nonNulls,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Future<void> showSubtitleControls({
|
||||||
}) async {
|
}) async {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierColor: Colors.black.withOpacity(0.1),
|
barrierColor: Colors.black.withValues(alpha: 0.1),
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
|
@ -60,7 +60,7 @@ class _VideoSubtitleControlsState extends ConsumerState<VideoSubtitleControls> {
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: controlsHidden ? Theme.of(context).dialogBackgroundColor.withOpacity(0.75) : Colors.transparent,
|
color: controlsHidden ? Theme.of(context).dialogBackgroundColor.withValues(alpha: 0.75) : Colors.transparent,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -220,8 +220,8 @@ class _VideoSubtitleControlsState extends ConsumerState<VideoSubtitleControls> {
|
||||||
const Icon(Icons.border_color_rounded),
|
const Icon(Icons.border_color_rounded),
|
||||||
...[Colors.white, Colors.yellow, Colors.black, Colors.grey, Colors.transparent].map(
|
...[Colors.white, Colors.yellow, Colors.black, Colors.grey, Colors.transparent].map(
|
||||||
(e) => FlatButton(
|
(e) => FlatButton(
|
||||||
onTap: () =>
|
onTap: () => provider
|
||||||
provider.setOutlineColor(e == Colors.transparent ? e : e.withOpacity(0.85)),
|
.setOutlineColor(e == Colors.transparent ? e : e.withValues(alpha: 0.85)),
|
||||||
borderRadiusGeometry: BorderRadius.circular(5),
|
borderRadiusGeometry: BorderRadius.circular(5),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -284,7 +284,7 @@ class _VideoSubtitleControlsState extends ConsumerState<VideoSubtitleControls> {
|
||||||
divisions: 20,
|
divisions: 20,
|
||||||
onChangeStart: (value) => setOpacity(const Key('backGroundOpacity')),
|
onChangeStart: (value) => setOpacity(const Key('backGroundOpacity')),
|
||||||
onChangeEnd: (value) => setOpacity(null),
|
onChangeEnd: (value) => setOpacity(null),
|
||||||
value: subSettings.backGroundColor.opacity.clamp(0, 1),
|
value: subSettings.backGroundColor.a.clamp(0, 1),
|
||||||
onChanged: (value) => provider.setBackGroundOpacity(value),
|
onChanged: (value) => provider.setBackGroundOpacity(value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -293,7 +293,7 @@ class _VideoSubtitleControlsState extends ConsumerState<VideoSubtitleControls> {
|
||||||
minWidth: 35,
|
minWidth: 35,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
subSettings.backGroundColor.opacity.toStringAsFixed(2),
|
subSettings.backGroundColor.a.toStringAsFixed(2),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:ficonsax/ficonsax.dart';
|
import 'package:ficonsax/ficonsax.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
@ -219,8 +218,8 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Colors.black.withOpacity(0.8),
|
Colors.black.withValues(alpha: 0.8),
|
||||||
Colors.black.withOpacity(0),
|
Colors.black.withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
@ -276,8 +275,8 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
begin: Alignment.bottomCenter,
|
begin: Alignment.bottomCenter,
|
||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Colors.black.withOpacity(0.8),
|
Colors.black.withValues(alpha: 0.8),
|
||||||
Colors.black.withOpacity(0),
|
Colors.black.withValues(alpha: 0),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
@ -425,7 +424,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
details.whereNotNull().join(' - '),
|
details.nonNulls.join(' - '),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
shadows: [
|
shadows: [
|
||||||
|
|
@ -506,7 +505,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.95),
|
color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.95),
|
||||||
),
|
),
|
||||||
textStyle: Theme.of(context).textTheme.labelLarge,
|
textStyle: Theme.of(context).textTheme.labelLarge,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
|
@ -533,7 +532,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.95),
|
color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.95),
|
||||||
),
|
),
|
||||||
textStyle: Theme.of(context).textTheme.labelLarge,
|
textStyle: Theme.of(context).textTheme.labelLarge,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
|
@ -625,7 +624,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
Future<void> clearOverlaySettings() async {
|
Future<void> clearOverlaySettings() async {
|
||||||
toggleOverlay(value: true);
|
toggleOverlay(value: true);
|
||||||
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
} else {
|
} else {
|
||||||
disableFullScreen();
|
disableFullScreen();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,12 @@ class MusicMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlaybackStatus {
|
enum PlaybackStatus {
|
||||||
Closed,
|
closed,
|
||||||
Changing,
|
changing,
|
||||||
Stopped,
|
stopped,
|
||||||
Playing,
|
playing,
|
||||||
Paused,
|
paused,
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PressedButton {
|
enum PressedButton {
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class FladderTheme {
|
||||||
),
|
),
|
||||||
buttonTheme: ButtonThemeData(shape: defaultShape),
|
buttonTheme: ButtonThemeData(shape: defaultShape),
|
||||||
chipTheme: ChipThemeData(
|
chipTheme: ChipThemeData(
|
||||||
side: BorderSide(width: 1, color: scheme?.onSurface.withOpacity(0.05) ?? Colors.white),
|
side: BorderSide(width: 1, color: scheme?.onSurface.withValues(alpha: 0.05) ?? Colors.white),
|
||||||
shape: defaultShape,
|
shape: defaultShape,
|
||||||
),
|
),
|
||||||
popupMenuTheme: PopupMenuThemeData(
|
popupMenuTheme: PopupMenuThemeData(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class AbsorbEvents extends ConsumerWidget {
|
||||||
onDoubleTap: () {},
|
onDoubleTap: () {},
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
onLongPress: () {},
|
onLongPress: () {},
|
||||||
child: Container(color: Colors.black.withOpacity(0), child: child),
|
child: Container(color: Colors.black.withValues(alpha: 0), child: child),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return child;
|
return child;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,37 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
|
||||||
|
Color? colorFromJson(dynamic color) {
|
||||||
|
if (color == null) return null;
|
||||||
|
|
||||||
|
if (color is Map<String, dynamic>) {
|
||||||
|
return Color.from(
|
||||||
|
alpha: color['alpha'] ?? 1.0,
|
||||||
|
red: color['red'] ?? 1.0,
|
||||||
|
green: color['green'] ?? 1.0,
|
||||||
|
blue: color['blue'] ?? 1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated format (integer value)
|
||||||
|
if (color is int) {
|
||||||
|
return Color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ColorExtensions on Color {
|
||||||
|
Map<String, dynamic> get toMap {
|
||||||
|
return {
|
||||||
|
'alpha': a,
|
||||||
|
'red': r,
|
||||||
|
'green': g,
|
||||||
|
'blue': b,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension DynamicSchemeVariantExtension on DynamicSchemeVariant {
|
extension DynamicSchemeVariantExtension on DynamicSchemeVariant {
|
||||||
String label(BuildContext context) => switch (this) {
|
String label(BuildContext context) => switch (this) {
|
||||||
DynamicSchemeVariant.tonalSpot => context.localized.schemeSettingsTonalSpot,
|
DynamicSchemeVariant.tonalSpot => context.localized.schemeSettingsTonalSpot,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class DebugBanner extends ConsumerWidget {
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||||
color: Colors.purpleAccent.withOpacity(0.8),
|
color: Colors.purpleAccent.withValues(alpha: 0.8),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class FloatingActionButtonAnimated extends ConsumerWidget {
|
class FloatingActionButtonAnimated extends ConsumerWidget {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
extension DurationExtensions on Duration? {
|
extension DurationExtensions on Duration? {
|
||||||
String? get humanize {
|
String? get humanize {
|
||||||
if (this == null) return null;
|
if (this == null) return null;
|
||||||
|
|
@ -7,7 +5,7 @@ extension DurationExtensions on Duration? {
|
||||||
final hours = duration.inHours != 0 ? '${duration.inHours.toString()}h' : null;
|
final hours = duration.inHours != 0 ? '${duration.inHours.toString()}h' : null;
|
||||||
final minutes = duration.inMinutes % 60 != 0 ? '${duration.inMinutes % 60}m'.padLeft(3, '0') : null;
|
final minutes = duration.inMinutes % 60 != 0 ? '${duration.inMinutes % 60}m'.padLeft(3, '0') : null;
|
||||||
final seconds = duration.inSeconds % 60 != 0 ? '${duration.inSeconds % 60}s'.padLeft(3, '0') : null;
|
final seconds = duration.inSeconds % 60 != 0 ? '${duration.inSeconds % 60}s'.padLeft(3, '0') : null;
|
||||||
final result = [hours, minutes, seconds].whereNotNull().map((e) => e).join(' ');
|
final result = [hours, minutes, seconds].nonNulls.map((e) => e).join(' ');
|
||||||
return result.isNotEmpty ? result : null;
|
return result.isNotEmpty ? result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,7 +16,7 @@ extension DurationExtensions on Duration? {
|
||||||
final minutes = (duration.inMinutes % 60).toString().padLeft(2, '0');
|
final minutes = (duration.inMinutes % 60).toString().padLeft(2, '0');
|
||||||
final seconds = (duration.inHours == 0 ? duration.inSeconds % 60 : null)?.toString().padLeft(2, '0');
|
final seconds = (duration.inHours == 0 ? duration.inSeconds % 60 : null)?.toString().padLeft(2, '0');
|
||||||
|
|
||||||
final result = [hours, minutes, seconds].whereNotNull().map((e) => e).join(':');
|
final result = [hours, minutes, seconds].nonNulls.map((e) => e).join(':');
|
||||||
return result.isNotEmpty ? result : null;
|
return result.isNotEmpty ? result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
|
|
@ -69,9 +70,12 @@ class _KeyedListViewState extends ConsumerState<KeyedListView> {
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
|
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
iconColor: atPosition
|
||||||
|
? Theme.of(context).colorScheme.onSecondary
|
||||||
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.35),
|
||||||
foregroundColor: atPosition
|
foregroundColor: atPosition
|
||||||
? Theme.of(context).colorScheme.onSecondary
|
? Theme.of(context).colorScheme.onSecondary
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.35),
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.35),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
itemScrollController.scrollTo(
|
itemScrollController.scrollTo(
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class _MouseParkingState extends ConsumerState<MouseParking> {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20)),
|
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20)),
|
||||||
color: parked ? Theme.of(context).colorScheme.primary.withOpacity(0.5) : Colors.black12,
|
color: parked ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.5) : Colors.black12,
|
||||||
),
|
),
|
||||||
child: const Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
|
|
||||||
extension StringExtensions on String {
|
extension StringExtensions on String {
|
||||||
|
|
@ -64,6 +62,6 @@ extension GenreExtensions on List<GenreItems> {
|
||||||
|
|
||||||
extension StringListExtension on List<String?> {
|
extension StringListExtension on List<String?> {
|
||||||
String get detailsTitle {
|
String get detailsTitle {
|
||||||
return whereNotNull().join(" ● ");
|
return nonNulls.join(" ● ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class FladderAppBar extends StatelessWidget implements PreferredSize {
|
||||||
} else {
|
} else {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
toolbarHeight: 0,
|
toolbarHeight: 0,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0),
|
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0),
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
systemOverlayStyle: const SystemUiOverlayStyle(),
|
systemOverlayStyle: const SystemUiOverlayStyle(),
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ class _CurrentlyPlayingBarState extends ConsumerState<FloatingPlayerBar> {
|
||||||
opacity: showExpandButton ? 1 : 0,
|
opacity: showExpandButton ? 1 : 0,
|
||||||
duration: const Duration(milliseconds: 125),
|
duration: const Duration(milliseconds: 125),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withValues(alpha: 0.6),
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
onTap: () async => openFullScreenPlayer(),
|
onTap: () async => openFullScreenPlayer(),
|
||||||
child: const Icon(Icons.keyboard_arrow_up_rounded),
|
child: const Icon(Icons.keyboard_arrow_up_rounded),
|
||||||
|
|
@ -191,7 +191,7 @@ class _CurrentlyPlayingBarState extends ConsumerState<FloatingPlayerBar> {
|
||||||
),
|
),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
backgroundColor: Colors.black.withOpacity(0.25),
|
backgroundColor: Colors.black.withValues(alpha: 0.25),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
value: progress.clamp(0, 1),
|
value: progress.clamp(0, 1),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:fladder/util/widget_extensions.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/util/widget_extensions.dart';
|
||||||
|
|
||||||
class NavigationButton extends ConsumerStatefulWidget {
|
class NavigationButton extends ConsumerStatefulWidget {
|
||||||
final String? label;
|
final String? label;
|
||||||
final Widget selectedIcon;
|
final Widget selectedIcon;
|
||||||
|
|
@ -58,10 +60,16 @@ class _NavigationButtonState extends ConsumerState<NavigationButton> {
|
||||||
elevation: const WidgetStatePropertyAll(0),
|
elevation: const WidgetStatePropertyAll(0),
|
||||||
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
|
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
|
||||||
backgroundColor: const WidgetStatePropertyAll(Colors.transparent),
|
backgroundColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||||
|
iconSize: const WidgetStatePropertyAll(24),
|
||||||
|
iconColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
return widget.selected
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.45);
|
||||||
|
}),
|
||||||
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
foregroundColor: WidgetStateProperty.resolveWith((states) {
|
||||||
return widget.selected
|
return widget.selected
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.45);
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.45);
|
||||||
})),
|
})),
|
||||||
onPressed: widget.onPressed,
|
onPressed: widget.onPressed,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
|
|
@ -94,7 +102,7 @@ class _NavigationButtonState extends ConsumerState<NavigationButton> {
|
||||||
width: widget.selected ? 14 : 0,
|
width: widget.selected ? 14 : 0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(widget.selected ? 1 : 0),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: widget.selected ? 1 : 0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/providers/api_provider.dart';
|
import 'package:fladder/providers/api_provider.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/widgets/shared/filled_button_await.dart';
|
import 'package:fladder/widgets/shared/filled_button_await.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
Future<Response<dynamic>?> showDeleteDialog(BuildContext context, ItemBaseModel item, WidgetRef ref) async {
|
Future<Response<dynamic>?> showDeleteDialog(BuildContext context, ItemBaseModel item, WidgetRef ref) async {
|
||||||
Response<dynamic>? response;
|
Response<dynamic>? response;
|
||||||
|
|
@ -23,6 +25,7 @@ Future<Response<dynamic>?> showDeleteDialog(BuildContext context, ItemBaseModel
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
response = await ref.read(jellyApiProvider).deleteItem(item.id);
|
response = await ref.read(jellyApiProvider).deleteItem(item.id);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class AnimatedVisibilityIconState extends State<AnimatedVisibilityIcon> {
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: (_currentFilledState ? widget.filledColor : widget.outlinedColor)?.withOpacity(0.2),
|
color: (_currentFilledState ? widget.filledColor : widget.outlinedColor)?.withValues(alpha: 0.2),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ IconData getBackIcon(BuildContext context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
final _shadows = [
|
final _shadows = [
|
||||||
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.2)),
|
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withValues(alpha: 0.2)),
|
||||||
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.1)),
|
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withValues(alpha: 0.1)),
|
||||||
BoxShadow(blurRadius: 16, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
|
BoxShadow(blurRadius: 16, spreadRadius: 6, color: Colors.black.withValues(alpha: 0.2)),
|
||||||
];
|
];
|
||||||
|
|
||||||
class ElevatedIconButton extends ConsumerWidget {
|
class ElevatedIconButton extends ConsumerWidget {
|
||||||
|
|
@ -36,7 +36,7 @@ class ElevatedIconButton extends ConsumerWidget {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
style: IconButtonTheme.of(context).style?.copyWith(
|
style: IconButtonTheme.of(context).style?.copyWith(
|
||||||
backgroundColor: WidgetStatePropertyAll(color?.withOpacity(0.15)),
|
backgroundColor: WidgetStatePropertyAll(color?.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
color: color,
|
color: color,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
|
|
||||||
|
|
@ -1,760 +0,0 @@
|
||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
//This is a copy of the CarouselView widget with some minor changes.
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
|
|
||||||
class MyCustomScrollBehavior extends MaterialScrollBehavior {
|
|
||||||
@override
|
|
||||||
Set<PointerDeviceKind> get dragDevices => PointerDeviceKind.values.toSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Material Design carousel widget.
|
|
||||||
///
|
|
||||||
/// The [FladderCarousel] present a scrollable list of items, each of which can dynamically
|
|
||||||
/// change size based on the chosen layout.
|
|
||||||
///
|
|
||||||
/// This widget supports uncontained carousel layout. It shows items that scroll
|
|
||||||
/// to the edge of the container, behaving similarly to a [ListView] where all
|
|
||||||
/// children are a uniform size.
|
|
||||||
///
|
|
||||||
/// The [FladderCarouselController] is used to control the [FladderCarouselController.initialItem].
|
|
||||||
///
|
|
||||||
/// The [FladderCarousel.itemExtent] property must be non-null and defines the base
|
|
||||||
/// size of items. While items typically maintain this size, the first and last
|
|
||||||
/// visible items may be slightly compressed during scrolling. The [shrinkExtent]
|
|
||||||
/// property controls the minimum allowable size for these compressed items.
|
|
||||||
///
|
|
||||||
/// {@tool dartpad}
|
|
||||||
/// Here is an example of [FladderCarousel] to show the uncontained layout. Each carousel
|
|
||||||
/// item has the same size but can be "squished" to the [shrinkExtent] when they
|
|
||||||
/// are show on the view and out of view.
|
|
||||||
///
|
|
||||||
/// ** See code in examples/api/lib/material/carousel/carousel.0.dart **
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [FladderCarouselController], which controls the first visible item in the carousel.
|
|
||||||
/// * [PageView], which is a scrollable list that works page by page.
|
|
||||||
class FladderCarousel extends StatefulWidget {
|
|
||||||
/// Creates a Material Design carousel.
|
|
||||||
const FladderCarousel({
|
|
||||||
super.key,
|
|
||||||
this.itemPadding,
|
|
||||||
this.padding = EdgeInsets.zero,
|
|
||||||
this.backgroundColor,
|
|
||||||
this.elevation,
|
|
||||||
this.shape,
|
|
||||||
this.overlayColor,
|
|
||||||
this.itemSnapping = false,
|
|
||||||
this.shrinkExtent = 0.0,
|
|
||||||
this.controller,
|
|
||||||
this.scrollDirection = Axis.horizontal,
|
|
||||||
this.reverse = false,
|
|
||||||
this.onTap,
|
|
||||||
this.onLongPress,
|
|
||||||
this.onSecondaryTap,
|
|
||||||
required this.itemExtent,
|
|
||||||
required this.children,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The amount of space to surround each carousel item with.
|
|
||||||
///
|
|
||||||
/// Defaults to [EdgeInsets.all] of 4 pixels.
|
|
||||||
final EdgeInsets? itemPadding;
|
|
||||||
|
|
||||||
final EdgeInsets padding;
|
|
||||||
|
|
||||||
/// The background color for each carousel item.
|
|
||||||
///
|
|
||||||
/// Defaults to [ColorScheme.surface].
|
|
||||||
final Color? backgroundColor;
|
|
||||||
|
|
||||||
/// The z-coordinate of each carousel item.
|
|
||||||
///
|
|
||||||
/// Defaults to 0.0.
|
|
||||||
final double? elevation;
|
|
||||||
|
|
||||||
/// The shape of each carousel item's [Material].
|
|
||||||
///
|
|
||||||
/// Defines each item's [Material.shape].
|
|
||||||
///
|
|
||||||
/// Defaults to a [RoundedRectangleBorder] with a circular corner radius
|
|
||||||
/// of 28.0.
|
|
||||||
final ShapeBorder? shape;
|
|
||||||
|
|
||||||
/// The highlight color to indicate the carousel items are in pressed, hovered
|
|
||||||
/// or focused states.
|
|
||||||
///
|
|
||||||
/// The default values are:
|
|
||||||
/// * [WidgetState.pressed] - [ColorScheme.onSurface] with an opacity of 0.1
|
|
||||||
/// * [WidgetState.hovered] - [ColorScheme.onSurface] with an opacity of 0.08
|
|
||||||
/// * [WidgetState.focused] - [ColorScheme.onSurface] with an opacity of 0.1
|
|
||||||
final WidgetStateProperty<Color?>? overlayColor;
|
|
||||||
|
|
||||||
/// The minimum allowable extent (size) in the main axis for carousel items
|
|
||||||
/// during scrolling transitions.
|
|
||||||
///
|
|
||||||
/// As the carousel scrolls, the first visible item is pinned and gradually
|
|
||||||
/// shrinks until it reaches this minimum extent before scrolling off-screen.
|
|
||||||
/// Similarly, the last visible item enters the viewport at this minimum size
|
|
||||||
/// and expands to its full [itemExtent].
|
|
||||||
///
|
|
||||||
/// In cases where the remaining viewport space for the last visible item is
|
|
||||||
/// larger than the defined [shrinkExtent], the [shrinkExtent] is dynamically
|
|
||||||
/// adjusted to match this remaining space, ensuring a smooth size transition.
|
|
||||||
///
|
|
||||||
/// Defaults to 0.0. Setting to 0.0 allows items to shrink/expand completely,
|
|
||||||
/// transitioning between 0.0 and the full [itemExtent]. In cases where the
|
|
||||||
/// remaining viewport space for the last visible item is larger than the
|
|
||||||
/// defined [shrinkExtent], the [shrinkExtent] is dynamically adjusted to match
|
|
||||||
/// this remaining space, ensuring a smooth size transition.
|
|
||||||
final double shrinkExtent;
|
|
||||||
|
|
||||||
/// Whether the carousel should keep scrolling to the next/previous items to
|
|
||||||
/// maintain the original layout.
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
final bool itemSnapping;
|
|
||||||
|
|
||||||
/// An object that can be used to control the position to which this scroll
|
|
||||||
/// view is scrolled.
|
|
||||||
final FladderCarouselController? controller;
|
|
||||||
|
|
||||||
/// The [Axis] along which the scroll view's offset increases with each item.
|
|
||||||
///
|
|
||||||
/// Defaults to [Axis.horizontal].
|
|
||||||
final Axis scrollDirection;
|
|
||||||
|
|
||||||
/// Whether the carousel list scrolls in the reading direction.
|
|
||||||
///
|
|
||||||
/// For example, if the reading direction is left-to-right and
|
|
||||||
/// [scrollDirection] is [Axis.horizontal], then the carousel scrolls from
|
|
||||||
/// left to right when [reverse] is false and from right to left when
|
|
||||||
/// [reverse] is true.
|
|
||||||
///
|
|
||||||
/// Similarly, if [scrollDirection] is [Axis.vertical], then the carousel view
|
|
||||||
/// scrolls from top to bottom when [reverse] is false and from bottom to top
|
|
||||||
/// when [reverse] is true.
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
final bool reverse;
|
|
||||||
|
|
||||||
/// Called when one of the [children] is tapped.
|
|
||||||
final ValueChanged<int>? onTap;
|
|
||||||
|
|
||||||
/// Called when one of the [children] is longPressed.
|
|
||||||
final ValueChanged<int>? onLongPress;
|
|
||||||
|
|
||||||
final ValueChanged<(int, TapDownDetails)>? onSecondaryTap;
|
|
||||||
|
|
||||||
/// The extent the children are forced to have in the main axis.
|
|
||||||
///
|
|
||||||
/// The item extent should not exceed the available space that the carousel
|
|
||||||
/// occupies to ensure at least one item is fully visible.
|
|
||||||
///
|
|
||||||
/// This must be non-null.
|
|
||||||
final double itemExtent;
|
|
||||||
|
|
||||||
/// The child widgets for the carousel.
|
|
||||||
final List<Widget> children;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<FladderCarousel> createState() => _CarouselViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CarouselViewState extends State<FladderCarousel> {
|
|
||||||
late double _itemExtent;
|
|
||||||
FladderCarouselController? _internalController;
|
|
||||||
FladderCarouselController get _controller => widget.controller ?? _internalController!;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (widget.controller == null) {
|
|
||||||
_internalController = FladderCarouselController();
|
|
||||||
}
|
|
||||||
_controller._attach(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
_itemExtent = widget.itemExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant FladderCarousel oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.controller != oldWidget.controller) {
|
|
||||||
oldWidget.controller?._detach(this);
|
|
||||||
if (widget.controller != null) {
|
|
||||||
_internalController?._detach(this);
|
|
||||||
_internalController = null;
|
|
||||||
widget.controller?._attach(this);
|
|
||||||
} else {
|
|
||||||
// widget.controller == null && oldWidget.controller != null
|
|
||||||
assert(_internalController == null);
|
|
||||||
_internalController = FladderCarouselController();
|
|
||||||
_controller._attach(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (widget.itemExtent != oldWidget.itemExtent) {
|
|
||||||
_itemExtent = widget.itemExtent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller._detach(this);
|
|
||||||
_internalController?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
AxisDirection _getDirection(BuildContext context) {
|
|
||||||
switch (widget.scrollDirection) {
|
|
||||||
case Axis.horizontal:
|
|
||||||
assert(debugCheckHasDirectionality(context));
|
|
||||||
final TextDirection textDirection = Directionality.of(context);
|
|
||||||
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
|
|
||||||
return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection;
|
|
||||||
case Axis.vertical:
|
|
||||||
return widget.reverse ? AxisDirection.up : AxisDirection.down;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ThemeData theme = Theme.of(context);
|
|
||||||
final AxisDirection axisDirection = _getDirection(context);
|
|
||||||
final ScrollPhysics physics =
|
|
||||||
widget.itemSnapping ? const CarouselScrollPhysics() : ScrollConfiguration.of(context).getScrollPhysics(context);
|
|
||||||
final EdgeInsets effectivePadding = widget.itemPadding ?? const EdgeInsets.all(4.0);
|
|
||||||
final Color effectiveBackgroundColor = widget.backgroundColor ?? Theme.of(context).colorScheme.surface;
|
|
||||||
final double effectiveElevation = widget.elevation ?? 0.0;
|
|
||||||
final ShapeBorder effectiveShape =
|
|
||||||
widget.shape ?? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0)));
|
|
||||||
|
|
||||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
final double mainAxisExtent = switch (widget.scrollDirection) {
|
|
||||||
Axis.horizontal => constraints.maxWidth,
|
|
||||||
Axis.vertical => constraints.maxHeight,
|
|
||||||
};
|
|
||||||
_itemExtent = clampDouble(_itemExtent, 0, mainAxisExtent);
|
|
||||||
|
|
||||||
return Scrollable(
|
|
||||||
axisDirection: axisDirection,
|
|
||||||
scrollBehavior: MyCustomScrollBehavior(),
|
|
||||||
controller: _controller,
|
|
||||||
physics: physics,
|
|
||||||
viewportBuilder: (BuildContext context, ViewportOffset position) {
|
|
||||||
return Viewport(
|
|
||||||
cacheExtent: 0.0,
|
|
||||||
cacheExtentStyle: CacheExtentStyle.viewport,
|
|
||||||
axisDirection: axisDirection,
|
|
||||||
offset: position,
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
slivers: <Widget>[
|
|
||||||
_SliverFixedExtentCarousel(
|
|
||||||
itemExtent: _itemExtent,
|
|
||||||
minExtent: widget.shrinkExtent,
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
return Padding(
|
|
||||||
padding: effectivePadding.add(EdgeInsets.only(
|
|
||||||
left: index == 0 ? widget.padding.left : 0,
|
|
||||||
right: index == widget.children.length - 1 ? widget.padding.right : 0,
|
|
||||||
)),
|
|
||||||
child: Material(
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
color: effectiveBackgroundColor,
|
|
||||||
elevation: effectiveElevation,
|
|
||||||
shape: effectiveShape,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: <Widget>[
|
|
||||||
widget.children.elementAt(index),
|
|
||||||
if (widget.onTap != null || widget.onSecondaryTap != null || widget.onLongPress != null)
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: widget.onTap != null ? () => widget.onTap!.call(index) : null,
|
|
||||||
onLongPress:
|
|
||||||
widget.onLongPress != null ? () => widget.onLongPress!.call(index) : null,
|
|
||||||
onSecondaryTapDown: widget.onSecondaryTap != null
|
|
||||||
? (details) => widget.onSecondaryTap!.call((index, details))
|
|
||||||
: null,
|
|
||||||
overlayColor: widget.overlayColor ??
|
|
||||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
||||||
if (states.contains(WidgetState.pressed)) {
|
|
||||||
return theme.colorScheme.onSurface.withOpacity(0.1);
|
|
||||||
}
|
|
||||||
if (states.contains(WidgetState.hovered)) {
|
|
||||||
return theme.colorScheme.onSurface.withOpacity(0.08);
|
|
||||||
}
|
|
||||||
if (states.contains(WidgetState.focused)) {
|
|
||||||
return theme.colorScheme.onSurface.withOpacity(0.1);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: widget.children.length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sliver that displays its box children in a linear array with a fixed extent
|
|
||||||
/// per item.
|
|
||||||
///
|
|
||||||
/// _To learn more about slivers, see [CustomScrollView.slivers]._
|
|
||||||
///
|
|
||||||
/// This sliver list arranges its children in a line along the main axis starting
|
|
||||||
/// at offset zero and without gaps. Each child is constrained to a fixed extent
|
|
||||||
/// along the main axis and the [SliverConstraints.crossAxisExtent]
|
|
||||||
/// along the cross axis. The difference between this and a list view with a fixed
|
|
||||||
/// extent is the first item and last item can be squished a little during scrolling
|
|
||||||
/// transition. This compression is controlled by the `minExtent` property and
|
|
||||||
/// aligns with the [Material Design Carousel specifications]
|
|
||||||
/// (https://m3.material.io/components/carousel/guidelines#96c5c157-fe5b-4ee3-a9b4-72bf8efab7e9).
|
|
||||||
class _SliverFixedExtentCarousel extends SliverMultiBoxAdaptorWidget {
|
|
||||||
const _SliverFixedExtentCarousel({
|
|
||||||
required super.delegate,
|
|
||||||
required this.minExtent,
|
|
||||||
required this.itemExtent,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double itemExtent;
|
|
||||||
final double minExtent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
RenderSliverFixedExtentBoxAdaptor createRenderObject(BuildContext context) {
|
|
||||||
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
|
|
||||||
return _RenderSliverFixedExtentCarousel(
|
|
||||||
childManager: element,
|
|
||||||
minExtent: minExtent,
|
|
||||||
maxExtent: itemExtent,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void updateRenderObject(BuildContext context, _RenderSliverFixedExtentCarousel renderObject) {
|
|
||||||
renderObject.maxExtent = itemExtent;
|
|
||||||
renderObject.minExtent = minExtent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RenderSliverFixedExtentCarousel extends RenderSliverFixedExtentBoxAdaptor {
|
|
||||||
_RenderSliverFixedExtentCarousel({
|
|
||||||
required super.childManager,
|
|
||||||
required double maxExtent,
|
|
||||||
required double minExtent,
|
|
||||||
}) : _maxExtent = maxExtent,
|
|
||||||
_minExtent = minExtent;
|
|
||||||
|
|
||||||
double get maxExtent => _maxExtent;
|
|
||||||
double _maxExtent;
|
|
||||||
set maxExtent(double value) {
|
|
||||||
if (_maxExtent == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_maxExtent = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
double get minExtent => _minExtent;
|
|
||||||
double _minExtent;
|
|
||||||
set minExtent(double value) {
|
|
||||||
if (_minExtent == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_minExtent = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This implements the [itemExtentBuilder] callback.
|
|
||||||
double _buildItemExtent(int index, SliverLayoutDimensions currentLayoutDimensions) {
|
|
||||||
final int firstVisibleIndex = (constraints.scrollOffset / maxExtent).floor();
|
|
||||||
|
|
||||||
// Calculate how many items have been completely scroll off screen.
|
|
||||||
final int offscreenItems = (constraints.scrollOffset / maxExtent).floor();
|
|
||||||
|
|
||||||
// If an item is partially off screen and partially on screen,
|
|
||||||
// `constraints.scrollOffset` must be greater than
|
|
||||||
// `offscreenItems * maxExtent`, so the difference between these two is how
|
|
||||||
// much the current first visible item is off screen.
|
|
||||||
final double offscreenExtent = constraints.scrollOffset - offscreenItems * maxExtent;
|
|
||||||
|
|
||||||
// If there is not enough space to place the last visible item but the remaining
|
|
||||||
// space is larger than `minExtent`, the extent for last item should be at
|
|
||||||
// least the remaining extent to ensure a smooth size transition.
|
|
||||||
final double effectiveMinExtent = math.max(constraints.remainingPaintExtent % maxExtent, minExtent);
|
|
||||||
|
|
||||||
// Two special cases are the first and last visible items. Other items' extent
|
|
||||||
// should all return `maxExtent`.
|
|
||||||
if (index == firstVisibleIndex) {
|
|
||||||
final double effectiveExtent = maxExtent - offscreenExtent;
|
|
||||||
return math.max(effectiveExtent, effectiveMinExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
final double scrollOffsetForLastIndex = constraints.scrollOffset + constraints.remainingPaintExtent;
|
|
||||||
if (index == getMaxChildIndexForScrollOffset(scrollOffsetForLastIndex, maxExtent)) {
|
|
||||||
return clampDouble(scrollOffsetForLastIndex - maxExtent * index, effectiveMinExtent, maxExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
late SliverLayoutDimensions _currentLayoutDimensions;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void performLayout() {
|
|
||||||
_currentLayoutDimensions = SliverLayoutDimensions(
|
|
||||||
scrollOffset: constraints.scrollOffset,
|
|
||||||
precedingScrollExtent: constraints.precedingScrollExtent,
|
|
||||||
viewportMainAxisExtent: constraints.viewportMainAxisExtent,
|
|
||||||
crossAxisExtent: constraints.crossAxisExtent,
|
|
||||||
);
|
|
||||||
super.performLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The layout offset for the child with the given index.
|
|
||||||
@override
|
|
||||||
double indexToLayoutOffset(
|
|
||||||
@Deprecated('The itemExtent is already available within the scope of this function. '
|
|
||||||
'This feature was deprecated after v3.20.0-7.0.pre.')
|
|
||||||
double itemExtent,
|
|
||||||
int index,
|
|
||||||
) {
|
|
||||||
final int firstVisibleIndex = (constraints.scrollOffset / maxExtent).floor();
|
|
||||||
|
|
||||||
// If there is not enough space to place the last visible item but the remaining
|
|
||||||
// space is larger than `minExtent`, the extent for last item should be at
|
|
||||||
// least the remaining extent to make sure a smooth size transition.
|
|
||||||
final double effectiveMinExtent = math.max(constraints.remainingPaintExtent % maxExtent, minExtent);
|
|
||||||
if (index == firstVisibleIndex) {
|
|
||||||
final double firstVisibleItemExtent = _buildItemExtent(index, _currentLayoutDimensions);
|
|
||||||
|
|
||||||
// If the first item is squished to be less than `effectievMinExtent`,
|
|
||||||
// then it should stop changinng its size and should start to scroll off screen.
|
|
||||||
if (firstVisibleItemExtent <= effectiveMinExtent) {
|
|
||||||
return maxExtent * index - effectiveMinExtent + maxExtent;
|
|
||||||
}
|
|
||||||
return constraints.scrollOffset;
|
|
||||||
}
|
|
||||||
return maxExtent * index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The minimum child index that is visible at the given scroll offset.
|
|
||||||
@override
|
|
||||||
int getMinChildIndexForScrollOffset(
|
|
||||||
double scrollOffset,
|
|
||||||
@Deprecated('The itemExtent is already available within the scope of this function. '
|
|
||||||
'This feature was deprecated after v3.20.0-7.0.pre.')
|
|
||||||
double itemExtent,
|
|
||||||
) {
|
|
||||||
final int firstVisibleIndex = (constraints.scrollOffset / maxExtent).floor();
|
|
||||||
return math.max(firstVisibleIndex, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum child index that is visible at the given scroll offset.
|
|
||||||
@override
|
|
||||||
int getMaxChildIndexForScrollOffset(
|
|
||||||
double scrollOffset,
|
|
||||||
@Deprecated('The itemExtent is already available within the scope of this function. '
|
|
||||||
'This feature was deprecated after v3.20.0-7.0.pre.')
|
|
||||||
double itemExtent,
|
|
||||||
) {
|
|
||||||
if (maxExtent > 0.0) {
|
|
||||||
final double actual = scrollOffset / maxExtent - 1;
|
|
||||||
final int round = actual.round();
|
|
||||||
if ((actual * maxExtent - round * maxExtent).abs() < precisionErrorTolerance) {
|
|
||||||
return math.max(0, round);
|
|
||||||
}
|
|
||||||
return math.max(0, actual.ceil());
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double? get itemExtent => null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ItemExtentBuilder? get itemExtentBuilder => _buildItemExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scroll physics used by a [FladderCarousel].
|
|
||||||
///
|
|
||||||
/// These physics cause the carousel item to snap to item boundaries.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [ScrollPhysics], the base class which defines the API for scrolling
|
|
||||||
/// physics.
|
|
||||||
/// * [PageScrollPhysics], scroll physics used by a [PageView].
|
|
||||||
class CarouselScrollPhysics extends ScrollPhysics {
|
|
||||||
/// Creates physics for a [FladderCarousel].
|
|
||||||
const CarouselScrollPhysics({super.parent});
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarouselScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
|
||||||
return CarouselScrollPhysics(parent: buildParent(ancestor));
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getTargetPixels(
|
|
||||||
_CarouselPosition position,
|
|
||||||
Tolerance tolerance,
|
|
||||||
double velocity,
|
|
||||||
) {
|
|
||||||
double fraction;
|
|
||||||
fraction = position.itemExtent! / position.viewportDimension;
|
|
||||||
|
|
||||||
final double itemWidth = position.viewportDimension * fraction;
|
|
||||||
|
|
||||||
final double actual = math.max(0.0, position.pixels) / itemWidth;
|
|
||||||
final double round = actual.roundToDouble();
|
|
||||||
double item;
|
|
||||||
if ((actual - round).abs() < precisionErrorTolerance) {
|
|
||||||
item = round;
|
|
||||||
} else {
|
|
||||||
item = actual;
|
|
||||||
}
|
|
||||||
if (velocity < -tolerance.velocity) {
|
|
||||||
item -= 0.5;
|
|
||||||
} else if (velocity > tolerance.velocity) {
|
|
||||||
item += 0.5;
|
|
||||||
}
|
|
||||||
return item.roundToDouble() * itemWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Simulation? createBallisticSimulation(
|
|
||||||
ScrollMetrics position,
|
|
||||||
double velocity,
|
|
||||||
) {
|
|
||||||
assert(
|
|
||||||
position is _CarouselPosition,
|
|
||||||
'CarouselScrollPhysics can only be used with Scrollables that uses '
|
|
||||||
'the FladderCarouselController',
|
|
||||||
);
|
|
||||||
|
|
||||||
final _CarouselPosition metrics = position as _CarouselPosition;
|
|
||||||
if ((velocity <= 0.0 && metrics.pixels <= metrics.minScrollExtent) ||
|
|
||||||
(velocity >= 0.0 && metrics.pixels >= metrics.maxScrollExtent)) {
|
|
||||||
return super.createBallisticSimulation(metrics, velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Tolerance tolerance = toleranceFor(metrics);
|
|
||||||
final double target = _getTargetPixels(metrics, tolerance, velocity);
|
|
||||||
if (target != metrics.pixels) {
|
|
||||||
return ScrollSpringSimulation(
|
|
||||||
spring,
|
|
||||||
metrics.pixels,
|
|
||||||
target,
|
|
||||||
velocity,
|
|
||||||
tolerance: tolerance,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get allowImplicitScrolling => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metrics for a [FladderCarousel].
|
|
||||||
class _CarouselMetrics extends FixedScrollMetrics {
|
|
||||||
/// Creates an immutable snapshot of values associated with a [FladderCarousel].
|
|
||||||
_CarouselMetrics({
|
|
||||||
required super.minScrollExtent,
|
|
||||||
required super.maxScrollExtent,
|
|
||||||
required super.pixels,
|
|
||||||
required super.viewportDimension,
|
|
||||||
required super.axisDirection,
|
|
||||||
this.itemExtent,
|
|
||||||
required super.devicePixelRatio,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Extent for the carousel item.
|
|
||||||
///
|
|
||||||
/// Used to compute the first item from the current [pixels].
|
|
||||||
final double? itemExtent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_CarouselMetrics copyWith({
|
|
||||||
double? minScrollExtent,
|
|
||||||
double? maxScrollExtent,
|
|
||||||
double? pixels,
|
|
||||||
double? viewportDimension,
|
|
||||||
AxisDirection? axisDirection,
|
|
||||||
double? itemExtent,
|
|
||||||
double? devicePixelRatio,
|
|
||||||
}) {
|
|
||||||
return _CarouselMetrics(
|
|
||||||
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
|
|
||||||
maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
|
|
||||||
pixels: pixels ?? (hasPixels ? this.pixels : null),
|
|
||||||
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
|
|
||||||
axisDirection: axisDirection ?? this.axisDirection,
|
|
||||||
itemExtent: itemExtent ?? this.itemExtent,
|
|
||||||
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CarouselPosition extends ScrollPositionWithSingleContext implements _CarouselMetrics {
|
|
||||||
_CarouselPosition({
|
|
||||||
required super.physics,
|
|
||||||
required super.context,
|
|
||||||
this.initialItem = 0,
|
|
||||||
required this.itemExtent,
|
|
||||||
super.oldPosition,
|
|
||||||
}) : _itemToShowOnStartup = initialItem.toDouble(),
|
|
||||||
super(initialPixels: null);
|
|
||||||
|
|
||||||
final int initialItem;
|
|
||||||
final double _itemToShowOnStartup;
|
|
||||||
// When the viewport has a zero-size, the item can not
|
|
||||||
// be retrieved by `getItemFromPixels`, so we need to cache the item
|
|
||||||
// for use when resizing the viewport to non-zero next time.
|
|
||||||
double? _cachedItem;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double? itemExtent;
|
|
||||||
|
|
||||||
double getItemFromPixels(double pixels, double viewportDimension) {
|
|
||||||
assert(viewportDimension > 0.0);
|
|
||||||
final double fraction = itemExtent! / viewportDimension;
|
|
||||||
|
|
||||||
final double actual = math.max(0.0, pixels) / (viewportDimension * fraction);
|
|
||||||
final double round = actual.roundToDouble();
|
|
||||||
if ((actual - round).abs() < precisionErrorTolerance) {
|
|
||||||
return round;
|
|
||||||
}
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getPixelsFromItem(double item) {
|
|
||||||
final double fraction = itemExtent! / viewportDimension;
|
|
||||||
|
|
||||||
return item * viewportDimension * fraction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool applyViewportDimension(double viewportDimension) {
|
|
||||||
final double? oldViewportDimensions = hasViewportDimension ? this.viewportDimension : null;
|
|
||||||
if (viewportDimension == oldViewportDimensions) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final bool result = super.applyViewportDimension(viewportDimension);
|
|
||||||
final double? oldPixels = hasPixels ? pixels : null;
|
|
||||||
double item;
|
|
||||||
if (oldPixels == null) {
|
|
||||||
item = _itemToShowOnStartup;
|
|
||||||
} else if (oldViewportDimensions == 0.0) {
|
|
||||||
// If resize from zero, we should use the _cachedItem to recover the state.
|
|
||||||
item = _cachedItem!;
|
|
||||||
} else {
|
|
||||||
item = getItemFromPixels(oldPixels, oldViewportDimensions!);
|
|
||||||
}
|
|
||||||
final double newPixels = getPixelsFromItem(item);
|
|
||||||
// If the viewportDimension is zero, cache the item
|
|
||||||
// in case the viewport is resized to be non-zero.
|
|
||||||
_cachedItem = (viewportDimension == 0.0) ? item : null;
|
|
||||||
|
|
||||||
if (newPixels != oldPixels) {
|
|
||||||
correctPixels(newPixels);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
_CarouselMetrics copyWith({
|
|
||||||
double? minScrollExtent,
|
|
||||||
double? maxScrollExtent,
|
|
||||||
double? pixels,
|
|
||||||
double? viewportDimension,
|
|
||||||
AxisDirection? axisDirection,
|
|
||||||
double? itemExtent,
|
|
||||||
List<int>? layoutWeights,
|
|
||||||
double? devicePixelRatio,
|
|
||||||
}) {
|
|
||||||
return _CarouselMetrics(
|
|
||||||
minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
|
|
||||||
maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
|
|
||||||
pixels: pixels ?? (hasPixels ? this.pixels : null),
|
|
||||||
viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
|
|
||||||
axisDirection: axisDirection ?? this.axisDirection,
|
|
||||||
itemExtent: itemExtent ?? this.itemExtent,
|
|
||||||
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A controller for [FladderCarousel].
|
|
||||||
///
|
|
||||||
/// Using a carousel controller helps to show the first visible item on the
|
|
||||||
/// carousel list.
|
|
||||||
class FladderCarouselController extends ScrollController {
|
|
||||||
/// Creates a carousel controller.
|
|
||||||
FladderCarouselController({
|
|
||||||
this.initialItem = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The item that expands to the maximum size when first creating the [FladderCarousel].
|
|
||||||
final int initialItem;
|
|
||||||
|
|
||||||
_CarouselViewState? _carouselState;
|
|
||||||
|
|
||||||
// ignore: use_setters_to_change_properties
|
|
||||||
void _attach(_CarouselViewState anchor) {
|
|
||||||
_carouselState = anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _detach(_CarouselViewState anchor) {
|
|
||||||
if (_carouselState == anchor) {
|
|
||||||
_carouselState = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
|
|
||||||
assert(_carouselState != null);
|
|
||||||
final double itemExtent = _carouselState!._itemExtent;
|
|
||||||
|
|
||||||
return _CarouselPosition(
|
|
||||||
physics: physics,
|
|
||||||
context: context,
|
|
||||||
initialItem: initialItem,
|
|
||||||
itemExtent: itemExtent,
|
|
||||||
oldPosition: oldPosition,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void attach(ScrollPosition position) {
|
|
||||||
super.attach(position);
|
|
||||||
final _CarouselPosition carouselPosition = position as _CarouselPosition;
|
|
||||||
carouselPosition.itemExtent = _carouselState!._itemExtent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -28,7 +28,7 @@ class FladderScrollbar extends ConsumerWidget {
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
color: info.isDragging
|
color: info.isDragging
|
||||||
? Theme.of(context).colorScheme.secondary
|
? Theme.of(context).colorScheme.secondary
|
||||||
: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.75),
|
: Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.75),
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ class ItemActionButton extends ItemAction {
|
||||||
minimumSize: const WidgetStatePropertyAll(Size(50, 50)),
|
minimumSize: const WidgetStatePropertyAll(Size(50, 50)),
|
||||||
elevation: const WidgetStatePropertyAll(0),
|
elevation: const WidgetStatePropertyAll(0),
|
||||||
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onSurface),
|
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onSurface),
|
||||||
|
iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (shouldPop) {
|
if (shouldPop) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Future<void> showModalSideSheet(
|
||||||
context: context,
|
context: context,
|
||||||
transitionDuration: transitionDuration ?? const Duration(milliseconds: 200),
|
transitionDuration: transitionDuration ?? const Duration(milliseconds: 200),
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
barrierColor: Theme.of(context).colorScheme.scrim.withOpacity(0.3),
|
barrierColor: Theme.of(context).colorScheme.scrim.withValues(alpha: 0.3),
|
||||||
barrierLabel: 'Material 3 side sheet',
|
barrierLabel: 'Material 3 side sheet',
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue