refactor: migrate models to freezed + json_serializable

This commit is contained in:
edde746
2026-05-11 12:54:26 +02:00
parent 2bbd31d927
commit d0bd919ff4
59 changed files with 9551 additions and 1236 deletions
+9
View File
@@ -38,6 +38,15 @@ jobs:
flutter pub get flutter pub get
- name: Verify generated files committed
run: |
dart run build_runner build --delete-conflicting-outputs
if ! git diff --exit-code lib/; then
echo "::error::Generated files (.g.dart / .freezed.dart) are out of date."
echo "Run 'scripts/codegen.sh' and commit the result."
exit 1
fi
- name: Verify formatting - name: Verify formatting
run: | run: |
# Find all Dart files excluding generated files # Find all Dart files excluding generated files
+15
View File
@@ -0,0 +1,15 @@
targets:
$default:
builders:
json_serializable:
options:
create_to_json: true
explicit_to_json: true
field_rename: none
include_if_null: true
checked: false
freezed:
options:
copy_with: true
equal: true
to_string: true
+31 -71
View File
@@ -1,114 +1,74 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
import 'media_kind.dart'; import 'media_kind.dart';
part 'library_query.freezed.dart';
/// Sort order applied to a library query. /// Sort order applied to a library query.
enum LibrarySortDirection { ascending, descending } enum LibrarySortDirection { ascending, descending }
class LibrarySort { @freezed
sealed class LibrarySort with _$LibrarySort {
/// Backend-neutral sort field. Common values: `addedAt`, `originallyAvailableAt`, /// Backend-neutral sort field. Common values: `addedAt`, `originallyAvailableAt`,
/// `lastViewedAt`, `title`, `rating`, `viewCount`, `random`. /// `lastViewedAt`, `title`, `rating`, `viewCount`, `random`.
final String field; const factory LibrarySort({
final LibrarySortDirection direction; required String field,
@Default(LibrarySortDirection.descending) LibrarySortDirection direction,
const LibrarySort({required this.field, this.direction = LibrarySortDirection.descending}); }) = _LibrarySort;
} }
/// A single filter clause. The semantics of `field` and `value` are /// A single filter clause. The semantics of `field` and `value` are
/// backend-translated — the neutral query just carries the intent. /// backend-translated — the neutral query just carries the intent.
class LibraryFilter { @freezed
final String field; sealed class LibraryFilter with _$LibraryFilter {
final String op; // "=", "!=", "contains", ">=", etc. const factory LibraryFilter({required String field, @Default('=') String op, required List<String> values}) =
final List<String> values; _LibraryFilter;
const LibraryFilter({required this.field, this.op = '=', required this.values});
} }
/// Backend-neutral library content query. Each backend's adapter translates /// Backend-neutral library content query. Each backend's adapter translates
/// these into its own query DSL (Plex `/library/sections/{id}/all?type=...` /// these into its own query DSL (Plex `/library/sections/{id}/all?type=...`
/// or Jellyfin `/Items?ParentId=...&Filters=...`). /// or Jellyfin `/Items?ParentId=...&Filters=...`).
class LibraryQuery { @freezed
sealed class LibraryQuery with _$LibraryQuery {
const factory LibraryQuery({
/// Restrict to a single kind (e.g. `MediaKind.movie`). Null = library default. /// Restrict to a single kind (e.g. `MediaKind.movie`). Null = library default.
final MediaKind? kind; MediaKind? kind,
/// Pagination — zero-based offset. /// Pagination — zero-based offset.
final int offset; @Default(0) int offset,
final int limit; @Default(50) int limit,
final LibrarySort? sort; LibrarySort? sort,
final List<LibraryFilter> filters; @Default(<LibraryFilter>[]) List<LibraryFilter> filters,
/// Free-text search restricted to this library. Distinct from the global /// Free-text search restricted to this library. Distinct from the global
/// search endpoint. /// search endpoint.
final String? search; String? search,
/// Whether to include items the active user has already watched. /// Whether to include items the active user has already watched.
final bool includeWatched; @Default(true) bool includeWatched,
/// Restrict the result to items whose sort name starts with this string — /// Restrict the result to items whose sort name starts with this string —
/// the alpha-jump bar's filter UX. The literal `#` is a sentinel for /// the alpha-jump bar's filter UX. The literal `#` is a sentinel for
/// "non-alphabetic" and translates to a `NameLessThan=A` query for backends /// "non-alphabetic" and translates to a `NameLessThan=A` query for backends
/// that support it. /// that support it.
final String? nameStartsWith; String? nameStartsWith,
/// Genre filter — used by the per-library filter sheet. Backends that /// Genre filter — used by the per-library filter sheet. Backends that
/// take multiple values (Jellyfin) AND/intersect; those that take one /// take multiple values (Jellyfin) AND/intersect; those that take one
/// (Plex's existing flow) consult `filters` instead. /// (Plex's existing flow) consult `filters` instead.
final List<String>? genres;
final List<String>? officialRatings;
final List<int>? years;
final List<String>? tags;
const LibraryQuery({
this.kind,
this.offset = 0,
this.limit = 50,
this.sort,
this.filters = const [],
this.search,
this.includeWatched = true,
this.nameStartsWith,
this.genres,
this.officialRatings,
this.years,
this.tags,
});
LibraryQuery copyWith({
MediaKind? kind,
int? offset,
int? limit,
LibrarySort? sort,
List<LibraryFilter>? filters,
String? search,
bool? includeWatched,
String? nameStartsWith,
List<String>? genres, List<String>? genres,
List<String>? officialRatings, List<String>? officialRatings,
List<int>? years, List<int>? years,
List<String>? tags, List<String>? tags,
}) { }) = _LibraryQuery;
return LibraryQuery(
kind: kind ?? this.kind,
offset: offset ?? this.offset,
limit: limit ?? this.limit,
sort: sort ?? this.sort,
filters: filters ?? this.filters,
search: search ?? this.search,
includeWatched: includeWatched ?? this.includeWatched,
nameStartsWith: nameStartsWith ?? this.nameStartsWith,
genres: genres ?? this.genres,
officialRatings: officialRatings ?? this.officialRatings,
years: years ?? this.years,
tags: tags ?? this.tags,
);
}
} }
/// Page of items returned by [MediaServerClient.getLibraryContent]. /// Page of items returned by [MediaServerClient.getLibraryContent].
/// Carries the total count so the UI can render correct pagination affordances. /// Carries the total count so the UI can render correct pagination affordances.
class LibraryPage<T> { @freezed
final List<T> items; sealed class LibraryPage<T> with _$LibraryPage<T> {
final int totalCount; const factory LibraryPage({required List<T> items, required int totalCount, @Default(0) int offset}) =
final int offset; _LibraryPage<T>;
const LibraryPage({required this.items, required this.totalCount, this.offset = 0});
} }
File diff suppressed because it is too large Load Diff
+7 -24
View File
@@ -1,20 +1,17 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'media_sort.freezed.dart';
part 'media_sort.g.dart'; part 'media_sort.g.dart';
@JsonSerializable() @freezed
class MediaSort { sealed class MediaSort with _$MediaSort {
final String key; const MediaSort._();
final String? descKey;
final String title;
final String? defaultDirection;
MediaSort({required this.key, this.descKey, required this.title, this.defaultDirection}); const factory MediaSort({required String key, String? descKey, required String title, String? defaultDirection}) =
_MediaSort;
factory MediaSort.fromJson(Map<String, dynamic> json) => _$MediaSortFromJson(json); factory MediaSort.fromJson(Map<String, dynamic> json) => _$MediaSortFromJson(json);
Map<String, dynamic> toJson() => _$MediaSortToJson(this);
String getSortKey({bool descending = false}) { String getSortKey({bool descending = false}) {
if (!descending) { if (!descending) {
return key; return key;
@@ -26,18 +23,4 @@ class MediaSort {
bool get isDefaultDescending { bool get isDefaultDescending {
return defaultDirection?.toLowerCase() == 'desc'; return defaultDirection?.toLowerCase() == 'desc';
} }
@override
String toString() {
return 'MediaSort(key: $key, title: $title, defaultDirection: $defaultDirection)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is MediaSort && other.key == key;
}
@override
int get hashCode => key.hashCode;
} }
+280
View File
@@ -0,0 +1,280 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'media_sort.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$MediaSort {
String get key; String? get descKey; String get title; String? get defaultDirection;
/// Create a copy of MediaSort
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MediaSortCopyWith<MediaSort> get copyWith => _$MediaSortCopyWithImpl<MediaSort>(this as MediaSort, _$identity);
/// Serializes this MediaSort to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MediaSort&&(identical(other.key, key) || other.key == key)&&(identical(other.descKey, descKey) || other.descKey == descKey)&&(identical(other.title, title) || other.title == title)&&(identical(other.defaultDirection, defaultDirection) || other.defaultDirection == defaultDirection));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,descKey,title,defaultDirection);
@override
String toString() {
return 'MediaSort(key: $key, descKey: $descKey, title: $title, defaultDirection: $defaultDirection)';
}
}
/// @nodoc
abstract mixin class $MediaSortCopyWith<$Res> {
factory $MediaSortCopyWith(MediaSort value, $Res Function(MediaSort) _then) = _$MediaSortCopyWithImpl;
@useResult
$Res call({
String key, String? descKey, String title, String? defaultDirection
});
}
/// @nodoc
class _$MediaSortCopyWithImpl<$Res>
implements $MediaSortCopyWith<$Res> {
_$MediaSortCopyWithImpl(this._self, this._then);
final MediaSort _self;
final $Res Function(MediaSort) _then;
/// Create a copy of MediaSort
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = null,Object? descKey = freezed,Object? title = null,Object? defaultDirection = freezed,}) {
return _then(_self.copyWith(
key: null == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String,descKey: freezed == descKey ? _self.descKey : descKey // ignore: cast_nullable_to_non_nullable
as String?,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,defaultDirection: freezed == defaultDirection ? _self.defaultDirection : defaultDirection // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [MediaSort].
extension MediaSortPatterns on MediaSort {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MediaSort value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MediaSort() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MediaSort value) $default,){
final _that = this;
switch (_that) {
case _MediaSort():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MediaSort value)? $default,){
final _that = this;
switch (_that) {
case _MediaSort() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String key, String? descKey, String title, String? defaultDirection)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MediaSort() when $default != null:
return $default(_that.key,_that.descKey,_that.title,_that.defaultDirection);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String key, String? descKey, String title, String? defaultDirection) $default,) {final _that = this;
switch (_that) {
case _MediaSort():
return $default(_that.key,_that.descKey,_that.title,_that.defaultDirection);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String key, String? descKey, String title, String? defaultDirection)? $default,) {final _that = this;
switch (_that) {
case _MediaSort() when $default != null:
return $default(_that.key,_that.descKey,_that.title,_that.defaultDirection);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _MediaSort extends MediaSort {
const _MediaSort({required this.key, this.descKey, required this.title, this.defaultDirection}): super._();
factory _MediaSort.fromJson(Map<String, dynamic> json) => _$MediaSortFromJson(json);
@override final String key;
@override final String? descKey;
@override final String title;
@override final String? defaultDirection;
/// Create a copy of MediaSort
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MediaSortCopyWith<_MediaSort> get copyWith => __$MediaSortCopyWithImpl<_MediaSort>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$MediaSortToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MediaSort&&(identical(other.key, key) || other.key == key)&&(identical(other.descKey, descKey) || other.descKey == descKey)&&(identical(other.title, title) || other.title == title)&&(identical(other.defaultDirection, defaultDirection) || other.defaultDirection == defaultDirection));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,descKey,title,defaultDirection);
@override
String toString() {
return 'MediaSort(key: $key, descKey: $descKey, title: $title, defaultDirection: $defaultDirection)';
}
}
/// @nodoc
abstract mixin class _$MediaSortCopyWith<$Res> implements $MediaSortCopyWith<$Res> {
factory _$MediaSortCopyWith(_MediaSort value, $Res Function(_MediaSort) _then) = __$MediaSortCopyWithImpl;
@override @useResult
$Res call({
String key, String? descKey, String title, String? defaultDirection
});
}
/// @nodoc
class __$MediaSortCopyWithImpl<$Res>
implements _$MediaSortCopyWith<$Res> {
__$MediaSortCopyWithImpl(this._self, this._then);
final _MediaSort _self;
final $Res Function(_MediaSort) _then;
/// Create a copy of MediaSort
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = null,Object? descKey = freezed,Object? title = null,Object? defaultDirection = freezed,}) {
return _then(_MediaSort(
key: null == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String,descKey: freezed == descKey ? _self.descKey : descKey // ignore: cast_nullable_to_non_nullable
as String?,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,defaultDirection: freezed == defaultDirection ? _self.defaultDirection : defaultDirection // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on
+4 -3
View File
@@ -6,16 +6,17 @@ part of 'media_sort.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
MediaSort _$MediaSortFromJson(Map<String, dynamic> json) => MediaSort( _MediaSort _$MediaSortFromJson(Map<String, dynamic> json) => _MediaSort(
key: json['key'] as String, key: json['key'] as String,
descKey: json['descKey'] as String?, descKey: json['descKey'] as String?,
title: json['title'] as String, title: json['title'] as String,
defaultDirection: json['defaultDirection'] as String?, defaultDirection: json['defaultDirection'] as String?,
); );
Map<String, dynamic> _$MediaSortToJson(MediaSort instance) => <String, dynamic>{ Map<String, dynamic> _$MediaSortToJson(_MediaSort instance) =>
<String, dynamic>{
'key': instance.key, 'key': instance.key,
'descKey': instance.descKey, 'descKey': instance.descKey,
'title': instance.title, 'title': instance.title,
'defaultDirection': instance.defaultDirection, 'defaultDirection': instance.defaultDirection,
}; };
+40 -101
View File
@@ -1,122 +1,61 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'media_item.dart'; import 'media_item.dart';
part 'play_queue.freezed.dart';
/// Backend-neutral play queue — a flat ordered list of items with a current /// Backend-neutral play queue — a flat ordered list of items with a current
/// cursor. Implementations differ in whether the queue is server-resourced /// cursor. Implementations differ in whether the queue is server-resourced
/// (Plex) or client-only (Jellyfin). /// (Plex) or client-only (Jellyfin).
sealed class PlayQueue { @freezed
/// Items in playback order. sealed class PlayQueue with _$PlayQueue {
List<MediaItem> get items; const PlayQueue._();
/// Index of the currently-playing item, or `null` if the queue has not /// Plex play queue — coordinated server-side via `/playQueues` so multiple
/// started. /// devices can view/control the same queue.
int? get currentIndex; const factory PlayQueue.plex({
/// Whether the queue has been shuffled.
bool get shuffled;
/// Backend that minted this queue.
String get backendId;
MediaItem? get current =>
currentIndex != null && currentIndex! >= 0 && currentIndex! < items.length ? items[currentIndex!] : null;
bool get hasNext => currentIndex != null && currentIndex! + 1 < items.length;
bool get hasPrevious => currentIndex != null && currentIndex! > 0;
}
/// Plex play queue — coordinated server-side via `/playQueues` so multiple
/// devices can view/control the same queue.
class PlexServerPlayQueue extends PlayQueue {
/// Plex `playQueueID` — addresses the queue for subsequent fetches. /// Plex `playQueueID` — addresses the queue for subsequent fetches.
final int playQueueId; required int playQueueId,
required List<MediaItem> items,
@override int? currentIndex,
final List<MediaItem> items; @Default(false) bool shuffled,
@override
final int? currentIndex;
@override
final bool shuffled;
/// Plex `playQueueSelectedItemID` of the active item. /// Plex `playQueueSelectedItemID` of the active item.
final int? selectedItemId; int? selectedItemId,
/// Plex `playQueueVersion` — server-side optimistic concurrency token. /// Plex `playQueueVersion` — server-side optimistic concurrency token.
final int? version; int? version,
/// Plex `playQueueSourceURI` — used for "Up Next" derivation. /// Plex `playQueueSourceURI` — used for "Up Next" derivation.
final String? sourceUri;
PlexServerPlayQueue({
required this.playQueueId,
required this.items,
this.currentIndex,
this.shuffled = false,
this.selectedItemId,
this.version,
this.sourceUri,
});
@override
String get backendId => 'plex';
PlexServerPlayQueue copyWith({
int? playQueueId,
List<MediaItem>? items,
int? currentIndex,
bool? shuffled,
int? selectedItemId,
int? version,
String? sourceUri, String? sourceUri,
}) { }) = PlexServerPlayQueue;
return PlexServerPlayQueue(
playQueueId: playQueueId ?? this.playQueueId,
items: items ?? this.items,
currentIndex: currentIndex ?? this.currentIndex,
shuffled: shuffled ?? this.shuffled,
selectedItemId: selectedItemId ?? this.selectedItemId,
version: version ?? this.version,
sourceUri: sourceUri ?? this.sourceUri,
);
}
}
/// Client-only play queue used by Jellyfin and any backend without a /// Client-only play queue used by Jellyfin and any backend without a
/// server-side queue concept. Each [LocalPlayQueue] is anchored by a /// server-side queue concept. Each [LocalPlayQueue] is anchored by a
/// client-generated UUID so callers can address it like a Plex queue. /// client-generated UUID so callers can address it like a Plex queue.
class LocalPlayQueue extends PlayQueue { const factory PlayQueue.local({
/// Client-generated UUID identifying this queue for the session. /// Client-generated UUID identifying this queue for the session.
final String id; required String id,
required List<MediaItem> items,
@override
final List<MediaItem> items;
@override
final int? currentIndex;
@override
final bool shuffled;
/// Server kind that owns this queue's items (typically `"jellyfin"`). /// Server kind that owns this queue's items (typically `"jellyfin"`).
@override required String backendId,
final String backendId; int? currentIndex,
@Default(false) bool shuffled,
}) = LocalPlayQueue;
LocalPlayQueue({ MediaItem? get current => switch (this) {
required this.id, PlexServerPlayQueue(:final items, :final currentIndex) || LocalPlayQueue(:final items, :final currentIndex) =>
required this.items, currentIndex != null && currentIndex >= 0 && currentIndex < items.length ? items[currentIndex] : null,
required this.backendId, };
this.currentIndex,
this.shuffled = false,
});
LocalPlayQueue copyWith({String? id, List<MediaItem>? items, int? currentIndex, bool? shuffled, String? backendId}) { bool get hasNext => switch (this) {
return LocalPlayQueue( PlexServerPlayQueue(:final items, :final currentIndex) ||
id: id ?? this.id, LocalPlayQueue(:final items, :final currentIndex) => currentIndex != null && currentIndex + 1 < items.length,
items: items ?? this.items, };
currentIndex: currentIndex ?? this.currentIndex,
shuffled: shuffled ?? this.shuffled, bool get hasPrevious => switch (this) {
backendId: backendId ?? this.backendId, PlexServerPlayQueue(:final currentIndex) ||
); LocalPlayQueue(:final currentIndex) => currentIndex != null && currentIndex > 0,
} };
} }
+377
View File
@@ -0,0 +1,377 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'play_queue.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PlayQueue {
List<MediaItem> get items; int? get currentIndex; bool get shuffled;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PlayQueueCopyWith<PlayQueue> get copyWith => _$PlayQueueCopyWithImpl<PlayQueue>(this as PlayQueue, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PlayQueue&&const DeepCollectionEquality().equals(other.items, items)&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.shuffled, shuffled) || other.shuffled == shuffled));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),currentIndex,shuffled);
@override
String toString() {
return 'PlayQueue(items: $items, currentIndex: $currentIndex, shuffled: $shuffled)';
}
}
/// @nodoc
abstract mixin class $PlayQueueCopyWith<$Res> {
factory $PlayQueueCopyWith(PlayQueue value, $Res Function(PlayQueue) _then) = _$PlayQueueCopyWithImpl;
@useResult
$Res call({
List<MediaItem> items, int? currentIndex, bool shuffled
});
}
/// @nodoc
class _$PlayQueueCopyWithImpl<$Res>
implements $PlayQueueCopyWith<$Res> {
_$PlayQueueCopyWithImpl(this._self, this._then);
final PlayQueue _self;
final $Res Function(PlayQueue) _then;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? currentIndex = freezed,Object? shuffled = null,}) {
return _then(_self.copyWith(
items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<MediaItem>,currentIndex: freezed == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable
as int?,shuffled: null == shuffled ? _self.shuffled : shuffled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [PlayQueue].
extension PlayQueuePatterns on PlayQueue {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( PlexServerPlayQueue value)? plex,TResult Function( LocalPlayQueue value)? local,required TResult orElse(),}){
final _that = this;
switch (_that) {
case PlexServerPlayQueue() when plex != null:
return plex(_that);case LocalPlayQueue() when local != null:
return local(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( PlexServerPlayQueue value) plex,required TResult Function( LocalPlayQueue value) local,}){
final _that = this;
switch (_that) {
case PlexServerPlayQueue():
return plex(_that);case LocalPlayQueue():
return local(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( PlexServerPlayQueue value)? plex,TResult? Function( LocalPlayQueue value)? local,}){
final _that = this;
switch (_that) {
case PlexServerPlayQueue() when plex != null:
return plex(_that);case LocalPlayQueue() when local != null:
return local(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( int playQueueId, List<MediaItem> items, int? currentIndex, bool shuffled, int? selectedItemId, int? version, String? sourceUri)? plex,TResult Function( String id, List<MediaItem> items, String backendId, int? currentIndex, bool shuffled)? local,required TResult orElse(),}) {final _that = this;
switch (_that) {
case PlexServerPlayQueue() when plex != null:
return plex(_that.playQueueId,_that.items,_that.currentIndex,_that.shuffled,_that.selectedItemId,_that.version,_that.sourceUri);case LocalPlayQueue() when local != null:
return local(_that.id,_that.items,_that.backendId,_that.currentIndex,_that.shuffled);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( int playQueueId, List<MediaItem> items, int? currentIndex, bool shuffled, int? selectedItemId, int? version, String? sourceUri) plex,required TResult Function( String id, List<MediaItem> items, String backendId, int? currentIndex, bool shuffled) local,}) {final _that = this;
switch (_that) {
case PlexServerPlayQueue():
return plex(_that.playQueueId,_that.items,_that.currentIndex,_that.shuffled,_that.selectedItemId,_that.version,_that.sourceUri);case LocalPlayQueue():
return local(_that.id,_that.items,_that.backendId,_that.currentIndex,_that.shuffled);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( int playQueueId, List<MediaItem> items, int? currentIndex, bool shuffled, int? selectedItemId, int? version, String? sourceUri)? plex,TResult? Function( String id, List<MediaItem> items, String backendId, int? currentIndex, bool shuffled)? local,}) {final _that = this;
switch (_that) {
case PlexServerPlayQueue() when plex != null:
return plex(_that.playQueueId,_that.items,_that.currentIndex,_that.shuffled,_that.selectedItemId,_that.version,_that.sourceUri);case LocalPlayQueue() when local != null:
return local(_that.id,_that.items,_that.backendId,_that.currentIndex,_that.shuffled);case _:
return null;
}
}
}
/// @nodoc
class PlexServerPlayQueue extends PlayQueue {
const PlexServerPlayQueue({required this.playQueueId, required final List<MediaItem> items, this.currentIndex, this.shuffled = false, this.selectedItemId, this.version, this.sourceUri}): _items = items,super._();
/// Plex `playQueueID` — addresses the queue for subsequent fetches.
final int playQueueId;
final List<MediaItem> _items;
@override List<MediaItem> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@override final int? currentIndex;
@override@JsonKey() final bool shuffled;
/// Plex `playQueueSelectedItemID` of the active item.
final int? selectedItemId;
/// Plex `playQueueVersion` — server-side optimistic concurrency token.
final int? version;
/// Plex `playQueueSourceURI` — used for "Up Next" derivation.
final String? sourceUri;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PlexServerPlayQueueCopyWith<PlexServerPlayQueue> get copyWith => _$PlexServerPlayQueueCopyWithImpl<PlexServerPlayQueue>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PlexServerPlayQueue&&(identical(other.playQueueId, playQueueId) || other.playQueueId == playQueueId)&&const DeepCollectionEquality().equals(other._items, _items)&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.shuffled, shuffled) || other.shuffled == shuffled)&&(identical(other.selectedItemId, selectedItemId) || other.selectedItemId == selectedItemId)&&(identical(other.version, version) || other.version == version)&&(identical(other.sourceUri, sourceUri) || other.sourceUri == sourceUri));
}
@override
int get hashCode => Object.hash(runtimeType,playQueueId,const DeepCollectionEquality().hash(_items),currentIndex,shuffled,selectedItemId,version,sourceUri);
@override
String toString() {
return 'PlayQueue.plex(playQueueId: $playQueueId, items: $items, currentIndex: $currentIndex, shuffled: $shuffled, selectedItemId: $selectedItemId, version: $version, sourceUri: $sourceUri)';
}
}
/// @nodoc
abstract mixin class $PlexServerPlayQueueCopyWith<$Res> implements $PlayQueueCopyWith<$Res> {
factory $PlexServerPlayQueueCopyWith(PlexServerPlayQueue value, $Res Function(PlexServerPlayQueue) _then) = _$PlexServerPlayQueueCopyWithImpl;
@override @useResult
$Res call({
int playQueueId, List<MediaItem> items, int? currentIndex, bool shuffled, int? selectedItemId, int? version, String? sourceUri
});
}
/// @nodoc
class _$PlexServerPlayQueueCopyWithImpl<$Res>
implements $PlexServerPlayQueueCopyWith<$Res> {
_$PlexServerPlayQueueCopyWithImpl(this._self, this._then);
final PlexServerPlayQueue _self;
final $Res Function(PlexServerPlayQueue) _then;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? playQueueId = null,Object? items = null,Object? currentIndex = freezed,Object? shuffled = null,Object? selectedItemId = freezed,Object? version = freezed,Object? sourceUri = freezed,}) {
return _then(PlexServerPlayQueue(
playQueueId: null == playQueueId ? _self.playQueueId : playQueueId // ignore: cast_nullable_to_non_nullable
as int,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<MediaItem>,currentIndex: freezed == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable
as int?,shuffled: null == shuffled ? _self.shuffled : shuffled // ignore: cast_nullable_to_non_nullable
as bool,selectedItemId: freezed == selectedItemId ? _self.selectedItemId : selectedItemId // ignore: cast_nullable_to_non_nullable
as int?,version: freezed == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
as int?,sourceUri: freezed == sourceUri ? _self.sourceUri : sourceUri // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class LocalPlayQueue extends PlayQueue {
const LocalPlayQueue({required this.id, required final List<MediaItem> items, required this.backendId, this.currentIndex, this.shuffled = false}): _items = items,super._();
/// Client-generated UUID identifying this queue for the session.
final String id;
final List<MediaItem> _items;
@override List<MediaItem> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
/// Server kind that owns this queue's items (typically `"jellyfin"`).
final String backendId;
@override final int? currentIndex;
@override@JsonKey() final bool shuffled;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LocalPlayQueueCopyWith<LocalPlayQueue> get copyWith => _$LocalPlayQueueCopyWithImpl<LocalPlayQueue>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocalPlayQueue&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._items, _items)&&(identical(other.backendId, backendId) || other.backendId == backendId)&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.shuffled, shuffled) || other.shuffled == shuffled));
}
@override
int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(_items),backendId,currentIndex,shuffled);
@override
String toString() {
return 'PlayQueue.local(id: $id, items: $items, backendId: $backendId, currentIndex: $currentIndex, shuffled: $shuffled)';
}
}
/// @nodoc
abstract mixin class $LocalPlayQueueCopyWith<$Res> implements $PlayQueueCopyWith<$Res> {
factory $LocalPlayQueueCopyWith(LocalPlayQueue value, $Res Function(LocalPlayQueue) _then) = _$LocalPlayQueueCopyWithImpl;
@override @useResult
$Res call({
String id, List<MediaItem> items, String backendId, int? currentIndex, bool shuffled
});
}
/// @nodoc
class _$LocalPlayQueueCopyWithImpl<$Res>
implements $LocalPlayQueueCopyWith<$Res> {
_$LocalPlayQueueCopyWithImpl(this._self, this._then);
final LocalPlayQueue _self;
final $Res Function(LocalPlayQueue) _then;
/// Create a copy of PlayQueue
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? items = null,Object? backendId = null,Object? currentIndex = freezed,Object? shuffled = null,}) {
return _then(LocalPlayQueue(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<MediaItem>,backendId: null == backendId ? _self.backendId : backendId // ignore: cast_nullable_to_non_nullable
as String,currentIndex: freezed == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable
as int?,shuffled: null == shuffled ? _self.shuffled : shuffled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on
+20 -20
View File
@@ -1,3 +1,11 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../utils/json_converters.dart';
part 'remote_command.freezed.dart';
part 'remote_command.g.dart';
enum RemoteCommandType { enum RemoteCommandType {
dpadUp, dpadUp,
dpadDown, dpadDown,
@@ -46,24 +54,16 @@ enum RemoteCommandType {
syncState, syncState,
} }
class RemoteCommand { class _RemoteCommandTypeConverter extends IndexedEnumConverter<RemoteCommandType> {
final RemoteCommandType type; const _RemoteCommandTypeConverter() : super(RemoteCommandType.values, RemoteCommandType.ping);
final Map<String, dynamic>? data; }
const RemoteCommand({required this.type, this.data}); @freezed
sealed class RemoteCommand with _$RemoteCommand {
factory RemoteCommand.fromJson(Map<String, dynamic> json) { const factory RemoteCommand({
final index = json['t'] as int; @JsonKey(name: 't') @_RemoteCommandTypeConverter() required RemoteCommandType type,
return RemoteCommand( @JsonKey(name: 'd') Map<String, dynamic>? data,
type: index < RemoteCommandType.values.length ? RemoteCommandType.values[index] : RemoteCommandType.ping, }) = _RemoteCommand;
data: json['d'] as Map<String, dynamic>?,
); factory RemoteCommand.fromJson(Map<String, dynamic> json) => _$RemoteCommandFromJson(json);
}
Map<String, dynamic> toJson() {
return {'t': type.index, if (data != null) 'd': data};
}
@override
String toString() => 'RemoteCommand(${type.name}, data: $data)';
} }
@@ -0,0 +1,282 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'remote_command.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RemoteCommand {
@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType get type;@JsonKey(name: 'd') Map<String, dynamic>? get data;
/// Create a copy of RemoteCommand
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RemoteCommandCopyWith<RemoteCommand> get copyWith => _$RemoteCommandCopyWithImpl<RemoteCommand>(this as RemoteCommand, _$identity);
/// Serializes this RemoteCommand to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteCommand&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(data));
@override
String toString() {
return 'RemoteCommand(type: $type, data: $data)';
}
}
/// @nodoc
abstract mixin class $RemoteCommandCopyWith<$Res> {
factory $RemoteCommandCopyWith(RemoteCommand value, $Res Function(RemoteCommand) _then) = _$RemoteCommandCopyWithImpl;
@useResult
$Res call({
@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType type,@JsonKey(name: 'd') Map<String, dynamic>? data
});
}
/// @nodoc
class _$RemoteCommandCopyWithImpl<$Res>
implements $RemoteCommandCopyWith<$Res> {
_$RemoteCommandCopyWithImpl(this._self, this._then);
final RemoteCommand _self;
final $Res Function(RemoteCommand) _then;
/// Create a copy of RemoteCommand
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? data = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as RemoteCommandType,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
}
/// Adds pattern-matching-related methods to [RemoteCommand].
extension RemoteCommandPatterns on RemoteCommand {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RemoteCommand value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _RemoteCommand() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RemoteCommand value) $default,){
final _that = this;
switch (_that) {
case _RemoteCommand():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RemoteCommand value)? $default,){
final _that = this;
switch (_that) {
case _RemoteCommand() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType type, @JsonKey(name: 'd') Map<String, dynamic>? data)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RemoteCommand() when $default != null:
return $default(_that.type,_that.data);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType type, @JsonKey(name: 'd') Map<String, dynamic>? data) $default,) {final _that = this;
switch (_that) {
case _RemoteCommand():
return $default(_that.type,_that.data);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType type, @JsonKey(name: 'd') Map<String, dynamic>? data)? $default,) {final _that = this;
switch (_that) {
case _RemoteCommand() when $default != null:
return $default(_that.type,_that.data);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _RemoteCommand implements RemoteCommand {
const _RemoteCommand({@JsonKey(name: 't')@_RemoteCommandTypeConverter() required this.type, @JsonKey(name: 'd') final Map<String, dynamic>? data}): _data = data;
factory _RemoteCommand.fromJson(Map<String, dynamic> json) => _$RemoteCommandFromJson(json);
@override@JsonKey(name: 't')@_RemoteCommandTypeConverter() final RemoteCommandType type;
final Map<String, dynamic>? _data;
@override@JsonKey(name: 'd') Map<String, dynamic>? get data {
final value = _data;
if (value == null) return null;
if (_data is EqualUnmodifiableMapView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
/// Create a copy of RemoteCommand
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RemoteCommandCopyWith<_RemoteCommand> get copyWith => __$RemoteCommandCopyWithImpl<_RemoteCommand>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$RemoteCommandToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteCommand&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._data, _data));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_data));
@override
String toString() {
return 'RemoteCommand(type: $type, data: $data)';
}
}
/// @nodoc
abstract mixin class _$RemoteCommandCopyWith<$Res> implements $RemoteCommandCopyWith<$Res> {
factory _$RemoteCommandCopyWith(_RemoteCommand value, $Res Function(_RemoteCommand) _then) = __$RemoteCommandCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(name: 't')@_RemoteCommandTypeConverter() RemoteCommandType type,@JsonKey(name: 'd') Map<String, dynamic>? data
});
}
/// @nodoc
class __$RemoteCommandCopyWithImpl<$Res>
implements _$RemoteCommandCopyWith<$Res> {
__$RemoteCommandCopyWithImpl(this._self, this._then);
final _RemoteCommand _self;
final $Res Function(_RemoteCommand) _then;
/// Create a copy of RemoteCommand
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? data = freezed,}) {
return _then(_RemoteCommand(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as RemoteCommandType,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
}
// dart format on
@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'remote_command.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_RemoteCommand _$RemoteCommandFromJson(Map<String, dynamic> json) =>
_RemoteCommand(
type: const _RemoteCommandTypeConverter().fromJson(
(json['t'] as num).toInt(),
),
data: json['d'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$RemoteCommandToJson(_RemoteCommand instance) =>
<String, dynamic>{
't': const _RemoteCommandTypeConverter().toJson(instance.type),
'd': instance.data,
};
+21 -86
View File
@@ -1,100 +1,35 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'remote_session.g.dart'; part 'remote_session.freezed.dart';
enum RemoteSessionRole { host, remote } enum RemoteSessionRole { host, remote }
enum RemoteSessionStatus { disconnected, connecting, connected, reconnecting, error } enum RemoteSessionStatus { disconnected, connecting, connected, reconnecting, error }
@JsonSerializable() @freezed
class RemoteDevice { sealed class RemoteDevice with _$RemoteDevice {
final String id; const factory RemoteDevice({
final String name; required String id,
final String platform; required String name,
final DateTime connectedAt; required String platform,
final Map<String, bool> capabilities; required DateTime connectedAt,
@Default(<String, bool>{}) Map<String, bool> capabilities,
RemoteDevice({ }) = _RemoteDevice;
required this.id,
required this.name,
required this.platform,
DateTime? connectedAt,
Map<String, bool>? capabilities,
}) : connectedAt = connectedAt ?? DateTime.now(),
capabilities = capabilities ?? {};
factory RemoteDevice.fromJson(Map<String, dynamic> json) => _$RemoteDeviceFromJson(json);
Map<String, dynamic> toJson() => _$RemoteDeviceToJson(this);
RemoteDevice copyWith({
String? id,
String? name,
String? platform,
DateTime? connectedAt,
Map<String, bool>? capabilities,
}) {
return RemoteDevice(
id: id ?? this.id,
name: name ?? this.name,
platform: platform ?? this.platform,
connectedAt: connectedAt ?? this.connectedAt,
capabilities: capabilities ?? this.capabilities,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is RemoteDevice && other.id == id;
}
@override
int get hashCode => id.hashCode;
} }
@JsonSerializable() @freezed
class RemoteSession { sealed class RemoteSession with _$RemoteSession {
@JsonKey(unknownEnumValue: RemoteSessionRole.remote) const RemoteSession._();
final RemoteSessionRole role;
@JsonKey(unknownEnumValue: RemoteSessionStatus.disconnected)
final RemoteSessionStatus status;
final RemoteDevice? connectedDevice;
final DateTime createdAt;
final String? errorMessage;
RemoteSession({ const factory RemoteSession({
required this.role, required RemoteSessionRole role,
this.status = RemoteSessionStatus.disconnected, @Default(RemoteSessionStatus.disconnected) RemoteSessionStatus status,
this.connectedDevice, RemoteDevice? connectedDevice,
DateTime? createdAt, required DateTime createdAt,
this.errorMessage, String? errorMessage,
}) : createdAt = createdAt ?? DateTime.now(); }) = _RemoteSession;
bool get isConnected => status == RemoteSessionStatus.connected; bool get isConnected => status == RemoteSessionStatus.connected;
bool get isHost => role == RemoteSessionRole.host; bool get isHost => role == RemoteSessionRole.host;
bool get isRemote => role == RemoteSessionRole.remote; bool get isRemote => role == RemoteSessionRole.remote;
factory RemoteSession.fromJson(Map<String, dynamic> json) => _$RemoteSessionFromJson(json);
Map<String, dynamic> toJson() => _$RemoteSessionToJson(this);
RemoteSession copyWith({
RemoteSessionRole? role,
RemoteSessionStatus? status,
RemoteDevice? connectedDevice,
bool clearConnectedDevice = false,
DateTime? createdAt,
String? errorMessage,
bool clearErrorMessage = false,
}) {
return RemoteSession(
role: role ?? this.role,
status: status ?? this.status,
connectedDevice: clearConnectedDevice ? null : (connectedDevice ?? this.connectedDevice),
createdAt: createdAt ?? this.createdAt,
errorMessage: clearErrorMessage ? null : (errorMessage ?? this.errorMessage),
);
}
} }
@@ -0,0 +1,570 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'remote_session.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RemoteDevice {
String get id; String get name; String get platform; DateTime get connectedAt; Map<String, bool> get capabilities;
/// Create a copy of RemoteDevice
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RemoteDeviceCopyWith<RemoteDevice> get copyWith => _$RemoteDeviceCopyWithImpl<RemoteDevice>(this as RemoteDevice, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteDevice&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.connectedAt, connectedAt) || other.connectedAt == connectedAt)&&const DeepCollectionEquality().equals(other.capabilities, capabilities));
}
@override
int get hashCode => Object.hash(runtimeType,id,name,platform,connectedAt,const DeepCollectionEquality().hash(capabilities));
@override
String toString() {
return 'RemoteDevice(id: $id, name: $name, platform: $platform, connectedAt: $connectedAt, capabilities: $capabilities)';
}
}
/// @nodoc
abstract mixin class $RemoteDeviceCopyWith<$Res> {
factory $RemoteDeviceCopyWith(RemoteDevice value, $Res Function(RemoteDevice) _then) = _$RemoteDeviceCopyWithImpl;
@useResult
$Res call({
String id, String name, String platform, DateTime connectedAt, Map<String, bool> capabilities
});
}
/// @nodoc
class _$RemoteDeviceCopyWithImpl<$Res>
implements $RemoteDeviceCopyWith<$Res> {
_$RemoteDeviceCopyWithImpl(this._self, this._then);
final RemoteDevice _self;
final $Res Function(RemoteDevice) _then;
/// Create a copy of RemoteDevice
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? platform = null,Object? connectedAt = null,Object? capabilities = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as String,connectedAt: null == connectedAt ? _self.connectedAt : connectedAt // ignore: cast_nullable_to_non_nullable
as DateTime,capabilities: null == capabilities ? _self.capabilities : capabilities // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
));
}
}
/// Adds pattern-matching-related methods to [RemoteDevice].
extension RemoteDevicePatterns on RemoteDevice {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RemoteDevice value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _RemoteDevice() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RemoteDevice value) $default,){
final _that = this;
switch (_that) {
case _RemoteDevice():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RemoteDevice value)? $default,){
final _that = this;
switch (_that) {
case _RemoteDevice() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String platform, DateTime connectedAt, Map<String, bool> capabilities)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RemoteDevice() when $default != null:
return $default(_that.id,_that.name,_that.platform,_that.connectedAt,_that.capabilities);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String platform, DateTime connectedAt, Map<String, bool> capabilities) $default,) {final _that = this;
switch (_that) {
case _RemoteDevice():
return $default(_that.id,_that.name,_that.platform,_that.connectedAt,_that.capabilities);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String platform, DateTime connectedAt, Map<String, bool> capabilities)? $default,) {final _that = this;
switch (_that) {
case _RemoteDevice() when $default != null:
return $default(_that.id,_that.name,_that.platform,_that.connectedAt,_that.capabilities);case _:
return null;
}
}
}
/// @nodoc
class _RemoteDevice implements RemoteDevice {
const _RemoteDevice({required this.id, required this.name, required this.platform, required this.connectedAt, final Map<String, bool> capabilities = const <String, bool>{}}): _capabilities = capabilities;
@override final String id;
@override final String name;
@override final String platform;
@override final DateTime connectedAt;
final Map<String, bool> _capabilities;
@override@JsonKey() Map<String, bool> get capabilities {
if (_capabilities is EqualUnmodifiableMapView) return _capabilities;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_capabilities);
}
/// Create a copy of RemoteDevice
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RemoteDeviceCopyWith<_RemoteDevice> get copyWith => __$RemoteDeviceCopyWithImpl<_RemoteDevice>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteDevice&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.connectedAt, connectedAt) || other.connectedAt == connectedAt)&&const DeepCollectionEquality().equals(other._capabilities, _capabilities));
}
@override
int get hashCode => Object.hash(runtimeType,id,name,platform,connectedAt,const DeepCollectionEquality().hash(_capabilities));
@override
String toString() {
return 'RemoteDevice(id: $id, name: $name, platform: $platform, connectedAt: $connectedAt, capabilities: $capabilities)';
}
}
/// @nodoc
abstract mixin class _$RemoteDeviceCopyWith<$Res> implements $RemoteDeviceCopyWith<$Res> {
factory _$RemoteDeviceCopyWith(_RemoteDevice value, $Res Function(_RemoteDevice) _then) = __$RemoteDeviceCopyWithImpl;
@override @useResult
$Res call({
String id, String name, String platform, DateTime connectedAt, Map<String, bool> capabilities
});
}
/// @nodoc
class __$RemoteDeviceCopyWithImpl<$Res>
implements _$RemoteDeviceCopyWith<$Res> {
__$RemoteDeviceCopyWithImpl(this._self, this._then);
final _RemoteDevice _self;
final $Res Function(_RemoteDevice) _then;
/// Create a copy of RemoteDevice
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? platform = null,Object? connectedAt = null,Object? capabilities = null,}) {
return _then(_RemoteDevice(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as String,connectedAt: null == connectedAt ? _self.connectedAt : connectedAt // ignore: cast_nullable_to_non_nullable
as DateTime,capabilities: null == capabilities ? _self._capabilities : capabilities // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
));
}
}
/// @nodoc
mixin _$RemoteSession {
RemoteSessionRole get role; RemoteSessionStatus get status; RemoteDevice? get connectedDevice; DateTime get createdAt; String? get errorMessage;
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RemoteSessionCopyWith<RemoteSession> get copyWith => _$RemoteSessionCopyWithImpl<RemoteSession>(this as RemoteSession, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteSession&&(identical(other.role, role) || other.role == role)&&(identical(other.status, status) || other.status == status)&&(identical(other.connectedDevice, connectedDevice) || other.connectedDevice == connectedDevice)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,role,status,connectedDevice,createdAt,errorMessage);
@override
String toString() {
return 'RemoteSession(role: $role, status: $status, connectedDevice: $connectedDevice, createdAt: $createdAt, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class $RemoteSessionCopyWith<$Res> {
factory $RemoteSessionCopyWith(RemoteSession value, $Res Function(RemoteSession) _then) = _$RemoteSessionCopyWithImpl;
@useResult
$Res call({
RemoteSessionRole role, RemoteSessionStatus status, RemoteDevice? connectedDevice, DateTime createdAt, String? errorMessage
});
$RemoteDeviceCopyWith<$Res>? get connectedDevice;
}
/// @nodoc
class _$RemoteSessionCopyWithImpl<$Res>
implements $RemoteSessionCopyWith<$Res> {
_$RemoteSessionCopyWithImpl(this._self, this._then);
final RemoteSession _self;
final $Res Function(RemoteSession) _then;
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? role = null,Object? status = null,Object? connectedDevice = freezed,Object? createdAt = null,Object? errorMessage = freezed,}) {
return _then(_self.copyWith(
role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
as RemoteSessionRole,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as RemoteSessionStatus,connectedDevice: freezed == connectedDevice ? _self.connectedDevice : connectedDevice // ignore: cast_nullable_to_non_nullable
as RemoteDevice?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$RemoteDeviceCopyWith<$Res>? get connectedDevice {
if (_self.connectedDevice == null) {
return null;
}
return $RemoteDeviceCopyWith<$Res>(_self.connectedDevice!, (value) {
return _then(_self.copyWith(connectedDevice: value));
});
}
}
/// Adds pattern-matching-related methods to [RemoteSession].
extension RemoteSessionPatterns on RemoteSession {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RemoteSession value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _RemoteSession() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RemoteSession value) $default,){
final _that = this;
switch (_that) {
case _RemoteSession():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RemoteSession value)? $default,){
final _that = this;
switch (_that) {
case _RemoteSession() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( RemoteSessionRole role, RemoteSessionStatus status, RemoteDevice? connectedDevice, DateTime createdAt, String? errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RemoteSession() when $default != null:
return $default(_that.role,_that.status,_that.connectedDevice,_that.createdAt,_that.errorMessage);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( RemoteSessionRole role, RemoteSessionStatus status, RemoteDevice? connectedDevice, DateTime createdAt, String? errorMessage) $default,) {final _that = this;
switch (_that) {
case _RemoteSession():
return $default(_that.role,_that.status,_that.connectedDevice,_that.createdAt,_that.errorMessage);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( RemoteSessionRole role, RemoteSessionStatus status, RemoteDevice? connectedDevice, DateTime createdAt, String? errorMessage)? $default,) {final _that = this;
switch (_that) {
case _RemoteSession() when $default != null:
return $default(_that.role,_that.status,_that.connectedDevice,_that.createdAt,_that.errorMessage);case _:
return null;
}
}
}
/// @nodoc
class _RemoteSession extends RemoteSession {
const _RemoteSession({required this.role, this.status = RemoteSessionStatus.disconnected, this.connectedDevice, required this.createdAt, this.errorMessage}): super._();
@override final RemoteSessionRole role;
@override@JsonKey() final RemoteSessionStatus status;
@override final RemoteDevice? connectedDevice;
@override final DateTime createdAt;
@override final String? errorMessage;
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RemoteSessionCopyWith<_RemoteSession> get copyWith => __$RemoteSessionCopyWithImpl<_RemoteSession>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteSession&&(identical(other.role, role) || other.role == role)&&(identical(other.status, status) || other.status == status)&&(identical(other.connectedDevice, connectedDevice) || other.connectedDevice == connectedDevice)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,role,status,connectedDevice,createdAt,errorMessage);
@override
String toString() {
return 'RemoteSession(role: $role, status: $status, connectedDevice: $connectedDevice, createdAt: $createdAt, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class _$RemoteSessionCopyWith<$Res> implements $RemoteSessionCopyWith<$Res> {
factory _$RemoteSessionCopyWith(_RemoteSession value, $Res Function(_RemoteSession) _then) = __$RemoteSessionCopyWithImpl;
@override @useResult
$Res call({
RemoteSessionRole role, RemoteSessionStatus status, RemoteDevice? connectedDevice, DateTime createdAt, String? errorMessage
});
@override $RemoteDeviceCopyWith<$Res>? get connectedDevice;
}
/// @nodoc
class __$RemoteSessionCopyWithImpl<$Res>
implements _$RemoteSessionCopyWith<$Res> {
__$RemoteSessionCopyWithImpl(this._self, this._then);
final _RemoteSession _self;
final $Res Function(_RemoteSession) _then;
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? role = null,Object? status = null,Object? connectedDevice = freezed,Object? createdAt = null,Object? errorMessage = freezed,}) {
return _then(_RemoteSession(
role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
as RemoteSessionRole,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as RemoteSessionStatus,connectedDevice: freezed == connectedDevice ? _self.connectedDevice : connectedDevice // ignore: cast_nullable_to_non_nullable
as RemoteDevice?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of RemoteSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$RemoteDeviceCopyWith<$Res>? get connectedDevice {
if (_self.connectedDevice == null) {
return null;
}
return $RemoteDeviceCopyWith<$Res>(_self.connectedDevice!, (value) {
return _then(_self.copyWith(connectedDevice: value));
});
}
}
// dart format on
@@ -1,75 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'remote_session.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
RemoteDevice _$RemoteDeviceFromJson(Map<String, dynamic> json) => RemoteDevice(
id: json['id'] as String,
name: json['name'] as String,
platform: json['platform'] as String,
connectedAt: json['connectedAt'] == null
? null
: DateTime.parse(json['connectedAt'] as String),
capabilities: (json['capabilities'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as bool),
),
);
Map<String, dynamic> _$RemoteDeviceToJson(RemoteDevice instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'platform': instance.platform,
'connectedAt': instance.connectedAt.toIso8601String(),
'capabilities': instance.capabilities,
};
RemoteSession _$RemoteSessionFromJson(Map<String, dynamic> json) =>
RemoteSession(
role: $enumDecode(
_$RemoteSessionRoleEnumMap,
json['role'],
unknownValue: RemoteSessionRole.remote,
),
status:
$enumDecodeNullable(
_$RemoteSessionStatusEnumMap,
json['status'],
unknownValue: RemoteSessionStatus.disconnected,
) ??
RemoteSessionStatus.disconnected,
connectedDevice: json['connectedDevice'] == null
? null
: RemoteDevice.fromJson(
json['connectedDevice'] as Map<String, dynamic>,
),
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
errorMessage: json['errorMessage'] as String?,
);
Map<String, dynamic> _$RemoteSessionToJson(RemoteSession instance) =>
<String, dynamic>{
'role': _$RemoteSessionRoleEnumMap[instance.role]!,
'status': _$RemoteSessionStatusEnumMap[instance.status]!,
'connectedDevice': instance.connectedDevice,
'createdAt': instance.createdAt.toIso8601String(),
'errorMessage': instance.errorMessage,
};
const _$RemoteSessionRoleEnumMap = {
RemoteSessionRole.host: 'host',
RemoteSessionRole.remote: 'remote',
};
const _$RemoteSessionStatusEnumMap = {
RemoteSessionStatus.disconnected: 'disconnected',
RemoteSessionStatus.connecting: 'connecting',
RemoteSessionStatus.connected: 'connected',
RemoteSessionStatus.reconnecting: 'reconnecting',
RemoteSessionStatus.error: 'error',
};
+29 -83
View File
@@ -1,5 +1,10 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
import '../utils/formatters.dart'; import '../utils/formatters.dart';
part 'download_models.freezed.dart';
enum DownloadStatus { enum DownloadStatus {
queued, queued,
downloading, downloading,
@@ -10,30 +15,21 @@ enum DownloadStatus {
partial, // Some episodes downloaded, but not all (for shows/seasons) partial, // Some episodes downloaded, but not all (for shows/seasons)
} }
class DownloadProgress { @freezed
final String globalKey; sealed class DownloadProgress with _$DownloadProgress {
final DownloadStatus status; const DownloadProgress._();
final int progress; // 0-100
final int downloadedBytes;
final int totalBytes;
final double speed; // bytes per second
final String? errorMessage;
final String? currentFile; // What's being downloaded (video, subtitles, artwork)
// Thumbnail path (populated after artwork download completes) const factory DownloadProgress({
final String? thumbPath; required String globalKey,
required DownloadStatus status,
const DownloadProgress({ @Default(0) int progress,
required this.globalKey, @Default(0) int downloadedBytes,
required this.status, @Default(0) int totalBytes,
this.progress = 0, @Default(0.0) double speed,
this.downloadedBytes = 0, String? errorMessage,
this.totalBytes = 0, String? currentFile,
this.speed = 0, String? thumbPath,
this.errorMessage, }) = _DownloadProgress;
this.currentFile,
this.thumbPath,
});
double get progressPercent => progress / 100.0; double get progressPercent => progress / 100.0;
@@ -42,73 +38,23 @@ class DownloadProgress {
String get totalFormatted => ByteFormatter.formatBytes(totalBytes); String get totalFormatted => ByteFormatter.formatBytes(totalBytes);
bool get hasArtworkPaths => thumbPath != null; bool get hasArtworkPaths => thumbPath != null;
DownloadProgress copyWith({
String? globalKey,
DownloadStatus? status,
int? progress,
int? downloadedBytes,
int? totalBytes,
double? speed,
String? errorMessage,
String? currentFile,
String? thumbPath,
}) {
return DownloadProgress(
globalKey: globalKey ?? this.globalKey,
status: status ?? this.status,
progress: progress ?? this.progress,
downloadedBytes: downloadedBytes ?? this.downloadedBytes,
totalBytes: totalBytes ?? this.totalBytes,
speed: speed ?? this.speed,
errorMessage: errorMessage ?? this.errorMessage,
currentFile: currentFile ?? this.currentFile,
thumbPath: thumbPath ?? this.thumbPath,
);
}
} }
class DeletionProgress { @freezed
final String globalKey; sealed class DeletionProgress with _$DeletionProgress {
final String itemTitle; const DeletionProgress._();
final int currentItem;
final int totalItems;
final String? currentOperation;
const DeletionProgress({ const factory DeletionProgress({
required this.globalKey, required String globalKey,
required this.itemTitle, required String itemTitle,
required this.currentItem, required int currentItem,
required this.totalItems, required int totalItems,
this.currentOperation, String? currentOperation,
}); }) = _DeletionProgress;
double get progressPercent => totalItems > 0 ? (currentItem / totalItems) : 0.0; double get progressPercent => totalItems > 0 ? (currentItem / totalItems) : 0.0;
int get progressPercentInt => (progressPercent * 100).round(); int get progressPercentInt => (progressPercent * 100).round();
bool get isComplete => currentItem >= totalItems; bool get isComplete => currentItem >= totalItems;
DeletionProgress copyWith({
String? globalKey,
String? itemTitle,
int? currentItem,
int? totalItems,
String? currentOperation,
}) {
return DeletionProgress(
globalKey: globalKey ?? this.globalKey,
itemTitle: itemTitle ?? this.itemTitle,
currentItem: currentItem ?? this.currentItem,
totalItems: totalItems ?? this.totalItems,
currentOperation: currentOperation ?? this.currentOperation,
);
}
@override
String toString() {
return 'DeletionProgress(globalKey: $globalKey, itemTitle: $itemTitle, '
'currentItem: $currentItem, totalItems: $totalItems, '
'progressPercent: $progressPercentInt%)';
}
} }
+552
View File
@@ -0,0 +1,552 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'download_models.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DownloadProgress {
String get globalKey; DownloadStatus get status; int get progress; int get downloadedBytes; int get totalBytes; double get speed; String? get errorMessage; String? get currentFile; String? get thumbPath;
/// Create a copy of DownloadProgress
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DownloadProgressCopyWith<DownloadProgress> get copyWith => _$DownloadProgressCopyWithImpl<DownloadProgress>(this as DownloadProgress, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadProgress&&(identical(other.globalKey, globalKey) || other.globalKey == globalKey)&&(identical(other.status, status) || other.status == status)&&(identical(other.progress, progress) || other.progress == progress)&&(identical(other.downloadedBytes, downloadedBytes) || other.downloadedBytes == downloadedBytes)&&(identical(other.totalBytes, totalBytes) || other.totalBytes == totalBytes)&&(identical(other.speed, speed) || other.speed == speed)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.currentFile, currentFile) || other.currentFile == currentFile)&&(identical(other.thumbPath, thumbPath) || other.thumbPath == thumbPath));
}
@override
int get hashCode => Object.hash(runtimeType,globalKey,status,progress,downloadedBytes,totalBytes,speed,errorMessage,currentFile,thumbPath);
@override
String toString() {
return 'DownloadProgress(globalKey: $globalKey, status: $status, progress: $progress, downloadedBytes: $downloadedBytes, totalBytes: $totalBytes, speed: $speed, errorMessage: $errorMessage, currentFile: $currentFile, thumbPath: $thumbPath)';
}
}
/// @nodoc
abstract mixin class $DownloadProgressCopyWith<$Res> {
factory $DownloadProgressCopyWith(DownloadProgress value, $Res Function(DownloadProgress) _then) = _$DownloadProgressCopyWithImpl;
@useResult
$Res call({
String globalKey, DownloadStatus status, int progress, int downloadedBytes, int totalBytes, double speed, String? errorMessage, String? currentFile, String? thumbPath
});
}
/// @nodoc
class _$DownloadProgressCopyWithImpl<$Res>
implements $DownloadProgressCopyWith<$Res> {
_$DownloadProgressCopyWithImpl(this._self, this._then);
final DownloadProgress _self;
final $Res Function(DownloadProgress) _then;
/// Create a copy of DownloadProgress
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? globalKey = null,Object? status = null,Object? progress = null,Object? downloadedBytes = null,Object? totalBytes = null,Object? speed = null,Object? errorMessage = freezed,Object? currentFile = freezed,Object? thumbPath = freezed,}) {
return _then(_self.copyWith(
globalKey: null == globalKey ? _self.globalKey : globalKey // ignore: cast_nullable_to_non_nullable
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as DownloadStatus,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
as int,downloadedBytes: null == downloadedBytes ? _self.downloadedBytes : downloadedBytes // ignore: cast_nullable_to_non_nullable
as int,totalBytes: null == totalBytes ? _self.totalBytes : totalBytes // ignore: cast_nullable_to_non_nullable
as int,speed: null == speed ? _self.speed : speed // ignore: cast_nullable_to_non_nullable
as double,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,currentFile: freezed == currentFile ? _self.currentFile : currentFile // ignore: cast_nullable_to_non_nullable
as String?,thumbPath: freezed == thumbPath ? _self.thumbPath : thumbPath // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [DownloadProgress].
extension DownloadProgressPatterns on DownloadProgress {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DownloadProgress value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DownloadProgress() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DownloadProgress value) $default,){
final _that = this;
switch (_that) {
case _DownloadProgress():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DownloadProgress value)? $default,){
final _that = this;
switch (_that) {
case _DownloadProgress() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String globalKey, DownloadStatus status, int progress, int downloadedBytes, int totalBytes, double speed, String? errorMessage, String? currentFile, String? thumbPath)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DownloadProgress() when $default != null:
return $default(_that.globalKey,_that.status,_that.progress,_that.downloadedBytes,_that.totalBytes,_that.speed,_that.errorMessage,_that.currentFile,_that.thumbPath);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String globalKey, DownloadStatus status, int progress, int downloadedBytes, int totalBytes, double speed, String? errorMessage, String? currentFile, String? thumbPath) $default,) {final _that = this;
switch (_that) {
case _DownloadProgress():
return $default(_that.globalKey,_that.status,_that.progress,_that.downloadedBytes,_that.totalBytes,_that.speed,_that.errorMessage,_that.currentFile,_that.thumbPath);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String globalKey, DownloadStatus status, int progress, int downloadedBytes, int totalBytes, double speed, String? errorMessage, String? currentFile, String? thumbPath)? $default,) {final _that = this;
switch (_that) {
case _DownloadProgress() when $default != null:
return $default(_that.globalKey,_that.status,_that.progress,_that.downloadedBytes,_that.totalBytes,_that.speed,_that.errorMessage,_that.currentFile,_that.thumbPath);case _:
return null;
}
}
}
/// @nodoc
class _DownloadProgress extends DownloadProgress {
const _DownloadProgress({required this.globalKey, required this.status, this.progress = 0, this.downloadedBytes = 0, this.totalBytes = 0, this.speed = 0.0, this.errorMessage, this.currentFile, this.thumbPath}): super._();
@override final String globalKey;
@override final DownloadStatus status;
@override@JsonKey() final int progress;
@override@JsonKey() final int downloadedBytes;
@override@JsonKey() final int totalBytes;
@override@JsonKey() final double speed;
@override final String? errorMessage;
@override final String? currentFile;
@override final String? thumbPath;
/// Create a copy of DownloadProgress
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DownloadProgressCopyWith<_DownloadProgress> get copyWith => __$DownloadProgressCopyWithImpl<_DownloadProgress>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DownloadProgress&&(identical(other.globalKey, globalKey) || other.globalKey == globalKey)&&(identical(other.status, status) || other.status == status)&&(identical(other.progress, progress) || other.progress == progress)&&(identical(other.downloadedBytes, downloadedBytes) || other.downloadedBytes == downloadedBytes)&&(identical(other.totalBytes, totalBytes) || other.totalBytes == totalBytes)&&(identical(other.speed, speed) || other.speed == speed)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.currentFile, currentFile) || other.currentFile == currentFile)&&(identical(other.thumbPath, thumbPath) || other.thumbPath == thumbPath));
}
@override
int get hashCode => Object.hash(runtimeType,globalKey,status,progress,downloadedBytes,totalBytes,speed,errorMessage,currentFile,thumbPath);
@override
String toString() {
return 'DownloadProgress(globalKey: $globalKey, status: $status, progress: $progress, downloadedBytes: $downloadedBytes, totalBytes: $totalBytes, speed: $speed, errorMessage: $errorMessage, currentFile: $currentFile, thumbPath: $thumbPath)';
}
}
/// @nodoc
abstract mixin class _$DownloadProgressCopyWith<$Res> implements $DownloadProgressCopyWith<$Res> {
factory _$DownloadProgressCopyWith(_DownloadProgress value, $Res Function(_DownloadProgress) _then) = __$DownloadProgressCopyWithImpl;
@override @useResult
$Res call({
String globalKey, DownloadStatus status, int progress, int downloadedBytes, int totalBytes, double speed, String? errorMessage, String? currentFile, String? thumbPath
});
}
/// @nodoc
class __$DownloadProgressCopyWithImpl<$Res>
implements _$DownloadProgressCopyWith<$Res> {
__$DownloadProgressCopyWithImpl(this._self, this._then);
final _DownloadProgress _self;
final $Res Function(_DownloadProgress) _then;
/// Create a copy of DownloadProgress
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? globalKey = null,Object? status = null,Object? progress = null,Object? downloadedBytes = null,Object? totalBytes = null,Object? speed = null,Object? errorMessage = freezed,Object? currentFile = freezed,Object? thumbPath = freezed,}) {
return _then(_DownloadProgress(
globalKey: null == globalKey ? _self.globalKey : globalKey // ignore: cast_nullable_to_non_nullable
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as DownloadStatus,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
as int,downloadedBytes: null == downloadedBytes ? _self.downloadedBytes : downloadedBytes // ignore: cast_nullable_to_non_nullable
as int,totalBytes: null == totalBytes ? _self.totalBytes : totalBytes // ignore: cast_nullable_to_non_nullable
as int,speed: null == speed ? _self.speed : speed // ignore: cast_nullable_to_non_nullable
as double,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,currentFile: freezed == currentFile ? _self.currentFile : currentFile // ignore: cast_nullable_to_non_nullable
as String?,thumbPath: freezed == thumbPath ? _self.thumbPath : thumbPath // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$DeletionProgress {
String get globalKey; String get itemTitle; int get currentItem; int get totalItems; String? get currentOperation;
/// Create a copy of DeletionProgress
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DeletionProgressCopyWith<DeletionProgress> get copyWith => _$DeletionProgressCopyWithImpl<DeletionProgress>(this as DeletionProgress, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeletionProgress&&(identical(other.globalKey, globalKey) || other.globalKey == globalKey)&&(identical(other.itemTitle, itemTitle) || other.itemTitle == itemTitle)&&(identical(other.currentItem, currentItem) || other.currentItem == currentItem)&&(identical(other.totalItems, totalItems) || other.totalItems == totalItems)&&(identical(other.currentOperation, currentOperation) || other.currentOperation == currentOperation));
}
@override
int get hashCode => Object.hash(runtimeType,globalKey,itemTitle,currentItem,totalItems,currentOperation);
@override
String toString() {
return 'DeletionProgress(globalKey: $globalKey, itemTitle: $itemTitle, currentItem: $currentItem, totalItems: $totalItems, currentOperation: $currentOperation)';
}
}
/// @nodoc
abstract mixin class $DeletionProgressCopyWith<$Res> {
factory $DeletionProgressCopyWith(DeletionProgress value, $Res Function(DeletionProgress) _then) = _$DeletionProgressCopyWithImpl;
@useResult
$Res call({
String globalKey, String itemTitle, int currentItem, int totalItems, String? currentOperation
});
}
/// @nodoc
class _$DeletionProgressCopyWithImpl<$Res>
implements $DeletionProgressCopyWith<$Res> {
_$DeletionProgressCopyWithImpl(this._self, this._then);
final DeletionProgress _self;
final $Res Function(DeletionProgress) _then;
/// Create a copy of DeletionProgress
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? globalKey = null,Object? itemTitle = null,Object? currentItem = null,Object? totalItems = null,Object? currentOperation = freezed,}) {
return _then(_self.copyWith(
globalKey: null == globalKey ? _self.globalKey : globalKey // ignore: cast_nullable_to_non_nullable
as String,itemTitle: null == itemTitle ? _self.itemTitle : itemTitle // ignore: cast_nullable_to_non_nullable
as String,currentItem: null == currentItem ? _self.currentItem : currentItem // ignore: cast_nullable_to_non_nullable
as int,totalItems: null == totalItems ? _self.totalItems : totalItems // ignore: cast_nullable_to_non_nullable
as int,currentOperation: freezed == currentOperation ? _self.currentOperation : currentOperation // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [DeletionProgress].
extension DeletionProgressPatterns on DeletionProgress {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DeletionProgress value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DeletionProgress() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DeletionProgress value) $default,){
final _that = this;
switch (_that) {
case _DeletionProgress():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DeletionProgress value)? $default,){
final _that = this;
switch (_that) {
case _DeletionProgress() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String globalKey, String itemTitle, int currentItem, int totalItems, String? currentOperation)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DeletionProgress() when $default != null:
return $default(_that.globalKey,_that.itemTitle,_that.currentItem,_that.totalItems,_that.currentOperation);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String globalKey, String itemTitle, int currentItem, int totalItems, String? currentOperation) $default,) {final _that = this;
switch (_that) {
case _DeletionProgress():
return $default(_that.globalKey,_that.itemTitle,_that.currentItem,_that.totalItems,_that.currentOperation);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String globalKey, String itemTitle, int currentItem, int totalItems, String? currentOperation)? $default,) {final _that = this;
switch (_that) {
case _DeletionProgress() when $default != null:
return $default(_that.globalKey,_that.itemTitle,_that.currentItem,_that.totalItems,_that.currentOperation);case _:
return null;
}
}
}
/// @nodoc
class _DeletionProgress extends DeletionProgress {
const _DeletionProgress({required this.globalKey, required this.itemTitle, required this.currentItem, required this.totalItems, this.currentOperation}): super._();
@override final String globalKey;
@override final String itemTitle;
@override final int currentItem;
@override final int totalItems;
@override final String? currentOperation;
/// Create a copy of DeletionProgress
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DeletionProgressCopyWith<_DeletionProgress> get copyWith => __$DeletionProgressCopyWithImpl<_DeletionProgress>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DeletionProgress&&(identical(other.globalKey, globalKey) || other.globalKey == globalKey)&&(identical(other.itemTitle, itemTitle) || other.itemTitle == itemTitle)&&(identical(other.currentItem, currentItem) || other.currentItem == currentItem)&&(identical(other.totalItems, totalItems) || other.totalItems == totalItems)&&(identical(other.currentOperation, currentOperation) || other.currentOperation == currentOperation));
}
@override
int get hashCode => Object.hash(runtimeType,globalKey,itemTitle,currentItem,totalItems,currentOperation);
@override
String toString() {
return 'DeletionProgress(globalKey: $globalKey, itemTitle: $itemTitle, currentItem: $currentItem, totalItems: $totalItems, currentOperation: $currentOperation)';
}
}
/// @nodoc
abstract mixin class _$DeletionProgressCopyWith<$Res> implements $DeletionProgressCopyWith<$Res> {
factory _$DeletionProgressCopyWith(_DeletionProgress value, $Res Function(_DeletionProgress) _then) = __$DeletionProgressCopyWithImpl;
@override @useResult
$Res call({
String globalKey, String itemTitle, int currentItem, int totalItems, String? currentOperation
});
}
/// @nodoc
class __$DeletionProgressCopyWithImpl<$Res>
implements _$DeletionProgressCopyWith<$Res> {
__$DeletionProgressCopyWithImpl(this._self, this._then);
final _DeletionProgress _self;
final $Res Function(_DeletionProgress) _then;
/// Create a copy of DeletionProgress
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? globalKey = null,Object? itemTitle = null,Object? currentItem = null,Object? totalItems = null,Object? currentOperation = freezed,}) {
return _then(_DeletionProgress(
globalKey: null == globalKey ? _self.globalKey : globalKey // ignore: cast_nullable_to_non_nullable
as String,itemTitle: null == itemTitle ? _self.itemTitle : itemTitle // ignore: cast_nullable_to_non_nullable
as String,currentItem: null == currentItem ? _self.currentItem : currentItem // ignore: cast_nullable_to_non_nullable
as int,totalItems: null == totalItems ? _self.totalItems : totalItems // ignore: cast_nullable_to_non_nullable
as int,currentOperation: freezed == currentOperation ? _self.currentOperation : currentOperation // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on
+1 -1
View File
@@ -27,5 +27,5 @@ Map<String, dynamic> _$PlexHomeToJson(PlexHome instance) => <String, dynamic>{
'guestUserUUID': instance.guestUserUUID, 'guestUserUUID': instance.guestUserUUID,
'guestEnabled': instance.guestEnabled, 'guestEnabled': instance.guestEnabled,
'subscription': instance.subscription, 'subscription': instance.subscription,
'users': instance.users, 'users': instance.users.map((e) => e.toJson()).toList(),
}; };
+26 -62
View File
@@ -1,3 +1,9 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
part 'shader_preset.freezed.dart';
part 'shader_preset.g.dart';
enum ShaderPresetType { none, nvscaler, artcnn, anime4k, custom } enum ShaderPresetType { none, nvscaler, artcnn, anime4k, custom }
/// ArtCNN real-time model sizes. /// ArtCNN real-time model sizes.
@@ -51,76 +57,34 @@ enum Anime4KMode {
modeCA, modeCA,
} }
class Anime4KConfig { @freezed
final Anime4KQuality quality; sealed class Anime4KConfig with _$Anime4KConfig {
final Anime4KMode mode; const factory Anime4KConfig({
@JsonKey(unknownEnumValue: Anime4KQuality.fast) required Anime4KQuality quality,
@JsonKey(unknownEnumValue: Anime4KMode.modeA) required Anime4KMode mode,
}) = _Anime4KConfig;
const Anime4KConfig({required this.quality, required this.mode}); factory Anime4KConfig.fromJson(Map<String, dynamic> json) => _$Anime4KConfigFromJson(json);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Anime4KConfig && other.quality == quality && other.mode == mode;
}
@override
int get hashCode => quality.hashCode ^ mode.hashCode;
Map<String, dynamic> toJson() => {'quality': quality.name, 'mode': mode.name};
factory Anime4KConfig.fromJson(Map<String, dynamic> json) {
return Anime4KConfig(
quality: Anime4KQuality.values.asNameMap()[json['quality']] ?? Anime4KQuality.fast,
mode: Anime4KMode.values.asNameMap()[json['mode']] ?? Anime4KMode.modeA,
);
}
} }
class ArtCNNConfig { @freezed
final ArtCNNModel model; sealed class ArtCNNConfig with _$ArtCNNConfig {
final ArtCNNVariant variant; const factory ArtCNNConfig({
@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) required ArtCNNModel model,
@JsonKey(unknownEnumValue: ArtCNNVariant.neutral) required ArtCNNVariant variant,
}) = _ArtCNNConfig;
const ArtCNNConfig({required this.model, required this.variant}); factory ArtCNNConfig.fromJson(Map<String, dynamic> json) => _$ArtCNNConfigFromJson(json);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ArtCNNConfig && other.model == model && other.variant == variant;
}
@override
int get hashCode => model.hashCode ^ variant.hashCode;
Map<String, dynamic> toJson() => {'model': model.name, 'variant': variant.name};
factory ArtCNNConfig.fromJson(Map<String, dynamic> json) {
return ArtCNNConfig(
model: ArtCNNModel.values.asNameMap()[json['model']] ?? ArtCNNModel.c4f16,
variant: ArtCNNVariant.values.asNameMap()[json['variant']] ?? ArtCNNVariant.neutral,
);
}
} }
class NVScalerConfig { @freezed
sealed class NVScalerConfig with _$NVScalerConfig {
const factory NVScalerConfig({
/// Whether to automatically skip NVScaler on HDR content /// Whether to automatically skip NVScaler on HDR content
final bool autoHdrSkip; @Default(true) bool autoHdrSkip,
}) = _NVScalerConfig;
const NVScalerConfig({this.autoHdrSkip = true}); factory NVScalerConfig.fromJson(Map<String, dynamic> json) => _$NVScalerConfigFromJson(json);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is NVScalerConfig && other.autoHdrSkip == autoHdrSkip;
}
@override
int get hashCode => autoHdrSkip.hashCode;
Map<String, dynamic> toJson() => {'autoHdrSkip': autoHdrSkip};
factory NVScalerConfig.fromJson(Map<String, dynamic> json) {
return NVScalerConfig(autoHdrSkip: json['autoHdrSkip'] as bool? ?? true);
}
} }
class ShaderPreset { class ShaderPreset {
+793
View File
@@ -0,0 +1,793 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shader_preset.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Anime4KConfig {
@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality get quality;@JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode get mode;
/// Create a copy of Anime4KConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$Anime4KConfigCopyWith<Anime4KConfig> get copyWith => _$Anime4KConfigCopyWithImpl<Anime4KConfig>(this as Anime4KConfig, _$identity);
/// Serializes this Anime4KConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Anime4KConfig&&(identical(other.quality, quality) || other.quality == quality)&&(identical(other.mode, mode) || other.mode == mode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,quality,mode);
@override
String toString() {
return 'Anime4KConfig(quality: $quality, mode: $mode)';
}
}
/// @nodoc
abstract mixin class $Anime4KConfigCopyWith<$Res> {
factory $Anime4KConfigCopyWith(Anime4KConfig value, $Res Function(Anime4KConfig) _then) = _$Anime4KConfigCopyWithImpl;
@useResult
$Res call({
@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality quality,@JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode mode
});
}
/// @nodoc
class _$Anime4KConfigCopyWithImpl<$Res>
implements $Anime4KConfigCopyWith<$Res> {
_$Anime4KConfigCopyWithImpl(this._self, this._then);
final Anime4KConfig _self;
final $Res Function(Anime4KConfig) _then;
/// Create a copy of Anime4KConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? quality = null,Object? mode = null,}) {
return _then(_self.copyWith(
quality: null == quality ? _self.quality : quality // ignore: cast_nullable_to_non_nullable
as Anime4KQuality,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable
as Anime4KMode,
));
}
}
/// Adds pattern-matching-related methods to [Anime4KConfig].
extension Anime4KConfigPatterns on Anime4KConfig {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Anime4KConfig value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Anime4KConfig() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Anime4KConfig value) $default,){
final _that = this;
switch (_that) {
case _Anime4KConfig():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Anime4KConfig value)? $default,){
final _that = this;
switch (_that) {
case _Anime4KConfig() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality quality, @JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode mode)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Anime4KConfig() when $default != null:
return $default(_that.quality,_that.mode);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality quality, @JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode mode) $default,) {final _that = this;
switch (_that) {
case _Anime4KConfig():
return $default(_that.quality,_that.mode);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality quality, @JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode mode)? $default,) {final _that = this;
switch (_that) {
case _Anime4KConfig() when $default != null:
return $default(_that.quality,_that.mode);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Anime4KConfig implements Anime4KConfig {
const _Anime4KConfig({@JsonKey(unknownEnumValue: Anime4KQuality.fast) required this.quality, @JsonKey(unknownEnumValue: Anime4KMode.modeA) required this.mode});
factory _Anime4KConfig.fromJson(Map<String, dynamic> json) => _$Anime4KConfigFromJson(json);
@override@JsonKey(unknownEnumValue: Anime4KQuality.fast) final Anime4KQuality quality;
@override@JsonKey(unknownEnumValue: Anime4KMode.modeA) final Anime4KMode mode;
/// Create a copy of Anime4KConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$Anime4KConfigCopyWith<_Anime4KConfig> get copyWith => __$Anime4KConfigCopyWithImpl<_Anime4KConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$Anime4KConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Anime4KConfig&&(identical(other.quality, quality) || other.quality == quality)&&(identical(other.mode, mode) || other.mode == mode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,quality,mode);
@override
String toString() {
return 'Anime4KConfig(quality: $quality, mode: $mode)';
}
}
/// @nodoc
abstract mixin class _$Anime4KConfigCopyWith<$Res> implements $Anime4KConfigCopyWith<$Res> {
factory _$Anime4KConfigCopyWith(_Anime4KConfig value, $Res Function(_Anime4KConfig) _then) = __$Anime4KConfigCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(unknownEnumValue: Anime4KQuality.fast) Anime4KQuality quality,@JsonKey(unknownEnumValue: Anime4KMode.modeA) Anime4KMode mode
});
}
/// @nodoc
class __$Anime4KConfigCopyWithImpl<$Res>
implements _$Anime4KConfigCopyWith<$Res> {
__$Anime4KConfigCopyWithImpl(this._self, this._then);
final _Anime4KConfig _self;
final $Res Function(_Anime4KConfig) _then;
/// Create a copy of Anime4KConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? quality = null,Object? mode = null,}) {
return _then(_Anime4KConfig(
quality: null == quality ? _self.quality : quality // ignore: cast_nullable_to_non_nullable
as Anime4KQuality,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable
as Anime4KMode,
));
}
}
/// @nodoc
mixin _$ArtCNNConfig {
@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel get model;@JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant get variant;
/// Create a copy of ArtCNNConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ArtCNNConfigCopyWith<ArtCNNConfig> get copyWith => _$ArtCNNConfigCopyWithImpl<ArtCNNConfig>(this as ArtCNNConfig, _$identity);
/// Serializes this ArtCNNConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ArtCNNConfig&&(identical(other.model, model) || other.model == model)&&(identical(other.variant, variant) || other.variant == variant));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,model,variant);
@override
String toString() {
return 'ArtCNNConfig(model: $model, variant: $variant)';
}
}
/// @nodoc
abstract mixin class $ArtCNNConfigCopyWith<$Res> {
factory $ArtCNNConfigCopyWith(ArtCNNConfig value, $Res Function(ArtCNNConfig) _then) = _$ArtCNNConfigCopyWithImpl;
@useResult
$Res call({
@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel model,@JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant variant
});
}
/// @nodoc
class _$ArtCNNConfigCopyWithImpl<$Res>
implements $ArtCNNConfigCopyWith<$Res> {
_$ArtCNNConfigCopyWithImpl(this._self, this._then);
final ArtCNNConfig _self;
final $Res Function(ArtCNNConfig) _then;
/// Create a copy of ArtCNNConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? model = null,Object? variant = null,}) {
return _then(_self.copyWith(
model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
as ArtCNNModel,variant: null == variant ? _self.variant : variant // ignore: cast_nullable_to_non_nullable
as ArtCNNVariant,
));
}
}
/// Adds pattern-matching-related methods to [ArtCNNConfig].
extension ArtCNNConfigPatterns on ArtCNNConfig {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ArtCNNConfig value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ArtCNNConfig() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ArtCNNConfig value) $default,){
final _that = this;
switch (_that) {
case _ArtCNNConfig():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ArtCNNConfig value)? $default,){
final _that = this;
switch (_that) {
case _ArtCNNConfig() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel model, @JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant variant)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ArtCNNConfig() when $default != null:
return $default(_that.model,_that.variant);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel model, @JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant variant) $default,) {final _that = this;
switch (_that) {
case _ArtCNNConfig():
return $default(_that.model,_that.variant);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel model, @JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant variant)? $default,) {final _that = this;
switch (_that) {
case _ArtCNNConfig() when $default != null:
return $default(_that.model,_that.variant);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ArtCNNConfig implements ArtCNNConfig {
const _ArtCNNConfig({@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) required this.model, @JsonKey(unknownEnumValue: ArtCNNVariant.neutral) required this.variant});
factory _ArtCNNConfig.fromJson(Map<String, dynamic> json) => _$ArtCNNConfigFromJson(json);
@override@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) final ArtCNNModel model;
@override@JsonKey(unknownEnumValue: ArtCNNVariant.neutral) final ArtCNNVariant variant;
/// Create a copy of ArtCNNConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ArtCNNConfigCopyWith<_ArtCNNConfig> get copyWith => __$ArtCNNConfigCopyWithImpl<_ArtCNNConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ArtCNNConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ArtCNNConfig&&(identical(other.model, model) || other.model == model)&&(identical(other.variant, variant) || other.variant == variant));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,model,variant);
@override
String toString() {
return 'ArtCNNConfig(model: $model, variant: $variant)';
}
}
/// @nodoc
abstract mixin class _$ArtCNNConfigCopyWith<$Res> implements $ArtCNNConfigCopyWith<$Res> {
factory _$ArtCNNConfigCopyWith(_ArtCNNConfig value, $Res Function(_ArtCNNConfig) _then) = __$ArtCNNConfigCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(unknownEnumValue: ArtCNNModel.c4f16) ArtCNNModel model,@JsonKey(unknownEnumValue: ArtCNNVariant.neutral) ArtCNNVariant variant
});
}
/// @nodoc
class __$ArtCNNConfigCopyWithImpl<$Res>
implements _$ArtCNNConfigCopyWith<$Res> {
__$ArtCNNConfigCopyWithImpl(this._self, this._then);
final _ArtCNNConfig _self;
final $Res Function(_ArtCNNConfig) _then;
/// Create a copy of ArtCNNConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? model = null,Object? variant = null,}) {
return _then(_ArtCNNConfig(
model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
as ArtCNNModel,variant: null == variant ? _self.variant : variant // ignore: cast_nullable_to_non_nullable
as ArtCNNVariant,
));
}
}
/// @nodoc
mixin _$NVScalerConfig {
/// Whether to automatically skip NVScaler on HDR content
bool get autoHdrSkip;
/// Create a copy of NVScalerConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NVScalerConfigCopyWith<NVScalerConfig> get copyWith => _$NVScalerConfigCopyWithImpl<NVScalerConfig>(this as NVScalerConfig, _$identity);
/// Serializes this NVScalerConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is NVScalerConfig&&(identical(other.autoHdrSkip, autoHdrSkip) || other.autoHdrSkip == autoHdrSkip));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,autoHdrSkip);
@override
String toString() {
return 'NVScalerConfig(autoHdrSkip: $autoHdrSkip)';
}
}
/// @nodoc
abstract mixin class $NVScalerConfigCopyWith<$Res> {
factory $NVScalerConfigCopyWith(NVScalerConfig value, $Res Function(NVScalerConfig) _then) = _$NVScalerConfigCopyWithImpl;
@useResult
$Res call({
bool autoHdrSkip
});
}
/// @nodoc
class _$NVScalerConfigCopyWithImpl<$Res>
implements $NVScalerConfigCopyWith<$Res> {
_$NVScalerConfigCopyWithImpl(this._self, this._then);
final NVScalerConfig _self;
final $Res Function(NVScalerConfig) _then;
/// Create a copy of NVScalerConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? autoHdrSkip = null,}) {
return _then(_self.copyWith(
autoHdrSkip: null == autoHdrSkip ? _self.autoHdrSkip : autoHdrSkip // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [NVScalerConfig].
extension NVScalerConfigPatterns on NVScalerConfig {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NVScalerConfig value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _NVScalerConfig() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NVScalerConfig value) $default,){
final _that = this;
switch (_that) {
case _NVScalerConfig():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NVScalerConfig value)? $default,){
final _that = this;
switch (_that) {
case _NVScalerConfig() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoHdrSkip)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _NVScalerConfig() when $default != null:
return $default(_that.autoHdrSkip);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoHdrSkip) $default,) {final _that = this;
switch (_that) {
case _NVScalerConfig():
return $default(_that.autoHdrSkip);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoHdrSkip)? $default,) {final _that = this;
switch (_that) {
case _NVScalerConfig() when $default != null:
return $default(_that.autoHdrSkip);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _NVScalerConfig implements NVScalerConfig {
const _NVScalerConfig({this.autoHdrSkip = true});
factory _NVScalerConfig.fromJson(Map<String, dynamic> json) => _$NVScalerConfigFromJson(json);
/// Whether to automatically skip NVScaler on HDR content
@override@JsonKey() final bool autoHdrSkip;
/// Create a copy of NVScalerConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NVScalerConfigCopyWith<_NVScalerConfig> get copyWith => __$NVScalerConfigCopyWithImpl<_NVScalerConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$NVScalerConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NVScalerConfig&&(identical(other.autoHdrSkip, autoHdrSkip) || other.autoHdrSkip == autoHdrSkip));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,autoHdrSkip);
@override
String toString() {
return 'NVScalerConfig(autoHdrSkip: $autoHdrSkip)';
}
}
/// @nodoc
abstract mixin class _$NVScalerConfigCopyWith<$Res> implements $NVScalerConfigCopyWith<$Res> {
factory _$NVScalerConfigCopyWith(_NVScalerConfig value, $Res Function(_NVScalerConfig) _then) = __$NVScalerConfigCopyWithImpl;
@override @useResult
$Res call({
bool autoHdrSkip
});
}
/// @nodoc
class __$NVScalerConfigCopyWithImpl<$Res>
implements _$NVScalerConfigCopyWith<$Res> {
__$NVScalerConfigCopyWithImpl(this._self, this._then);
final _NVScalerConfig _self;
final $Res Function(_NVScalerConfig) _then;
/// Create a copy of NVScalerConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? autoHdrSkip = null,}) {
return _then(_NVScalerConfig(
autoHdrSkip: null == autoHdrSkip ? _self.autoHdrSkip : autoHdrSkip // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on
+78
View File
@@ -0,0 +1,78 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shader_preset.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Anime4KConfig _$Anime4KConfigFromJson(Map<String, dynamic> json) =>
_Anime4KConfig(
quality: $enumDecode(
_$Anime4KQualityEnumMap,
json['quality'],
unknownValue: Anime4KQuality.fast,
),
mode: $enumDecode(
_$Anime4KModeEnumMap,
json['mode'],
unknownValue: Anime4KMode.modeA,
),
);
Map<String, dynamic> _$Anime4KConfigToJson(_Anime4KConfig instance) =>
<String, dynamic>{
'quality': _$Anime4KQualityEnumMap[instance.quality]!,
'mode': _$Anime4KModeEnumMap[instance.mode]!,
};
const _$Anime4KQualityEnumMap = {
Anime4KQuality.fast: 'fast',
Anime4KQuality.hq: 'hq',
};
const _$Anime4KModeEnumMap = {
Anime4KMode.modeA: 'modeA',
Anime4KMode.modeB: 'modeB',
Anime4KMode.modeC: 'modeC',
Anime4KMode.modeAA: 'modeAA',
Anime4KMode.modeBB: 'modeBB',
Anime4KMode.modeCA: 'modeCA',
};
_ArtCNNConfig _$ArtCNNConfigFromJson(Map<String, dynamic> json) =>
_ArtCNNConfig(
model: $enumDecode(
_$ArtCNNModelEnumMap,
json['model'],
unknownValue: ArtCNNModel.c4f16,
),
variant: $enumDecode(
_$ArtCNNVariantEnumMap,
json['variant'],
unknownValue: ArtCNNVariant.neutral,
),
);
Map<String, dynamic> _$ArtCNNConfigToJson(_ArtCNNConfig instance) =>
<String, dynamic>{
'model': _$ArtCNNModelEnumMap[instance.model]!,
'variant': _$ArtCNNVariantEnumMap[instance.variant]!,
};
const _$ArtCNNModelEnumMap = {
ArtCNNModel.c4f16: 'c4f16',
ArtCNNModel.c4f32: 'c4f32',
};
const _$ArtCNNVariantEnumMap = {
ArtCNNVariant.neutral: 'neutral',
ArtCNNVariant.denoise: 'denoise',
ArtCNNVariant.denoiseSharpen: 'denoiseSharpen',
};
_NVScalerConfig _$NVScalerConfigFromJson(Map<String, dynamic> json) =>
_NVScalerConfig(autoHdrSkip: json['autoHdrSkip'] as bool? ?? true);
Map<String, dynamic> _$NVScalerConfigToJson(_NVScalerConfig instance) =>
<String, dynamic>{'autoHdrSkip': instance.autoHdrSkip};
+21 -40
View File
@@ -1,52 +1,33 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'device_code.freezed.dart';
/// Result of requesting a device code from an RFC 8628 authorization server. /// Result of requesting a device code from an RFC 8628 authorization server.
/// ///
/// The user enters [userCode] at [verificationUrl]; the app polls the token /// The user enters [userCode] at [verificationUrl]; the app polls the token
/// endpoint with [deviceCode] every [interval] seconds until [expiresIn] /// endpoint with [deviceCode] every [interval] seconds until [expiresIn]
/// seconds elapse. /// seconds elapse.
class DeviceCode { @freezed
final String deviceCode; sealed class DeviceCode with _$DeviceCode {
final String userCode; const factory DeviceCode({
final String verificationUrl; required String deviceCode,
required String userCode,
required String verificationUrl,
required int expiresIn,
required int interval,
/// URL with the code pre-filled (e.g. `https://trakt.tv/activate/ABC12345`) /// URL with the code pre-filled (e.g. `https://trakt.tv/activate/ABC12345`)
/// when the provider supports it. Nullable — Simkl doesn't. /// when the provider supports it. Nullable — Simkl doesn't.
final String? verificationUrlComplete; String? verificationUrlComplete,
}) = _DeviceCode;
final int expiresIn;
final int interval;
const DeviceCode({
required this.deviceCode,
required this.userCode,
required this.verificationUrl,
required this.expiresIn,
required this.interval,
this.verificationUrlComplete,
});
} }
/// Discriminated event emitted by a device-code poll loop. /// Discriminated event emitted by a device-code poll loop.
sealed class DevicePollEvent { @freezed
const DevicePollEvent(); sealed class DevicePollEvent with _$DevicePollEvent {
} const factory DevicePollEvent.pending() = DevicePollPending;
const factory DevicePollEvent.slowDown() = DevicePollSlowDown;
class DevicePollPending extends DevicePollEvent { const factory DevicePollEvent.denied() = DevicePollDenied;
const DevicePollPending(); const factory DevicePollEvent.expired() = DevicePollExpired;
} const factory DevicePollEvent.success(Map<String, dynamic> tokenResponse) = DevicePollSuccess;
class DevicePollSlowDown extends DevicePollEvent {
const DevicePollSlowDown();
}
class DevicePollDenied extends DevicePollEvent {
const DevicePollDenied();
}
class DevicePollExpired extends DevicePollEvent {
const DevicePollExpired();
}
class DevicePollSuccess extends DevicePollEvent {
final Map<String, dynamic> tokenResponse;
const DevicePollSuccess(this.tokenResponse);
} }
@@ -0,0 +1,718 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'device_code.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DeviceCode {
String get deviceCode; String get userCode; String get verificationUrl; int get expiresIn; int get interval;/// URL with the code pre-filled (e.g. `https://trakt.tv/activate/ABC12345`)
/// when the provider supports it. Nullable — Simkl doesn't.
String? get verificationUrlComplete;
/// Create a copy of DeviceCode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DeviceCodeCopyWith<DeviceCode> get copyWith => _$DeviceCodeCopyWithImpl<DeviceCode>(this as DeviceCode, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeviceCode&&(identical(other.deviceCode, deviceCode) || other.deviceCode == deviceCode)&&(identical(other.userCode, userCode) || other.userCode == userCode)&&(identical(other.verificationUrl, verificationUrl) || other.verificationUrl == verificationUrl)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.interval, interval) || other.interval == interval)&&(identical(other.verificationUrlComplete, verificationUrlComplete) || other.verificationUrlComplete == verificationUrlComplete));
}
@override
int get hashCode => Object.hash(runtimeType,deviceCode,userCode,verificationUrl,expiresIn,interval,verificationUrlComplete);
@override
String toString() {
return 'DeviceCode(deviceCode: $deviceCode, userCode: $userCode, verificationUrl: $verificationUrl, expiresIn: $expiresIn, interval: $interval, verificationUrlComplete: $verificationUrlComplete)';
}
}
/// @nodoc
abstract mixin class $DeviceCodeCopyWith<$Res> {
factory $DeviceCodeCopyWith(DeviceCode value, $Res Function(DeviceCode) _then) = _$DeviceCodeCopyWithImpl;
@useResult
$Res call({
String deviceCode, String userCode, String verificationUrl, int expiresIn, int interval, String? verificationUrlComplete
});
}
/// @nodoc
class _$DeviceCodeCopyWithImpl<$Res>
implements $DeviceCodeCopyWith<$Res> {
_$DeviceCodeCopyWithImpl(this._self, this._then);
final DeviceCode _self;
final $Res Function(DeviceCode) _then;
/// Create a copy of DeviceCode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? deviceCode = null,Object? userCode = null,Object? verificationUrl = null,Object? expiresIn = null,Object? interval = null,Object? verificationUrlComplete = freezed,}) {
return _then(_self.copyWith(
deviceCode: null == deviceCode ? _self.deviceCode : deviceCode // ignore: cast_nullable_to_non_nullable
as String,userCode: null == userCode ? _self.userCode : userCode // ignore: cast_nullable_to_non_nullable
as String,verificationUrl: null == verificationUrl ? _self.verificationUrl : verificationUrl // ignore: cast_nullable_to_non_nullable
as String,expiresIn: null == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
as int,interval: null == interval ? _self.interval : interval // ignore: cast_nullable_to_non_nullable
as int,verificationUrlComplete: freezed == verificationUrlComplete ? _self.verificationUrlComplete : verificationUrlComplete // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [DeviceCode].
extension DeviceCodePatterns on DeviceCode {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DeviceCode value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DeviceCode() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DeviceCode value) $default,){
final _that = this;
switch (_that) {
case _DeviceCode():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DeviceCode value)? $default,){
final _that = this;
switch (_that) {
case _DeviceCode() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceCode, String userCode, String verificationUrl, int expiresIn, int interval, String? verificationUrlComplete)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DeviceCode() when $default != null:
return $default(_that.deviceCode,_that.userCode,_that.verificationUrl,_that.expiresIn,_that.interval,_that.verificationUrlComplete);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceCode, String userCode, String verificationUrl, int expiresIn, int interval, String? verificationUrlComplete) $default,) {final _that = this;
switch (_that) {
case _DeviceCode():
return $default(_that.deviceCode,_that.userCode,_that.verificationUrl,_that.expiresIn,_that.interval,_that.verificationUrlComplete);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceCode, String userCode, String verificationUrl, int expiresIn, int interval, String? verificationUrlComplete)? $default,) {final _that = this;
switch (_that) {
case _DeviceCode() when $default != null:
return $default(_that.deviceCode,_that.userCode,_that.verificationUrl,_that.expiresIn,_that.interval,_that.verificationUrlComplete);case _:
return null;
}
}
}
/// @nodoc
class _DeviceCode implements DeviceCode {
const _DeviceCode({required this.deviceCode, required this.userCode, required this.verificationUrl, required this.expiresIn, required this.interval, this.verificationUrlComplete});
@override final String deviceCode;
@override final String userCode;
@override final String verificationUrl;
@override final int expiresIn;
@override final int interval;
/// URL with the code pre-filled (e.g. `https://trakt.tv/activate/ABC12345`)
/// when the provider supports it. Nullable — Simkl doesn't.
@override final String? verificationUrlComplete;
/// Create a copy of DeviceCode
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DeviceCodeCopyWith<_DeviceCode> get copyWith => __$DeviceCodeCopyWithImpl<_DeviceCode>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DeviceCode&&(identical(other.deviceCode, deviceCode) || other.deviceCode == deviceCode)&&(identical(other.userCode, userCode) || other.userCode == userCode)&&(identical(other.verificationUrl, verificationUrl) || other.verificationUrl == verificationUrl)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.interval, interval) || other.interval == interval)&&(identical(other.verificationUrlComplete, verificationUrlComplete) || other.verificationUrlComplete == verificationUrlComplete));
}
@override
int get hashCode => Object.hash(runtimeType,deviceCode,userCode,verificationUrl,expiresIn,interval,verificationUrlComplete);
@override
String toString() {
return 'DeviceCode(deviceCode: $deviceCode, userCode: $userCode, verificationUrl: $verificationUrl, expiresIn: $expiresIn, interval: $interval, verificationUrlComplete: $verificationUrlComplete)';
}
}
/// @nodoc
abstract mixin class _$DeviceCodeCopyWith<$Res> implements $DeviceCodeCopyWith<$Res> {
factory _$DeviceCodeCopyWith(_DeviceCode value, $Res Function(_DeviceCode) _then) = __$DeviceCodeCopyWithImpl;
@override @useResult
$Res call({
String deviceCode, String userCode, String verificationUrl, int expiresIn, int interval, String? verificationUrlComplete
});
}
/// @nodoc
class __$DeviceCodeCopyWithImpl<$Res>
implements _$DeviceCodeCopyWith<$Res> {
__$DeviceCodeCopyWithImpl(this._self, this._then);
final _DeviceCode _self;
final $Res Function(_DeviceCode) _then;
/// Create a copy of DeviceCode
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? deviceCode = null,Object? userCode = null,Object? verificationUrl = null,Object? expiresIn = null,Object? interval = null,Object? verificationUrlComplete = freezed,}) {
return _then(_DeviceCode(
deviceCode: null == deviceCode ? _self.deviceCode : deviceCode // ignore: cast_nullable_to_non_nullable
as String,userCode: null == userCode ? _self.userCode : userCode // ignore: cast_nullable_to_non_nullable
as String,verificationUrl: null == verificationUrl ? _self.verificationUrl : verificationUrl // ignore: cast_nullable_to_non_nullable
as String,expiresIn: null == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
as int,interval: null == interval ? _self.interval : interval // ignore: cast_nullable_to_non_nullable
as int,verificationUrlComplete: freezed == verificationUrlComplete ? _self.verificationUrlComplete : verificationUrlComplete // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$DevicePollEvent {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollEvent);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'DevicePollEvent()';
}
}
/// @nodoc
class $DevicePollEventCopyWith<$Res> {
$DevicePollEventCopyWith(DevicePollEvent _, $Res Function(DevicePollEvent) __);
}
/// Adds pattern-matching-related methods to [DevicePollEvent].
extension DevicePollEventPatterns on DevicePollEvent {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( DevicePollPending value)? pending,TResult Function( DevicePollSlowDown value)? slowDown,TResult Function( DevicePollDenied value)? denied,TResult Function( DevicePollExpired value)? expired,TResult Function( DevicePollSuccess value)? success,required TResult orElse(),}){
final _that = this;
switch (_that) {
case DevicePollPending() when pending != null:
return pending(_that);case DevicePollSlowDown() when slowDown != null:
return slowDown(_that);case DevicePollDenied() when denied != null:
return denied(_that);case DevicePollExpired() when expired != null:
return expired(_that);case DevicePollSuccess() when success != null:
return success(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( DevicePollPending value) pending,required TResult Function( DevicePollSlowDown value) slowDown,required TResult Function( DevicePollDenied value) denied,required TResult Function( DevicePollExpired value) expired,required TResult Function( DevicePollSuccess value) success,}){
final _that = this;
switch (_that) {
case DevicePollPending():
return pending(_that);case DevicePollSlowDown():
return slowDown(_that);case DevicePollDenied():
return denied(_that);case DevicePollExpired():
return expired(_that);case DevicePollSuccess():
return success(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( DevicePollPending value)? pending,TResult? Function( DevicePollSlowDown value)? slowDown,TResult? Function( DevicePollDenied value)? denied,TResult? Function( DevicePollExpired value)? expired,TResult? Function( DevicePollSuccess value)? success,}){
final _that = this;
switch (_that) {
case DevicePollPending() when pending != null:
return pending(_that);case DevicePollSlowDown() when slowDown != null:
return slowDown(_that);case DevicePollDenied() when denied != null:
return denied(_that);case DevicePollExpired() when expired != null:
return expired(_that);case DevicePollSuccess() when success != null:
return success(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? pending,TResult Function()? slowDown,TResult Function()? denied,TResult Function()? expired,TResult Function( Map<String, dynamic> tokenResponse)? success,required TResult orElse(),}) {final _that = this;
switch (_that) {
case DevicePollPending() when pending != null:
return pending();case DevicePollSlowDown() when slowDown != null:
return slowDown();case DevicePollDenied() when denied != null:
return denied();case DevicePollExpired() when expired != null:
return expired();case DevicePollSuccess() when success != null:
return success(_that.tokenResponse);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() pending,required TResult Function() slowDown,required TResult Function() denied,required TResult Function() expired,required TResult Function( Map<String, dynamic> tokenResponse) success,}) {final _that = this;
switch (_that) {
case DevicePollPending():
return pending();case DevicePollSlowDown():
return slowDown();case DevicePollDenied():
return denied();case DevicePollExpired():
return expired();case DevicePollSuccess():
return success(_that.tokenResponse);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? pending,TResult? Function()? slowDown,TResult? Function()? denied,TResult? Function()? expired,TResult? Function( Map<String, dynamic> tokenResponse)? success,}) {final _that = this;
switch (_that) {
case DevicePollPending() when pending != null:
return pending();case DevicePollSlowDown() when slowDown != null:
return slowDown();case DevicePollDenied() when denied != null:
return denied();case DevicePollExpired() when expired != null:
return expired();case DevicePollSuccess() when success != null:
return success(_that.tokenResponse);case _:
return null;
}
}
}
/// @nodoc
class DevicePollPending implements DevicePollEvent {
const DevicePollPending();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollPending);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'DevicePollEvent.pending()';
}
}
/// @nodoc
class $DevicePollPendingCopyWith<$Res> implements $DevicePollEventCopyWith<$Res> {
$DevicePollPendingCopyWith(DevicePollPending _, $Res Function(DevicePollPending) __);
}
/// @nodoc
class _$DevicePollPendingCopyWithImpl<$Res>
implements $DevicePollPendingCopyWith<$Res> {
_$DevicePollPendingCopyWithImpl(this._self, this._then);
final DevicePollPending _self;
final $Res Function(DevicePollPending) _then;
}
/// @nodoc
class DevicePollSlowDown implements DevicePollEvent {
const DevicePollSlowDown();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollSlowDown);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'DevicePollEvent.slowDown()';
}
}
/// @nodoc
class $DevicePollSlowDownCopyWith<$Res> implements $DevicePollEventCopyWith<$Res> {
$DevicePollSlowDownCopyWith(DevicePollSlowDown _, $Res Function(DevicePollSlowDown) __);
}
/// @nodoc
class _$DevicePollSlowDownCopyWithImpl<$Res>
implements $DevicePollSlowDownCopyWith<$Res> {
_$DevicePollSlowDownCopyWithImpl(this._self, this._then);
final DevicePollSlowDown _self;
final $Res Function(DevicePollSlowDown) _then;
}
/// @nodoc
class DevicePollDenied implements DevicePollEvent {
const DevicePollDenied();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollDenied);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'DevicePollEvent.denied()';
}
}
/// @nodoc
class $DevicePollDeniedCopyWith<$Res> implements $DevicePollEventCopyWith<$Res> {
$DevicePollDeniedCopyWith(DevicePollDenied _, $Res Function(DevicePollDenied) __);
}
/// @nodoc
class _$DevicePollDeniedCopyWithImpl<$Res>
implements $DevicePollDeniedCopyWith<$Res> {
_$DevicePollDeniedCopyWithImpl(this._self, this._then);
final DevicePollDenied _self;
final $Res Function(DevicePollDenied) _then;
}
/// @nodoc
class DevicePollExpired implements DevicePollEvent {
const DevicePollExpired();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollExpired);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'DevicePollEvent.expired()';
}
}
/// @nodoc
class $DevicePollExpiredCopyWith<$Res> implements $DevicePollEventCopyWith<$Res> {
$DevicePollExpiredCopyWith(DevicePollExpired _, $Res Function(DevicePollExpired) __);
}
/// @nodoc
class _$DevicePollExpiredCopyWithImpl<$Res>
implements $DevicePollExpiredCopyWith<$Res> {
_$DevicePollExpiredCopyWithImpl(this._self, this._then);
final DevicePollExpired _self;
final $Res Function(DevicePollExpired) _then;
}
/// @nodoc
class DevicePollSuccess implements DevicePollEvent {
const DevicePollSuccess(final Map<String, dynamic> tokenResponse): _tokenResponse = tokenResponse;
final Map<String, dynamic> _tokenResponse;
Map<String, dynamic> get tokenResponse {
if (_tokenResponse is EqualUnmodifiableMapView) return _tokenResponse;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tokenResponse);
}
/// Create a copy of DevicePollEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DevicePollSuccessCopyWith<DevicePollSuccess> get copyWith => _$DevicePollSuccessCopyWithImpl<DevicePollSuccess>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DevicePollSuccess&&const DeepCollectionEquality().equals(other._tokenResponse, _tokenResponse));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_tokenResponse));
@override
String toString() {
return 'DevicePollEvent.success(tokenResponse: $tokenResponse)';
}
}
/// @nodoc
abstract mixin class $DevicePollSuccessCopyWith<$Res> implements $DevicePollEventCopyWith<$Res> {
factory $DevicePollSuccessCopyWith(DevicePollSuccess value, $Res Function(DevicePollSuccess) _then) = _$DevicePollSuccessCopyWithImpl;
@useResult
$Res call({
Map<String, dynamic> tokenResponse
});
}
/// @nodoc
class _$DevicePollSuccessCopyWithImpl<$Res>
implements $DevicePollSuccessCopyWith<$Res> {
_$DevicePollSuccessCopyWithImpl(this._self, this._then);
final DevicePollSuccess _self;
final $Res Function(DevicePollSuccess) _then;
/// Create a copy of DevicePollEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') $Res call({Object? tokenResponse = null,}) {
return _then(DevicePollSuccess(
null == tokenResponse ? _self._tokenResponse : tokenResponse // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
));
}
}
// dart format on
+27 -20
View File
@@ -1,16 +1,42 @@
// ignore_for_file: invalid_annotation_target
import 'package:json_annotation/json_annotation.dart';
import '../../utils/json_utils.dart';
part 'fribb_mapping_row.g.dart';
Object? _readTvdbSeason(Map json, String key) {
final season = json['season'];
return season is Map ? season['tvdb'] : null;
}
Object? _readTmdbSeason(Map json, String key) {
final season = json['season'];
return season is Map ? season['tmdb'] : null;
}
/// One row from `anime-list-mini.json` (Fribb/anime-lists). /// One row from `anime-list-mini.json` (Fribb/anime-lists).
@JsonSerializable(createToJson: false)
class FribbMappingRow { class FribbMappingRow {
@JsonKey(name: 'anilist_id', fromJson: flexibleInt)
final int? anilistId; final int? anilistId;
@JsonKey(name: 'imdb_id')
final String? imdbId; final String? imdbId;
@JsonKey(name: 'mal_id', fromJson: flexibleInt)
final int? malId; final int? malId;
@JsonKey(name: 'simkl_id', fromJson: flexibleInt)
final int? simklId; final int? simklId;
@JsonKey(name: 'themoviedb_id', fromJson: flexibleInt)
final int? tmdbId; final int? tmdbId;
@JsonKey(name: 'tvdb_id', fromJson: flexibleInt)
final int? tvdbId; final int? tvdbId;
/// Plex season number this mapping corresponds to. A single show-level /// Plex season number this mapping corresponds to. A single show-level
/// external ID can resolve to multiple rows for split-cour anime; the /// external ID can resolve to multiple rows for split-cour anime; the
/// resolver picks by matching the episode's `parentIndex` against these. /// resolver picks by matching the episode's `parentIndex` against these.
@JsonKey(readValue: _readTvdbSeason, fromJson: flexibleInt)
final int? tvdbSeason; final int? tvdbSeason;
@JsonKey(readValue: _readTmdbSeason, fromJson: flexibleInt)
final int? tmdbSeason; final int? tmdbSeason;
/// `TV` / `MOVIE` / `OVA` / `ONA` / `SPECIAL` / `UNKNOWN` / `null`. /// `TV` / `MOVIE` / `OVA` / `ONA` / `SPECIAL` / `UNKNOWN` / `null`.
@@ -30,24 +56,5 @@ class FribbMappingRow {
bool get isMovie => type == 'MOVIE'; bool get isMovie => type == 'MOVIE';
factory FribbMappingRow.fromJson(Map<String, dynamic> json) { factory FribbMappingRow.fromJson(Map<String, dynamic> json) => _$FribbMappingRowFromJson(json);
final season = json['season'];
int? tvdbSeason;
int? tmdbSeason;
if (season is Map) {
tvdbSeason = (season['tvdb'] as num?)?.toInt();
tmdbSeason = (season['tmdb'] as num?)?.toInt();
}
return FribbMappingRow(
anilistId: (json['anilist_id'] as num?)?.toInt(),
imdbId: json['imdb_id'] as String?,
malId: (json['mal_id'] as num?)?.toInt(),
simklId: (json['simkl_id'] as num?)?.toInt(),
tmdbId: (json['themoviedb_id'] as num?)?.toInt(),
tvdbId: (json['tvdb_id'] as num?)?.toInt(),
tvdbSeason: tvdbSeason,
tmdbSeason: tmdbSeason,
type: json['type'] as String?,
);
}
} }
@@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fribb_mapping_row.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FribbMappingRow _$FribbMappingRowFromJson(Map<String, dynamic> json) =>
FribbMappingRow(
anilistId: flexibleInt(json['anilist_id']),
imdbId: json['imdb_id'] as String?,
malId: flexibleInt(json['mal_id']),
simklId: flexibleInt(json['simkl_id']),
tmdbId: flexibleInt(json['themoviedb_id']),
tvdbId: flexibleInt(json['tvdb_id']),
tvdbSeason: flexibleInt(_readTvdbSeason(json, 'tvdbSeason')),
tmdbSeason: flexibleInt(_readTmdbSeason(json, 'tmdbSeason')),
type: json['type'] as String?,
);
+40 -56
View File
@@ -1,110 +1,94 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'trakt_ids.dart'; import 'trakt_ids.dart';
part 'trakt_scrobble_request.freezed.dart';
/// Body for `POST /scrobble/{start|pause|stop}` and `POST /sync/history`. /// Body for `POST /scrobble/{start|pause|stop}` and `POST /sync/history`.
/// ///
/// Either movie IDs or show IDs + season/episode are set, never both. /// Either movie IDs or show IDs + season/episode are set, never both.
/// [progress] is the percent (0100) for scrobble; ignored for `/sync/history`. /// [progress] is the percent (0100) for scrobble; ignored for `/sync/history`.
class TraktScrobbleRequest { @freezed
final TraktIds? _movieIds; sealed class TraktScrobbleRequest with _$TraktScrobbleRequest {
final TraktIds? _showIds; const TraktScrobbleRequest._();
final int? _season;
final int? _episode;
final double? progress;
const TraktScrobbleRequest._({TraktIds? movieIds, TraktIds? showIds, int? season, int? episode, this.progress})
: _movieIds = movieIds,
_showIds = showIds,
_season = season,
_episode = episode;
bool get isMovie => _movieIds != null;
bool get isEpisode => _showIds != null;
TraktScrobbleRequest copyWith({double? progress}) => TraktScrobbleRequest._(
movieIds: _movieIds,
showIds: _showIds,
season: _season,
episode: _episode,
progress: progress ?? this.progress,
);
/// Build a movie scrobble payload. /// Build a movie scrobble payload.
factory TraktScrobbleRequest.movie({required TraktIds ids, double? progress}) { const factory TraktScrobbleRequest.movie({required TraktIds ids, double? progress}) = TraktScrobbleMovieRequest;
return TraktScrobbleRequest._(movieIds: ids, progress: progress);
}
/// Build an episode scrobble payload using the show's external IDs plus /// Build an episode scrobble payload using the show's external IDs plus
/// season/episode index. Trakt prefers this shape over an episode-IDs-only /// season/episode index. Trakt prefers this shape over an episode-IDs-only
/// payload because it works even when the episode itself isn't in Trakt's /// payload because it works even when the episode itself isn't in Trakt's
/// catalog yet. /// catalog yet.
factory TraktScrobbleRequest.episode({ const factory TraktScrobbleRequest.episode({
required TraktIds showIds, required TraktIds showIds,
required int season, required int season,
required int number, required int number,
double? progress, double? progress,
}) { }) = TraktScrobbleEpisodeRequest;
return TraktScrobbleRequest._(showIds: showIds, season: season, episode: number, progress: progress);
}
Map<String, dynamic> toJson() => { bool get isMovie => this is TraktScrobbleMovieRequest;
if (_movieIds != null) 'movie': {'ids': _movieIds.toJson()}, bool get isEpisode => this is TraktScrobbleEpisodeRequest;
if (_showIds != null) 'show': {'ids': _showIds.toJson()},
if (_season != null && _episode != null) 'episode': {'season': _season, 'number': _episode}, Map<String, dynamic> toJson() => switch (this) {
TraktScrobbleMovieRequest(:final ids, :final progress) => {
'movie': {'ids': ids.toJson()},
'progress': ?progress, 'progress': ?progress,
},
TraktScrobbleEpisodeRequest(:final showIds, :final season, :final number, :final progress) => {
'show': {'ids': showIds.toJson()},
'episode': {'season': season, 'number': number},
'progress': ?progress,
},
}; };
/// Build a `POST /sync/history` body that adds this item to history. /// Build a `POST /sync/history` body that adds this item to history.
/// ///
/// Optional [watchedAt] (ISO-8601 UTC) lets the server attribute the play /// Optional [watchedAt] (ISO-8601 UTC) lets the server attribute the play
/// to a specific point in time; defaults to "now" on Trakt's side. /// to a specific point in time; defaults to "now" on Trakt's side.
Map<String, dynamic> toHistoryAddBody({String? watchedAt}) { Map<String, dynamic> toHistoryAddBody({String? watchedAt}) => switch (this) {
if (isMovie) { TraktScrobbleMovieRequest(:final ids) => {
return {
'movies': [ 'movies': [
{'watched_at': ?watchedAt, 'ids': _movieIds!.toJson()}, {'watched_at': ?watchedAt, 'ids': ids.toJson()},
], ],
}; },
} TraktScrobbleEpisodeRequest(:final showIds, :final season, :final number) => {
return {
'shows': [ 'shows': [
{ {
'ids': _showIds!.toJson(), 'ids': showIds.toJson(),
'seasons': [ 'seasons': [
{ {
'number': _season, 'number': season,
'episodes': [ 'episodes': [
{'watched_at': ?watchedAt, 'number': _episode}, {'watched_at': ?watchedAt, 'number': number},
], ],
}, },
], ],
}, },
], ],
},
}; };
}
/// Build a `POST /sync/history/remove` body that removes this item from history. /// Build a `POST /sync/history/remove` body that removes this item from history.
Map<String, dynamic> toHistoryRemoveBody() { Map<String, dynamic> toHistoryRemoveBody() => switch (this) {
if (isMovie) { TraktScrobbleMovieRequest(:final ids) => {
return {
'movies': [ 'movies': [
{'ids': _movieIds!.toJson()}, {'ids': ids.toJson()},
], ],
}; },
} TraktScrobbleEpisodeRequest(:final showIds, :final season, :final number) => {
return {
'shows': [ 'shows': [
{ {
'ids': _showIds!.toJson(), 'ids': showIds.toJson(),
'seasons': [ 'seasons': [
{ {
'number': _season, 'number': season,
'episodes': [ 'episodes': [
{'number': _episode}, {'number': number},
], ],
}, },
], ],
}, },
], ],
},
}; };
}
} }
@@ -0,0 +1,345 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'trakt_scrobble_request.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TraktScrobbleRequest {
double? get progress;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TraktScrobbleRequestCopyWith<TraktScrobbleRequest> get copyWith => _$TraktScrobbleRequestCopyWithImpl<TraktScrobbleRequest>(this as TraktScrobbleRequest, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktScrobbleRequest&&(identical(other.progress, progress) || other.progress == progress));
}
@override
int get hashCode => Object.hash(runtimeType,progress);
@override
String toString() {
return 'TraktScrobbleRequest(progress: $progress)';
}
}
/// @nodoc
abstract mixin class $TraktScrobbleRequestCopyWith<$Res> {
factory $TraktScrobbleRequestCopyWith(TraktScrobbleRequest value, $Res Function(TraktScrobbleRequest) _then) = _$TraktScrobbleRequestCopyWithImpl;
@useResult
$Res call({
double? progress
});
}
/// @nodoc
class _$TraktScrobbleRequestCopyWithImpl<$Res>
implements $TraktScrobbleRequestCopyWith<$Res> {
_$TraktScrobbleRequestCopyWithImpl(this._self, this._then);
final TraktScrobbleRequest _self;
final $Res Function(TraktScrobbleRequest) _then;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? progress = freezed,}) {
return _then(_self.copyWith(
progress: freezed == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
/// Adds pattern-matching-related methods to [TraktScrobbleRequest].
extension TraktScrobbleRequestPatterns on TraktScrobbleRequest {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( TraktScrobbleMovieRequest value)? movie,TResult Function( TraktScrobbleEpisodeRequest value)? episode,required TResult orElse(),}){
final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest() when movie != null:
return movie(_that);case TraktScrobbleEpisodeRequest() when episode != null:
return episode(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( TraktScrobbleMovieRequest value) movie,required TResult Function( TraktScrobbleEpisodeRequest value) episode,}){
final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest():
return movie(_that);case TraktScrobbleEpisodeRequest():
return episode(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( TraktScrobbleMovieRequest value)? movie,TResult? Function( TraktScrobbleEpisodeRequest value)? episode,}){
final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest() when movie != null:
return movie(_that);case TraktScrobbleEpisodeRequest() when episode != null:
return episode(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( TraktIds ids, double? progress)? movie,TResult Function( TraktIds showIds, int season, int number, double? progress)? episode,required TResult orElse(),}) {final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest() when movie != null:
return movie(_that.ids,_that.progress);case TraktScrobbleEpisodeRequest() when episode != null:
return episode(_that.showIds,_that.season,_that.number,_that.progress);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( TraktIds ids, double? progress) movie,required TResult Function( TraktIds showIds, int season, int number, double? progress) episode,}) {final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest():
return movie(_that.ids,_that.progress);case TraktScrobbleEpisodeRequest():
return episode(_that.showIds,_that.season,_that.number,_that.progress);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( TraktIds ids, double? progress)? movie,TResult? Function( TraktIds showIds, int season, int number, double? progress)? episode,}) {final _that = this;
switch (_that) {
case TraktScrobbleMovieRequest() when movie != null:
return movie(_that.ids,_that.progress);case TraktScrobbleEpisodeRequest() when episode != null:
return episode(_that.showIds,_that.season,_that.number,_that.progress);case _:
return null;
}
}
}
/// @nodoc
class TraktScrobbleMovieRequest extends TraktScrobbleRequest {
const TraktScrobbleMovieRequest({required this.ids, this.progress}): super._();
final TraktIds ids;
@override final double? progress;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TraktScrobbleMovieRequestCopyWith<TraktScrobbleMovieRequest> get copyWith => _$TraktScrobbleMovieRequestCopyWithImpl<TraktScrobbleMovieRequest>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktScrobbleMovieRequest&&(identical(other.ids, ids) || other.ids == ids)&&(identical(other.progress, progress) || other.progress == progress));
}
@override
int get hashCode => Object.hash(runtimeType,ids,progress);
@override
String toString() {
return 'TraktScrobbleRequest.movie(ids: $ids, progress: $progress)';
}
}
/// @nodoc
abstract mixin class $TraktScrobbleMovieRequestCopyWith<$Res> implements $TraktScrobbleRequestCopyWith<$Res> {
factory $TraktScrobbleMovieRequestCopyWith(TraktScrobbleMovieRequest value, $Res Function(TraktScrobbleMovieRequest) _then) = _$TraktScrobbleMovieRequestCopyWithImpl;
@override @useResult
$Res call({
TraktIds ids, double? progress
});
}
/// @nodoc
class _$TraktScrobbleMovieRequestCopyWithImpl<$Res>
implements $TraktScrobbleMovieRequestCopyWith<$Res> {
_$TraktScrobbleMovieRequestCopyWithImpl(this._self, this._then);
final TraktScrobbleMovieRequest _self;
final $Res Function(TraktScrobbleMovieRequest) _then;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? ids = null,Object? progress = freezed,}) {
return _then(TraktScrobbleMovieRequest(
ids: null == ids ? _self.ids : ids // ignore: cast_nullable_to_non_nullable
as TraktIds,progress: freezed == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
/// @nodoc
class TraktScrobbleEpisodeRequest extends TraktScrobbleRequest {
const TraktScrobbleEpisodeRequest({required this.showIds, required this.season, required this.number, this.progress}): super._();
final TraktIds showIds;
final int season;
final int number;
@override final double? progress;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TraktScrobbleEpisodeRequestCopyWith<TraktScrobbleEpisodeRequest> get copyWith => _$TraktScrobbleEpisodeRequestCopyWithImpl<TraktScrobbleEpisodeRequest>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TraktScrobbleEpisodeRequest&&(identical(other.showIds, showIds) || other.showIds == showIds)&&(identical(other.season, season) || other.season == season)&&(identical(other.number, number) || other.number == number)&&(identical(other.progress, progress) || other.progress == progress));
}
@override
int get hashCode => Object.hash(runtimeType,showIds,season,number,progress);
@override
String toString() {
return 'TraktScrobbleRequest.episode(showIds: $showIds, season: $season, number: $number, progress: $progress)';
}
}
/// @nodoc
abstract mixin class $TraktScrobbleEpisodeRequestCopyWith<$Res> implements $TraktScrobbleRequestCopyWith<$Res> {
factory $TraktScrobbleEpisodeRequestCopyWith(TraktScrobbleEpisodeRequest value, $Res Function(TraktScrobbleEpisodeRequest) _then) = _$TraktScrobbleEpisodeRequestCopyWithImpl;
@override @useResult
$Res call({
TraktIds showIds, int season, int number, double? progress
});
}
/// @nodoc
class _$TraktScrobbleEpisodeRequestCopyWithImpl<$Res>
implements $TraktScrobbleEpisodeRequestCopyWith<$Res> {
_$TraktScrobbleEpisodeRequestCopyWithImpl(this._self, this._then);
final TraktScrobbleEpisodeRequest _self;
final $Res Function(TraktScrobbleEpisodeRequest) _then;
/// Create a copy of TraktScrobbleRequest
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? showIds = null,Object? season = null,Object? number = null,Object? progress = freezed,}) {
return _then(TraktScrobbleEpisodeRequest(
showIds: null == showIds ? _self.showIds : showIds // ignore: cast_nullable_to_non_nullable
as TraktIds,season: null == season ? _self.season : season // ignore: cast_nullable_to_non_nullable
as int,number: null == number ? _self.number : number // ignore: cast_nullable_to_non_nullable
as int,progress: freezed == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
// dart format on
+6 -1
View File
@@ -1,4 +1,9 @@
import 'package:json_annotation/json_annotation.dart';
part 'trakt_user.g.dart';
/// Minimal Trakt user info parsed from `GET /users/settings`. /// Minimal Trakt user info parsed from `GET /users/settings`.
@JsonSerializable(createToJson: false)
class TraktUser { class TraktUser {
final String username; final String username;
final String? name; final String? name;
@@ -10,6 +15,6 @@ class TraktUser {
if (user == null) { if (user == null) {
throw const FormatException('Trakt /users/settings response missing "user" field'); throw const FormatException('Trakt /users/settings response missing "user" field');
} }
return TraktUser(username: user['username'] as String, name: user['name'] as String?); return _$TraktUserFromJson(user);
} }
} }
+12
View File
@@ -0,0 +1,12 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'trakt_user.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TraktUser _$TraktUserFromJson(Map<String, dynamic> json) => TraktUser(
username: json['username'] as String,
name: json['name'] as String?,
);
+66 -142
View File
@@ -1,95 +1,77 @@
class BufferRange { // ignore_for_file: invalid_annotation_target
final Duration start; import 'package:freezed_annotation/freezed_annotation.dart';
final Duration end;
const BufferRange({required this.start, required this.end}); part 'models.freezed.dart';
@freezed
sealed class BufferRange with _$BufferRange {
const factory BufferRange({required Duration start, required Duration end}) = _BufferRange;
} }
/// [cause] is an optional machine-readable tag (e.g. `server-http-500`), /// [cause] is an optional machine-readable tag (e.g. `server-http-500`),
/// letting the UI branch without parsing [message]. /// letting the UI branch without parsing [message].
class PlayerError { @Freezed(toStringOverride: false)
sealed class PlayerError with _$PlayerError {
const PlayerError._();
const factory PlayerError(String message, {String? cause}) = _PlayerError;
/// Cause tag for a server-side HTTP 500 — shared-user bandwidth or /// Cause tag for a server-side HTTP 500 — shared-user bandwidth or
/// transcoding limit rejection set by the server owner. /// transcoding limit rejection set by the server owner.
static const String serverHttp500 = 'server-http-500'; static const String serverHttp500 = 'server-http-500';
final String message;
final String? cause;
const PlayerError(this.message, {this.cause});
@override @override
String toString() => message; String toString() => message;
} }
enum PlayerLogLevel { none, fatal, error, warn, info, verbose, debug, trace } enum PlayerLogLevel { none, fatal, error, warn, info, verbose, debug, trace }
class AudioTrack { @freezed
final String id; sealed class AudioTrack with _$AudioTrack {
final String? title; const AudioTrack._();
final String? language;
final String? codec;
final int? channels;
int? get channelsCount => channels;
final int? sampleRate;
final int? bitrate;
final bool isDefault;
final bool isForced;
const AudioTrack({ const factory AudioTrack({
required this.id, required String id,
this.title, String? title,
this.language, String? language,
this.codec, String? codec,
this.channels, int? channels,
this.sampleRate, int? sampleRate,
this.bitrate, int? bitrate,
this.isDefault = false, @Default(false) bool isDefault,
this.isForced = false, @Default(false) bool isForced,
}); }) = _AudioTrack;
static const auto = AudioTrack(id: 'auto', title: 'Auto'); static const auto = AudioTrack(id: 'auto', title: 'Auto');
static const off = AudioTrack(id: 'no', title: 'Off'); static const off = AudioTrack(id: 'no', title: 'Off');
int? get channelsCount => channels;
String get displayName { String get displayName {
if (title != null && title!.isNotEmpty) return title!; if (title != null && title!.isNotEmpty) return title!;
if (language != null && language!.isNotEmpty) return language!; if (language != null && language!.isNotEmpty) return language!;
return 'Track $id'; return 'Track $id';
} }
@override
String toString() => 'AudioTrack($id, $displayName)';
@override
bool operator ==(Object other) =>
identical(this, other) || other is AudioTrack && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
} }
class SubtitleTrack { @freezed
final String id; sealed class SubtitleTrack with _$SubtitleTrack {
final String? title; const SubtitleTrack._();
final String? language;
final String? codec;
final bool isDefault;
final bool isForced;
final bool isExternal;
final String? uri;
const SubtitleTrack({ const factory SubtitleTrack({
required this.id, required String id,
this.title, String? title,
this.language, String? language,
this.codec, String? codec,
this.isDefault = false, @Default(false) bool isDefault,
this.isForced = false, @Default(false) bool isForced,
this.isExternal = false, @Default(false) bool isExternal,
this.uri, String? uri,
}); }) = _SubtitleTrack;
factory SubtitleTrack.uri(String uri, {String? title, String? language}) { factory SubtitleTrack.uri(String uri, {String? title, String? language}) =>
return SubtitleTrack(id: 'external:$uri', title: title, language: language, isExternal: true, uri: uri); SubtitleTrack(id: 'external:$uri', title: title, language: language, isExternal: true, uri: uri);
}
static const auto = SubtitleTrack(id: 'auto', title: 'Auto'); static const auto = SubtitleTrack(id: 'auto', title: 'Auto');
@@ -101,103 +83,45 @@ class SubtitleTrack {
if (isExternal) return 'External'; if (isExternal) return 'External';
return 'Track $id'; return 'Track $id';
} }
@override
String toString() => 'SubtitleTrack($id, $displayName)';
@override
bool operator ==(Object other) =>
identical(this, other) || other is SubtitleTrack && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
} }
class Tracks { @Freezed(toStringOverride: false)
final List<AudioTrack> audio; sealed class Tracks with _$Tracks {
final List<SubtitleTrack> subtitle; const Tracks._();
const Tracks({this.audio = const [], this.subtitle = const []}); const factory Tracks({
@Default(<AudioTrack>[]) List<AudioTrack> audio,
Tracks copyWith({List<AudioTrack>? audio, List<SubtitleTrack>? subtitle}) { @Default(<SubtitleTrack>[]) List<SubtitleTrack> subtitle,
return Tracks(audio: audio ?? this.audio, subtitle: subtitle ?? this.subtitle); }) = _Tracks;
}
@override @override
String toString() => 'Tracks(audio: ${audio.length}, subtitle: ${subtitle.length})'; String toString() => 'Tracks(audio: ${audio.length}, subtitle: ${subtitle.length})';
} }
/// Sentinel value used to distinguish "not provided" from "explicitly set to null" in copyWith. @freezed
const _sentinel = Object(); sealed class TrackSelection with _$TrackSelection {
const factory TrackSelection({AudioTrack? audio, SubtitleTrack? subtitle, SubtitleTrack? secondarySubtitle}) =
class TrackSelection { _TrackSelection;
final AudioTrack? audio;
final SubtitleTrack? subtitle;
final SubtitleTrack? secondarySubtitle;
const TrackSelection({this.audio, this.subtitle, this.secondarySubtitle});
/// Creates a copy with the given fields replaced.
/// Use [secondarySubtitle] with explicit null to clear the secondary subtitle.
TrackSelection copyWith({AudioTrack? audio, SubtitleTrack? subtitle, Object? secondarySubtitle = _sentinel}) {
return TrackSelection(
audio: audio ?? this.audio,
subtitle: subtitle ?? this.subtitle,
secondarySubtitle: identical(secondarySubtitle, _sentinel)
? this.secondarySubtitle
: secondarySubtitle as SubtitleTrack?,
);
}
@override
String toString() => 'TrackSelection(audio: $audio, subtitle: $subtitle, secondarySubtitle: $secondarySubtitle)';
} }
class AudioDevice { @freezed
final String name; sealed class AudioDevice with _$AudioDevice {
final String description; const factory AudioDevice({required String name, @Default('') String description}) = _AudioDevice;
const AudioDevice({required this.name, this.description = ''});
static const auto = AudioDevice(name: 'auto', description: 'Auto'); static const auto = AudioDevice(name: 'auto', description: 'Auto');
@override
String toString() => 'AudioDevice($name, $description)';
@override
bool operator ==(Object other) =>
identical(this, other) || other is AudioDevice && runtimeType == other.runtimeType && name == other.name;
@override
int get hashCode => name.hashCode;
} }
class PlayerLog { @Freezed(toStringOverride: false)
final PlayerLogLevel level; sealed class PlayerLog with _$PlayerLog {
final String prefix; const PlayerLog._();
final String text;
const PlayerLog({required this.level, required this.prefix, required this.text}); const factory PlayerLog({required PlayerLogLevel level, required String prefix, required String text}) = _PlayerLog;
@override @override
String toString() => '[$prefix] ${level.name}: $text'; String toString() => '[$prefix] ${level.name}: $text';
} }
class Media { @freezed
final String uri; sealed class Media with _$Media {
final Map<String, String>? headers; const factory Media(String uri, {Map<String, String>? headers, Duration? start}) = _Media;
final Duration? start;
const Media(this.uri, {this.headers, this.start});
@override
String toString() => 'Media($uri)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Media && runtimeType == other.runtimeType && uri == other.uri && start == other.start;
@override
int get hashCode => uri.hashCode ^ start.hashCode;
} }
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -438,18 +438,18 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
selectedTrack = _state.tracks.audio.firstWhereOrNull((t) => t.id == id); selectedTrack = _state.tracks.audio.firstWhereOrNull((t) => t.id == id);
} }
if (selectedTrack == null) return;
_state = _state.copyWith(track: _state.track.copyWith(audio: selectedTrack)); _state = _state.copyWith(track: _state.track.copyWith(audio: selectedTrack));
trackController.add(_state.track); trackController.add(_state.track);
} }
void updateSelectedSubtitleTrack(dynamic trackId) { void updateSelectedSubtitleTrack(dynamic trackId) {
final id = trackId?.toString(); final id = trackId?.toString();
SubtitleTrack? selectedTrack; final selectedTrack = (id == null || id == 'no')
selectedTrack = (id == null || id == 'no')
? SubtitleTrack.off ? SubtitleTrack.off
: _state.tracks.subtitle.firstWhereOrNull((t) => t.id == id); : _state.tracks.subtitle.firstWhereOrNull((t) => t.id == id);
if (selectedTrack == null) return;
_state = _state.copyWith(track: _state.track.copyWith(subtitle: selectedTrack)); _state = _state.copyWith(track: _state.track.copyWith(subtitle: selectedTrack));
trackController.add(_state.track); trackController.add(_state.track);
} }
+99 -106
View File
@@ -1,80 +1,72 @@
import 'dart:convert'; import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../models/plex/plex_home_user.dart'; import '../models/plex/plex_home_user.dart';
part 'profile.freezed.dart';
/// Top-level profile — the user-facing identity in the app. /// Top-level profile — the user-facing identity in the app.
/// ///
/// Two kinds: /// Two kinds:
/// - [ProfileKind.local]: a Plezy-only profile created by the user. May have /// - [LocalProfile]: a Plezy-only profile created by the user. May have
/// an optional 4-digit PIN. /// an optional 4-digit PIN.
/// - [ProfileKind.plexHome]: auto-surfaced from a connected Plex account's /// - [PlexHomeProfile]: auto-surfaced from a connected Plex account's
/// Home users. PIN protection is handled server-side by Plex via the /// Home users. PIN protection is handled server-side by Plex via the
/// `/home/users/{uuid}/switch` flow — `pinHash` is unused. /// `/home/users/{uuid}/switch` flow — `pinHash` is unused.
/// ///
/// A profile owns 1+ connections via the `profile_connections` join table. /// A profile owns 1+ connections via the `profile_connections` join table.
/// The join row carries the per-profile user-level token used to talk to /// The join row carries the per-profile user-level token used to talk to
/// each connection. /// each connection.
class Profile { @freezed
final String id; sealed class Profile with _$Profile {
final ProfileKind kind; const Profile._();
final String displayName;
final String? avatarThumbUrl;
/// Hashed PIN if set — only meaningful for [ProfileKind.local]. The raw const factory Profile.local({
/// PIN is never persisted; see [computePinHash]. required String id,
final String? pinHash; required String displayName,
String? avatarThumbUrl,
/// For [ProfileKind.plexHome]: the parent Plex account's connection id. /// Hashed PIN if set. The raw PIN is never persisted; see [computePinHash].
/// `null` for local profiles. String? pinHash,
final String? parentConnectionId; @Default(0) int sortOrder,
required DateTime createdAt,
DateTime? lastUsedAt,
}) = LocalProfile;
/// For [ProfileKind.plexHome]: the Plex Home user UUID. Used by the const factory Profile.plexHome({
/// active-profile binder to call `/home/users/{uuid}/switch`. `null` for required String id,
/// local profiles. required String displayName,
final String? plexHomeUserUuid; String? avatarThumbUrl,
/// Plex Home flags — only meaningful for [ProfileKind.plexHome]. /// The parent Plex account's connection id.
final bool plexRestricted; String? parentConnectionId,
final bool plexAdmin;
/// The Plex Home user UUID. Used by the active-profile binder to call
/// `/home/users/{uuid}/switch`.
String? plexHomeUserUuid,
@Default(false) bool plexRestricted,
@Default(false) bool plexAdmin,
/// Plex's `protected` flag — true when the home user has a PIN that must /// Plex's `protected` flag — true when the home user has a PIN that must
/// be entered before `/home/users/{uuid}/switch` will succeed. /// be entered before `/home/users/{uuid}/switch` will succeed.
final bool plexProtected; @Default(false) bool plexProtected,
@Default(0) int sortOrder,
final int sortOrder; required DateTime createdAt,
final DateTime createdAt; DateTime? lastUsedAt,
final DateTime? lastUsedAt; }) = PlexHomeProfile;
Profile({
required this.id,
required this.kind,
required this.displayName,
this.avatarThumbUrl,
this.pinHash,
this.parentConnectionId,
this.plexHomeUserUuid,
this.plexRestricted = false,
this.plexAdmin = false,
this.plexProtected = false,
this.sortOrder = 0,
required this.createdAt,
this.lastUsedAt,
});
/// Construct an in-memory virtual `Profile` for a Plex Home user. These /// Construct an in-memory virtual `Profile` for a Plex Home user. These
/// are never persisted — Plex owns the Home user list, so the picker /// are never persisted — Plex owns the Home user list, so the picker
/// reads them live from [PlexHomeService] and merges them with the local /// reads them live from [PlexHomeService] and merges them with the local
/// rows from [ProfileRegistry]. /// rows from `ProfileRegistry`.
factory Profile.virtualPlexHome({ factory Profile.virtualPlexHome({
required String connectionId, required String connectionId,
required PlexHomeUser homeUser, required PlexHomeUser homeUser,
DateTime? lastUsedAt, DateTime? lastUsedAt,
}) { }) => Profile.plexHome(
return Profile(
id: plexHomeProfileId(accountConnectionId: connectionId, homeUserUuid: homeUser.uuid), id: plexHomeProfileId(accountConnectionId: connectionId, homeUserUuid: homeUser.uuid),
kind: ProfileKind.plexHome,
displayName: homeUser.displayName, displayName: homeUser.displayName,
avatarThumbUrl: homeUser.thumb.isNotEmpty ? homeUser.thumb : null, avatarThumbUrl: homeUser.thumb.isNotEmpty ? homeUser.thumb : null,
parentConnectionId: connectionId, parentConnectionId: connectionId,
@@ -86,63 +78,6 @@ class Profile {
createdAt: DateTime.fromMillisecondsSinceEpoch(0), createdAt: DateTime.fromMillisecondsSinceEpoch(0),
lastUsedAt: lastUsedAt, lastUsedAt: lastUsedAt,
); );
}
bool get isLocal => kind == ProfileKind.local;
bool get isPlexHome => kind == ProfileKind.plexHome;
/// True when entering this profile requires user-supplied PIN.
///
/// Locals: gated by their own [pinHash].
/// Plex Home: gated by Plex's own protected flag (`plexProtected`).
bool get isPinProtected => isLocal ? (pinHash != null && pinHash!.isNotEmpty) : plexProtected;
Profile copyWith({
String? id,
ProfileKind? kind,
String? displayName,
String? avatarThumbUrl,
bool clearAvatar = false,
String? pinHash,
bool clearPin = false,
String? parentConnectionId,
String? plexHomeUserUuid,
bool? plexRestricted,
bool? plexAdmin,
bool? plexProtected,
int? sortOrder,
DateTime? createdAt,
DateTime? lastUsedAt,
bool clearLastUsedAt = false,
}) {
return Profile(
id: id ?? this.id,
kind: kind ?? this.kind,
displayName: displayName ?? this.displayName,
avatarThumbUrl: clearAvatar ? null : (avatarThumbUrl ?? this.avatarThumbUrl),
pinHash: clearPin ? null : (pinHash ?? this.pinHash),
parentConnectionId: parentConnectionId ?? this.parentConnectionId,
plexHomeUserUuid: plexHomeUserUuid ?? this.plexHomeUserUuid,
plexRestricted: plexRestricted ?? this.plexRestricted,
plexAdmin: plexAdmin ?? this.plexAdmin,
plexProtected: plexProtected ?? this.plexProtected,
sortOrder: sortOrder ?? this.sortOrder,
createdAt: createdAt ?? this.createdAt,
lastUsedAt: clearLastUsedAt ? null : (lastUsedAt ?? this.lastUsedAt),
);
}
Map<String, Object?> toConfigJson() {
return switch (kind) {
ProfileKind.local => {'pinHash': pinHash},
ProfileKind.plexHome => {
'parentConnectionId': parentConnectionId,
'restricted': plexRestricted,
'admin': plexAdmin,
'protected': plexProtected,
},
};
}
factory Profile.fromRow({ factory Profile.fromRow({
required String id, required String id,
@@ -156,9 +91,8 @@ class Profile {
}) { }) {
final parsedKind = ProfileKind.fromId(kind); final parsedKind = ProfileKind.fromId(kind);
return switch (parsedKind) { return switch (parsedKind) {
ProfileKind.local => Profile( ProfileKind.local => Profile.local(
id: id, id: id,
kind: parsedKind,
displayName: displayName, displayName: displayName,
avatarThumbUrl: avatarThumbUrl, avatarThumbUrl: avatarThumbUrl,
pinHash: json['pinHash'] as String?, pinHash: json['pinHash'] as String?,
@@ -166,9 +100,8 @@ class Profile {
createdAt: createdAt, createdAt: createdAt,
lastUsedAt: lastUsedAt, lastUsedAt: lastUsedAt,
), ),
ProfileKind.plexHome => Profile( ProfileKind.plexHome => Profile.plexHome(
id: id, id: id,
kind: parsedKind,
displayName: displayName, displayName: displayName,
avatarThumbUrl: avatarThumbUrl, avatarThumbUrl: avatarThumbUrl,
parentConnectionId: json['parentConnectionId'] as String?, parentConnectionId: json['parentConnectionId'] as String?,
@@ -181,6 +114,66 @@ class Profile {
), ),
}; };
} }
bool get isLocal => this is LocalProfile;
bool get isPlexHome => this is PlexHomeProfile;
ProfileKind get kind => switch (this) {
LocalProfile() => ProfileKind.local,
PlexHomeProfile() => ProfileKind.plexHome,
};
/// True when entering this profile requires user-supplied PIN.
///
/// Locals: gated by their own [pinHash].
/// Plex Home: gated by Plex's own protected flag (`plexProtected`).
bool get isPinProtected => switch (this) {
LocalProfile(:final pinHash) => pinHash != null && pinHash.isNotEmpty,
PlexHomeProfile(:final plexProtected) => plexProtected,
};
/// Hashed PIN, only set for [LocalProfile]. Returns null for plexHome.
String? get pinHash => switch (this) {
LocalProfile(:final pinHash) => pinHash,
PlexHomeProfile() => null,
};
/// Parent Plex account connection id, only set for [PlexHomeProfile].
String? get parentConnectionId => switch (this) {
LocalProfile() => null,
PlexHomeProfile(:final parentConnectionId) => parentConnectionId,
};
/// Plex Home user UUID, only set for [PlexHomeProfile].
String? get plexHomeUserUuid => switch (this) {
LocalProfile() => null,
PlexHomeProfile(:final plexHomeUserUuid) => plexHomeUserUuid,
};
bool get plexRestricted => switch (this) {
LocalProfile() => false,
PlexHomeProfile(:final plexRestricted) => plexRestricted,
};
bool get plexAdmin => switch (this) {
LocalProfile() => false,
PlexHomeProfile(:final plexAdmin) => plexAdmin,
};
bool get plexProtected => switch (this) {
LocalProfile() => false,
PlexHomeProfile(:final plexProtected) => plexProtected,
};
Map<String, Object?> toConfigJson() => switch (this) {
LocalProfile(:final pinHash) => {'pinHash': pinHash},
PlexHomeProfile(:final parentConnectionId, :final plexRestricted, :final plexAdmin, :final plexProtected) => {
'parentConnectionId': parentConnectionId,
'restricted': plexRestricted,
'admin': plexAdmin,
'protected': plexProtected,
},
};
} }
enum ProfileKind { enum ProfileKind {
+380
View File
@@ -0,0 +1,380 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'profile.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Profile {
String get id; String get displayName; String? get avatarThumbUrl; int get sortOrder; DateTime get createdAt; DateTime? get lastUsedAt;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProfileCopyWith<Profile> get copyWith => _$ProfileCopyWithImpl<Profile>(this as Profile, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Profile&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.avatarThumbUrl, avatarThumbUrl) || other.avatarThumbUrl == avatarThumbUrl)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,displayName,avatarThumbUrl,sortOrder,createdAt,lastUsedAt);
@override
String toString() {
return 'Profile(id: $id, displayName: $displayName, avatarThumbUrl: $avatarThumbUrl, sortOrder: $sortOrder, createdAt: $createdAt, lastUsedAt: $lastUsedAt)';
}
}
/// @nodoc
abstract mixin class $ProfileCopyWith<$Res> {
factory $ProfileCopyWith(Profile value, $Res Function(Profile) _then) = _$ProfileCopyWithImpl;
@useResult
$Res call({
String id, String displayName, String? avatarThumbUrl, int sortOrder, DateTime createdAt, DateTime? lastUsedAt
});
}
/// @nodoc
class _$ProfileCopyWithImpl<$Res>
implements $ProfileCopyWith<$Res> {
_$ProfileCopyWithImpl(this._self, this._then);
final Profile _self;
final $Res Function(Profile) _then;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? displayName = null,Object? avatarThumbUrl = freezed,Object? sortOrder = null,Object? createdAt = null,Object? lastUsedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
as String,avatarThumbUrl: freezed == avatarThumbUrl ? _self.avatarThumbUrl : avatarThumbUrl // ignore: cast_nullable_to_non_nullable
as String?,sortOrder: null == sortOrder ? _self.sortOrder : sortOrder // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,lastUsedAt: freezed == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// Adds pattern-matching-related methods to [Profile].
extension ProfilePatterns on Profile {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( LocalProfile value)? local,TResult Function( PlexHomeProfile value)? plexHome,required TResult orElse(),}){
final _that = this;
switch (_that) {
case LocalProfile() when local != null:
return local(_that);case PlexHomeProfile() when plexHome != null:
return plexHome(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( LocalProfile value) local,required TResult Function( PlexHomeProfile value) plexHome,}){
final _that = this;
switch (_that) {
case LocalProfile():
return local(_that);case PlexHomeProfile():
return plexHome(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( LocalProfile value)? local,TResult? Function( PlexHomeProfile value)? plexHome,}){
final _that = this;
switch (_that) {
case LocalProfile() when local != null:
return local(_that);case PlexHomeProfile() when plexHome != null:
return plexHome(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String id, String displayName, String? avatarThumbUrl, String? pinHash, int sortOrder, DateTime createdAt, DateTime? lastUsedAt)? local,TResult Function( String id, String displayName, String? avatarThumbUrl, String? parentConnectionId, String? plexHomeUserUuid, bool plexRestricted, bool plexAdmin, bool plexProtected, int sortOrder, DateTime createdAt, DateTime? lastUsedAt)? plexHome,required TResult orElse(),}) {final _that = this;
switch (_that) {
case LocalProfile() when local != null:
return local(_that.id,_that.displayName,_that.avatarThumbUrl,_that.pinHash,_that.sortOrder,_that.createdAt,_that.lastUsedAt);case PlexHomeProfile() when plexHome != null:
return plexHome(_that.id,_that.displayName,_that.avatarThumbUrl,_that.parentConnectionId,_that.plexHomeUserUuid,_that.plexRestricted,_that.plexAdmin,_that.plexProtected,_that.sortOrder,_that.createdAt,_that.lastUsedAt);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String id, String displayName, String? avatarThumbUrl, String? pinHash, int sortOrder, DateTime createdAt, DateTime? lastUsedAt) local,required TResult Function( String id, String displayName, String? avatarThumbUrl, String? parentConnectionId, String? plexHomeUserUuid, bool plexRestricted, bool plexAdmin, bool plexProtected, int sortOrder, DateTime createdAt, DateTime? lastUsedAt) plexHome,}) {final _that = this;
switch (_that) {
case LocalProfile():
return local(_that.id,_that.displayName,_that.avatarThumbUrl,_that.pinHash,_that.sortOrder,_that.createdAt,_that.lastUsedAt);case PlexHomeProfile():
return plexHome(_that.id,_that.displayName,_that.avatarThumbUrl,_that.parentConnectionId,_that.plexHomeUserUuid,_that.plexRestricted,_that.plexAdmin,_that.plexProtected,_that.sortOrder,_that.createdAt,_that.lastUsedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String id, String displayName, String? avatarThumbUrl, String? pinHash, int sortOrder, DateTime createdAt, DateTime? lastUsedAt)? local,TResult? Function( String id, String displayName, String? avatarThumbUrl, String? parentConnectionId, String? plexHomeUserUuid, bool plexRestricted, bool plexAdmin, bool plexProtected, int sortOrder, DateTime createdAt, DateTime? lastUsedAt)? plexHome,}) {final _that = this;
switch (_that) {
case LocalProfile() when local != null:
return local(_that.id,_that.displayName,_that.avatarThumbUrl,_that.pinHash,_that.sortOrder,_that.createdAt,_that.lastUsedAt);case PlexHomeProfile() when plexHome != null:
return plexHome(_that.id,_that.displayName,_that.avatarThumbUrl,_that.parentConnectionId,_that.plexHomeUserUuid,_that.plexRestricted,_that.plexAdmin,_that.plexProtected,_that.sortOrder,_that.createdAt,_that.lastUsedAt);case _:
return null;
}
}
}
/// @nodoc
class LocalProfile extends Profile {
const LocalProfile({required this.id, required this.displayName, this.avatarThumbUrl, this.pinHash, this.sortOrder = 0, required this.createdAt, this.lastUsedAt}): super._();
@override final String id;
@override final String displayName;
@override final String? avatarThumbUrl;
/// Hashed PIN if set. The raw PIN is never persisted; see [computePinHash].
final String? pinHash;
@override@JsonKey() final int sortOrder;
@override final DateTime createdAt;
@override final DateTime? lastUsedAt;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LocalProfileCopyWith<LocalProfile> get copyWith => _$LocalProfileCopyWithImpl<LocalProfile>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocalProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.avatarThumbUrl, avatarThumbUrl) || other.avatarThumbUrl == avatarThumbUrl)&&(identical(other.pinHash, pinHash) || other.pinHash == pinHash)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,displayName,avatarThumbUrl,pinHash,sortOrder,createdAt,lastUsedAt);
@override
String toString() {
return 'Profile.local(id: $id, displayName: $displayName, avatarThumbUrl: $avatarThumbUrl, pinHash: $pinHash, sortOrder: $sortOrder, createdAt: $createdAt, lastUsedAt: $lastUsedAt)';
}
}
/// @nodoc
abstract mixin class $LocalProfileCopyWith<$Res> implements $ProfileCopyWith<$Res> {
factory $LocalProfileCopyWith(LocalProfile value, $Res Function(LocalProfile) _then) = _$LocalProfileCopyWithImpl;
@override @useResult
$Res call({
String id, String displayName, String? avatarThumbUrl, String? pinHash, int sortOrder, DateTime createdAt, DateTime? lastUsedAt
});
}
/// @nodoc
class _$LocalProfileCopyWithImpl<$Res>
implements $LocalProfileCopyWith<$Res> {
_$LocalProfileCopyWithImpl(this._self, this._then);
final LocalProfile _self;
final $Res Function(LocalProfile) _then;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? displayName = null,Object? avatarThumbUrl = freezed,Object? pinHash = freezed,Object? sortOrder = null,Object? createdAt = null,Object? lastUsedAt = freezed,}) {
return _then(LocalProfile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
as String,avatarThumbUrl: freezed == avatarThumbUrl ? _self.avatarThumbUrl : avatarThumbUrl // ignore: cast_nullable_to_non_nullable
as String?,pinHash: freezed == pinHash ? _self.pinHash : pinHash // ignore: cast_nullable_to_non_nullable
as String?,sortOrder: null == sortOrder ? _self.sortOrder : sortOrder // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,lastUsedAt: freezed == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// @nodoc
class PlexHomeProfile extends Profile {
const PlexHomeProfile({required this.id, required this.displayName, this.avatarThumbUrl, this.parentConnectionId, this.plexHomeUserUuid, this.plexRestricted = false, this.plexAdmin = false, this.plexProtected = false, this.sortOrder = 0, required this.createdAt, this.lastUsedAt}): super._();
@override final String id;
@override final String displayName;
@override final String? avatarThumbUrl;
/// The parent Plex account's connection id.
final String? parentConnectionId;
/// The Plex Home user UUID. Used by the active-profile binder to call
/// `/home/users/{uuid}/switch`.
final String? plexHomeUserUuid;
@JsonKey() final bool plexRestricted;
@JsonKey() final bool plexAdmin;
/// Plex's `protected` flag — true when the home user has a PIN that must
/// be entered before `/home/users/{uuid}/switch` will succeed.
@JsonKey() final bool plexProtected;
@override@JsonKey() final int sortOrder;
@override final DateTime createdAt;
@override final DateTime? lastUsedAt;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PlexHomeProfileCopyWith<PlexHomeProfile> get copyWith => _$PlexHomeProfileCopyWithImpl<PlexHomeProfile>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PlexHomeProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.avatarThumbUrl, avatarThumbUrl) || other.avatarThumbUrl == avatarThumbUrl)&&(identical(other.parentConnectionId, parentConnectionId) || other.parentConnectionId == parentConnectionId)&&(identical(other.plexHomeUserUuid, plexHomeUserUuid) || other.plexHomeUserUuid == plexHomeUserUuid)&&(identical(other.plexRestricted, plexRestricted) || other.plexRestricted == plexRestricted)&&(identical(other.plexAdmin, plexAdmin) || other.plexAdmin == plexAdmin)&&(identical(other.plexProtected, plexProtected) || other.plexProtected == plexProtected)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,displayName,avatarThumbUrl,parentConnectionId,plexHomeUserUuid,plexRestricted,plexAdmin,plexProtected,sortOrder,createdAt,lastUsedAt);
@override
String toString() {
return 'Profile.plexHome(id: $id, displayName: $displayName, avatarThumbUrl: $avatarThumbUrl, parentConnectionId: $parentConnectionId, plexHomeUserUuid: $plexHomeUserUuid, plexRestricted: $plexRestricted, plexAdmin: $plexAdmin, plexProtected: $plexProtected, sortOrder: $sortOrder, createdAt: $createdAt, lastUsedAt: $lastUsedAt)';
}
}
/// @nodoc
abstract mixin class $PlexHomeProfileCopyWith<$Res> implements $ProfileCopyWith<$Res> {
factory $PlexHomeProfileCopyWith(PlexHomeProfile value, $Res Function(PlexHomeProfile) _then) = _$PlexHomeProfileCopyWithImpl;
@override @useResult
$Res call({
String id, String displayName, String? avatarThumbUrl, String? parentConnectionId, String? plexHomeUserUuid, bool plexRestricted, bool plexAdmin, bool plexProtected, int sortOrder, DateTime createdAt, DateTime? lastUsedAt
});
}
/// @nodoc
class _$PlexHomeProfileCopyWithImpl<$Res>
implements $PlexHomeProfileCopyWith<$Res> {
_$PlexHomeProfileCopyWithImpl(this._self, this._then);
final PlexHomeProfile _self;
final $Res Function(PlexHomeProfile) _then;
/// Create a copy of Profile
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? displayName = null,Object? avatarThumbUrl = freezed,Object? parentConnectionId = freezed,Object? plexHomeUserUuid = freezed,Object? plexRestricted = null,Object? plexAdmin = null,Object? plexProtected = null,Object? sortOrder = null,Object? createdAt = null,Object? lastUsedAt = freezed,}) {
return _then(PlexHomeProfile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
as String,avatarThumbUrl: freezed == avatarThumbUrl ? _self.avatarThumbUrl : avatarThumbUrl // ignore: cast_nullable_to_non_nullable
as String?,parentConnectionId: freezed == parentConnectionId ? _self.parentConnectionId : parentConnectionId // ignore: cast_nullable_to_non_nullable
as String?,plexHomeUserUuid: freezed == plexHomeUserUuid ? _self.plexHomeUserUuid : plexHomeUserUuid // ignore: cast_nullable_to_non_nullable
as String?,plexRestricted: null == plexRestricted ? _self.plexRestricted : plexRestricted // ignore: cast_nullable_to_non_nullable
as bool,plexAdmin: null == plexAdmin ? _self.plexAdmin : plexAdmin // ignore: cast_nullable_to_non_nullable
as bool,plexProtected: null == plexProtected ? _self.plexProtected : plexProtected // ignore: cast_nullable_to_non_nullable
as bool,sortOrder: null == sortOrder ? _self.sortOrder : sortOrder // ignore: cast_nullable_to_non_nullable
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,lastUsedAt: freezed == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
// dart format on
+16 -40
View File
@@ -1,3 +1,7 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'profile_connection.freezed.dart';
/// A binding between a [Profile] and a [Connection], carrying the /// A binding between a [Profile] and a [Connection], carrying the
/// per-profile user-level token used when the profile is active. /// per-profile user-level token used when the profile is active.
/// ///
@@ -9,47 +13,19 @@
/// ///
/// For Jellyfin: [userToken] mirrors the Connection's accessToken (one /// For Jellyfin: [userToken] mirrors the Connection's accessToken (one
/// user per connection); [userIdentifier] is the Jellyfin user id. /// user per connection); [userIdentifier] is the Jellyfin user id.
class ProfileConnection { @freezed
final String profileId; sealed class ProfileConnection with _$ProfileConnection {
final String connectionId; const ProfileConnection._();
final String? userToken;
final String userIdentifier;
final bool isDefault;
final DateTime? tokenAcquiredAt;
final DateTime? lastUsedAt;
const ProfileConnection({ const factory ProfileConnection({
required this.profileId, required String profileId,
required this.connectionId, required String connectionId,
this.userToken, String? userToken,
required this.userIdentifier, required String userIdentifier,
this.isDefault = false, @Default(false) bool isDefault,
this.tokenAcquiredAt, DateTime? tokenAcquiredAt,
this.lastUsedAt, DateTime? lastUsedAt,
}); }) = _ProfileConnection;
bool get hasToken => userToken != null && userToken!.isNotEmpty; bool get hasToken => userToken != null && userToken!.isNotEmpty;
ProfileConnection copyWith({
String? profileId,
String? connectionId,
String? userToken,
bool clearUserToken = false,
String? userIdentifier,
bool? isDefault,
DateTime? tokenAcquiredAt,
bool clearTokenAcquiredAt = false,
DateTime? lastUsedAt,
bool clearLastUsedAt = false,
}) {
return ProfileConnection(
profileId: profileId ?? this.profileId,
connectionId: connectionId ?? this.connectionId,
userToken: clearUserToken ? null : (userToken ?? this.userToken),
userIdentifier: userIdentifier ?? this.userIdentifier,
isDefault: isDefault ?? this.isDefault,
tokenAcquiredAt: clearTokenAcquiredAt ? null : (tokenAcquiredAt ?? this.tokenAcquiredAt),
lastUsedAt: clearLastUsedAt ? null : (lastUsedAt ?? this.lastUsedAt),
);
}
} }
@@ -0,0 +1,283 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'profile_connection.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ProfileConnection {
String get profileId; String get connectionId; String? get userToken; String get userIdentifier; bool get isDefault; DateTime? get tokenAcquiredAt; DateTime? get lastUsedAt;
/// Create a copy of ProfileConnection
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProfileConnectionCopyWith<ProfileConnection> get copyWith => _$ProfileConnectionCopyWithImpl<ProfileConnection>(this as ProfileConnection, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfileConnection&&(identical(other.profileId, profileId) || other.profileId == profileId)&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.userToken, userToken) || other.userToken == userToken)&&(identical(other.userIdentifier, userIdentifier) || other.userIdentifier == userIdentifier)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault)&&(identical(other.tokenAcquiredAt, tokenAcquiredAt) || other.tokenAcquiredAt == tokenAcquiredAt)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt));
}
@override
int get hashCode => Object.hash(runtimeType,profileId,connectionId,userToken,userIdentifier,isDefault,tokenAcquiredAt,lastUsedAt);
@override
String toString() {
return 'ProfileConnection(profileId: $profileId, connectionId: $connectionId, userToken: $userToken, userIdentifier: $userIdentifier, isDefault: $isDefault, tokenAcquiredAt: $tokenAcquiredAt, lastUsedAt: $lastUsedAt)';
}
}
/// @nodoc
abstract mixin class $ProfileConnectionCopyWith<$Res> {
factory $ProfileConnectionCopyWith(ProfileConnection value, $Res Function(ProfileConnection) _then) = _$ProfileConnectionCopyWithImpl;
@useResult
$Res call({
String profileId, String connectionId, String? userToken, String userIdentifier, bool isDefault, DateTime? tokenAcquiredAt, DateTime? lastUsedAt
});
}
/// @nodoc
class _$ProfileConnectionCopyWithImpl<$Res>
implements $ProfileConnectionCopyWith<$Res> {
_$ProfileConnectionCopyWithImpl(this._self, this._then);
final ProfileConnection _self;
final $Res Function(ProfileConnection) _then;
/// Create a copy of ProfileConnection
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? profileId = null,Object? connectionId = null,Object? userToken = freezed,Object? userIdentifier = null,Object? isDefault = null,Object? tokenAcquiredAt = freezed,Object? lastUsedAt = freezed,}) {
return _then(_self.copyWith(
profileId: null == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as String,connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable
as String,userToken: freezed == userToken ? _self.userToken : userToken // ignore: cast_nullable_to_non_nullable
as String?,userIdentifier: null == userIdentifier ? _self.userIdentifier : userIdentifier // ignore: cast_nullable_to_non_nullable
as String,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
as bool,tokenAcquiredAt: freezed == tokenAcquiredAt ? _self.tokenAcquiredAt : tokenAcquiredAt // ignore: cast_nullable_to_non_nullable
as DateTime?,lastUsedAt: freezed == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// Adds pattern-matching-related methods to [ProfileConnection].
extension ProfileConnectionPatterns on ProfileConnection {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProfileConnection value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ProfileConnection() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProfileConnection value) $default,){
final _that = this;
switch (_that) {
case _ProfileConnection():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProfileConnection value)? $default,){
final _that = this;
switch (_that) {
case _ProfileConnection() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String profileId, String connectionId, String? userToken, String userIdentifier, bool isDefault, DateTime? tokenAcquiredAt, DateTime? lastUsedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProfileConnection() when $default != null:
return $default(_that.profileId,_that.connectionId,_that.userToken,_that.userIdentifier,_that.isDefault,_that.tokenAcquiredAt,_that.lastUsedAt);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String profileId, String connectionId, String? userToken, String userIdentifier, bool isDefault, DateTime? tokenAcquiredAt, DateTime? lastUsedAt) $default,) {final _that = this;
switch (_that) {
case _ProfileConnection():
return $default(_that.profileId,_that.connectionId,_that.userToken,_that.userIdentifier,_that.isDefault,_that.tokenAcquiredAt,_that.lastUsedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String profileId, String connectionId, String? userToken, String userIdentifier, bool isDefault, DateTime? tokenAcquiredAt, DateTime? lastUsedAt)? $default,) {final _that = this;
switch (_that) {
case _ProfileConnection() when $default != null:
return $default(_that.profileId,_that.connectionId,_that.userToken,_that.userIdentifier,_that.isDefault,_that.tokenAcquiredAt,_that.lastUsedAt);case _:
return null;
}
}
}
/// @nodoc
class _ProfileConnection extends ProfileConnection {
const _ProfileConnection({required this.profileId, required this.connectionId, this.userToken, required this.userIdentifier, this.isDefault = false, this.tokenAcquiredAt, this.lastUsedAt}): super._();
@override final String profileId;
@override final String connectionId;
@override final String? userToken;
@override final String userIdentifier;
@override@JsonKey() final bool isDefault;
@override final DateTime? tokenAcquiredAt;
@override final DateTime? lastUsedAt;
/// Create a copy of ProfileConnection
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ProfileConnectionCopyWith<_ProfileConnection> get copyWith => __$ProfileConnectionCopyWithImpl<_ProfileConnection>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileConnection&&(identical(other.profileId, profileId) || other.profileId == profileId)&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.userToken, userToken) || other.userToken == userToken)&&(identical(other.userIdentifier, userIdentifier) || other.userIdentifier == userIdentifier)&&(identical(other.isDefault, isDefault) || other.isDefault == isDefault)&&(identical(other.tokenAcquiredAt, tokenAcquiredAt) || other.tokenAcquiredAt == tokenAcquiredAt)&&(identical(other.lastUsedAt, lastUsedAt) || other.lastUsedAt == lastUsedAt));
}
@override
int get hashCode => Object.hash(runtimeType,profileId,connectionId,userToken,userIdentifier,isDefault,tokenAcquiredAt,lastUsedAt);
@override
String toString() {
return 'ProfileConnection(profileId: $profileId, connectionId: $connectionId, userToken: $userToken, userIdentifier: $userIdentifier, isDefault: $isDefault, tokenAcquiredAt: $tokenAcquiredAt, lastUsedAt: $lastUsedAt)';
}
}
/// @nodoc
abstract mixin class _$ProfileConnectionCopyWith<$Res> implements $ProfileConnectionCopyWith<$Res> {
factory _$ProfileConnectionCopyWith(_ProfileConnection value, $Res Function(_ProfileConnection) _then) = __$ProfileConnectionCopyWithImpl;
@override @useResult
$Res call({
String profileId, String connectionId, String? userToken, String userIdentifier, bool isDefault, DateTime? tokenAcquiredAt, DateTime? lastUsedAt
});
}
/// @nodoc
class __$ProfileConnectionCopyWithImpl<$Res>
implements _$ProfileConnectionCopyWith<$Res> {
__$ProfileConnectionCopyWithImpl(this._self, this._then);
final _ProfileConnection _self;
final $Res Function(_ProfileConnection) _then;
/// Create a copy of ProfileConnection
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? profileId = null,Object? connectionId = null,Object? userToken = freezed,Object? userIdentifier = null,Object? isDefault = null,Object? tokenAcquiredAt = freezed,Object? lastUsedAt = freezed,}) {
return _then(_ProfileConnection(
profileId: null == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as String,connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable
as String,userToken: freezed == userToken ? _self.userToken : userToken // ignore: cast_nullable_to_non_nullable
as String?,userIdentifier: null == userIdentifier ? _self.userIdentifier : userIdentifier // ignore: cast_nullable_to_non_nullable
as String,isDefault: null == isDefault ? _self.isDefault : isDefault // ignore: cast_nullable_to_non_nullable
as bool,tokenAcquiredAt: freezed == tokenAcquiredAt ? _self.tokenAcquiredAt : tokenAcquiredAt // ignore: cast_nullable_to_non_nullable
as DateTime?,lastUsedAt: freezed == lastUsedAt ? _self.lastUsedAt : lastUsedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
// dart format on
+22 -9
View File
@@ -459,7 +459,11 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
final contexts = List<RemoteAuthContext>.unmodifiable(_authContexts); final contexts = List<RemoteAuthContext>.unmodifiable(_authContexts);
final result = await _peerService!.createSessionForContexts(_deviceName, _platform, contexts); final result = await _peerService!.createSessionForContexts(_deviceName, _platform, contexts);
_session = RemoteSession(role: RemoteSessionRole.host, status: RemoteSessionStatus.connected); _session = RemoteSession(
role: RemoteSessionRole.host,
status: RemoteSessionStatus.connected,
createdAt: DateTime.now(),
);
safeNotifyListeners(); safeNotifyListeners();
// Start LAN discovery broadcasting // Start LAN discovery broadcasting
@@ -480,6 +484,7 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
role: RemoteSessionRole.host, role: RemoteSessionRole.host,
status: RemoteSessionStatus.error, status: RemoteSessionStatus.error,
errorMessage: e.toString(), errorMessage: e.toString(),
createdAt: DateTime.now(),
); );
safeNotifyListeners(); safeNotifyListeners();
} }
@@ -538,7 +543,11 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
_peerService = CompanionRemotePeerService(); _peerService = CompanionRemotePeerService();
_setupPeerServiceListeners(); _setupPeerServiceListeners();
_session = RemoteSession(role: RemoteSessionRole.remote, status: RemoteSessionStatus.connecting); _session = RemoteSession(
role: RemoteSessionRole.remote,
status: RemoteSessionStatus.connecting,
createdAt: DateTime.now(),
);
safeNotifyListeners(); safeNotifyListeners();
try { try {
@@ -582,7 +591,11 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
_peerService = CompanionRemotePeerService(); _peerService = CompanionRemotePeerService();
_setupPeerServiceListeners(); _setupPeerServiceListeners();
_session = RemoteSession(role: RemoteSessionRole.remote, status: RemoteSessionStatus.connecting); _session = RemoteSession(
role: RemoteSessionRole.remote,
status: RemoteSessionStatus.connecting,
createdAt: DateTime.now(),
);
safeNotifyListeners(); safeNotifyListeners();
try { try {
@@ -629,13 +642,13 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
_deviceDisconnectedSubscription = _peerService!.onDeviceDisconnected.listen((_) { _deviceDisconnectedSubscription = _peerService!.onDeviceDisconnected.listen((_) {
appLogger.d('CompanionRemote: Device disconnected (intentional: $_intentionalDisconnect)'); appLogger.d('CompanionRemote: Device disconnected (intentional: $_intentionalDisconnect)');
if (_intentionalDisconnect) { if (_intentionalDisconnect) {
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true); _session = _session?.copyWith(status: RemoteSessionStatus.disconnected, connectedDevice: null);
safeNotifyListeners(); safeNotifyListeners();
} else if (isHost) { } else if (isHost) {
_session = _session?.copyWith( _session = _session?.copyWith(
status: RemoteSessionStatus.reconnecting, status: RemoteSessionStatus.reconnecting,
clearConnectedDevice: true, connectedDevice: null,
clearErrorMessage: true, errorMessage: null,
); );
safeNotifyListeners(); safeNotifyListeners();
appLogger.d('CompanionRemote: Host waiting for client to reconnect'); appLogger.d('CompanionRemote: Host waiting for client to reconnect');
@@ -668,7 +681,7 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
appLogger.d('CompanionRemote: Device info - name: $name, platform: $platform, role: $role'); appLogger.d('CompanionRemote: Device info - name: $name, platform: $platform, role: $role');
final device = RemoteDevice(id: id, name: name, platform: platform); final device = RemoteDevice(id: id, name: name, platform: platform, connectedAt: DateTime.now());
_session = _session?.copyWith(connectedDevice: device); _session = _session?.copyWith(connectedDevice: device);
safeNotifyListeners(); safeNotifyListeners();
@@ -756,7 +769,7 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
_lastAuthContextId = _peerService!.selectedAuthContextId ?? authContextId; _lastAuthContextId = _peerService!.selectedAuthContextId ?? authContextId;
_lastHostClientId = _peerService!.selectedHostClientId ?? _lastHostClientId; _lastHostClientId = _peerService!.selectedHostClientId ?? _lastHostClientId;
_session = _session?.copyWith(status: RemoteSessionStatus.connected, clearErrorMessage: true); _session = _session?.copyWith(status: RemoteSessionStatus.connected, errorMessage: null);
_reconnectAttempts = 0; _reconnectAttempts = 0;
safeNotifyListeners(); safeNotifyListeners();
appLogger.d('CompanionRemote: Reconnected successfully'); appLogger.d('CompanionRemote: Reconnected successfully');
@@ -777,7 +790,7 @@ class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin
void cancelReconnect() { void cancelReconnect() {
_reconnectTimer?.cancel(); _reconnectTimer?.cancel();
_reconnectAttempts = 0; _reconnectAttempts = 0;
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true); _session = _session?.copyWith(status: RemoteSessionStatus.disconnected, connectedDevice: null);
safeNotifyListeners(); safeNotifyListeners();
} }
@@ -64,9 +64,8 @@ class _AddLocalProfileScreenState extends State<AddLocalProfileScreen> with Cont
setState(() => _saving = true); setState(() => _saving = true);
final registry = context.read<ProfileRegistry>(); final registry = context.read<ProfileRegistry>();
final profile = Profile( final profile = Profile.local(
id: 'local-${const Uuid().v4()}', id: 'local-${const Uuid().v4()}',
kind: ProfileKind.local,
displayName: name, displayName: name,
pinHash: _pinHash, pinHash: _pinHash,
sortOrder: DateTime.now().millisecondsSinceEpoch, sortOrder: DateTime.now().millisecondsSinceEpoch,
@@ -87,14 +87,18 @@ class _ProfileDetailScreenState extends State<ProfileDetailScreen> with Controll
onMismatch: (ctx) => showErrorSnackBar(ctx, t.profiles.pinsDontMatch), onMismatch: (ctx) => showErrorSnackBar(ctx, t.profiles.pinsDontMatch),
); );
if (pin == null || !mounted) return; if (pin == null || !mounted) return;
final updated = _profile.copyWith(pinHash: computePinHash(pin)); final profile = _profile;
if (profile is! LocalProfile) return;
final updated = profile.copyWith(pinHash: computePinHash(pin));
await context.read<ProfileRegistry>().upsert(updated); await context.read<ProfileRegistry>().upsert(updated);
if (!mounted) return; if (!mounted) return;
setState(() => _profile = updated); setState(() => _profile = updated);
} }
Future<void> _clearPin() async { Future<void> _clearPin() async {
final updated = _profile.copyWith(clearPin: true); final profile = _profile;
if (profile is! LocalProfile) return;
final updated = profile.copyWith(pinHash: null);
await context.read<ProfileRegistry>().upsert(updated); await context.read<ProfileRegistry>().upsert(updated);
if (!mounted) return; if (!mounted) return;
setState(() => _profile = updated); setState(() => _profile = updated);
@@ -264,9 +264,8 @@ class _AddJellyfinScreenState extends State<AddJellyfinScreen> with AsyncFormSta
hasProfiles: activeProvider.profiles.isNotEmpty, hasProfiles: activeProvider.profiles.isNotEmpty,
)) { )) {
final now = DateTime.now(); final now = DateTime.now();
final profile = Profile( final profile = Profile.local(
id: 'local-${const Uuid().v4()}', id: 'local-${const Uuid().v4()}',
kind: ProfileKind.local,
displayName: connection.userName.isNotEmpty ? connection.userName : connection.serverName, displayName: connection.userName.isNotEmpty ? connection.userName : connection.serverName,
sortOrder: now.millisecondsSinceEpoch, sortOrder: now.millisecondsSinceEpoch,
createdAt: now, createdAt: now,
@@ -343,7 +343,12 @@ class CompanionRemotePeerService with KeepaliveMixin {
await _sendEncryptedToSocket(socket, jsonEncode({'type': 'authSuccess'})); await _sendEncryptedToSocket(socket, jsonEncode({'type': 'authSuccess'}));
// Notify connection // Notify connection
final device = RemoteDevice(id: 'remote-client', name: deviceName, platform: platform); final device = RemoteDevice(
id: 'remote-client',
name: deviceName,
platform: platform,
connectedAt: DateTime.now(),
);
_deviceConnectedController.add(device); _deviceConnectedController.add(device);
_connectionStateController.add(RemoteSessionStatus.connected); _connectionStateController.add(RemoteSessionStatus.connected);
@@ -516,7 +521,12 @@ class CompanionRemotePeerService with KeepaliveMixin {
completer.complete(); completer.complete();
} }
final device = RemoteDevice(id: 'host', name: 'Desktop', platform: 'desktop'); final device = RemoteDevice(
id: 'host',
name: 'Desktop',
platform: 'desktop',
connectedAt: DateTime.now(),
);
_deviceConnectedController.add(device); _deviceConnectedController.add(device);
_connectionStateController.add(RemoteSessionStatus.connected); _connectionStateController.add(RemoteSessionStatus.connected);
+21
View File
@@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
/// Maps an enum value to/from its `int` index. Useful for compact wire formats
/// (e.g. companion-remote commands) where the over-the-wire size matters and
/// new enum cases are always appended.
///
/// Out-of-range indices on the wire fall back to [_fallback] instead of
/// throwing — important for forward-compat with newer clients sending
/// commands the host doesn't yet understand.
class IndexedEnumConverter<T extends Enum> implements JsonConverter<T, int> {
const IndexedEnumConverter(this._values, this._fallback);
final List<T> _values;
final T _fallback;
@override
T fromJson(int json) => json >= 0 && json < _values.length ? _values[json] : _fallback;
@override
int toJson(T object) => object.index;
}
+28 -87
View File
@@ -1,98 +1,43 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'watch_session.freezed.dart';
enum SessionRole { host, guest } enum SessionRole { host, guest }
enum ControlMode { hostOnly, anyone } enum ControlMode { hostOnly, anyone }
enum SessionState { disconnected, connecting, connected, error } enum SessionState { disconnected, connecting, connected, error }
class Participant { @freezed
final String peerId; sealed class Participant with _$Participant {
final String displayName; const factory Participant({
final bool isHost; required String peerId,
final Duration lastKnownPosition; required String displayName,
final bool isBuffering; required bool isHost,
@Default(Duration.zero) Duration lastKnownPosition,
const Participant({ @Default(false) bool isBuffering,
required this.peerId, }) = _Participant;
required this.displayName,
required this.isHost,
this.lastKnownPosition = Duration.zero,
this.isBuffering = false,
});
Participant copyWith({
String? peerId,
String? displayName,
bool? isHost,
Duration? lastKnownPosition,
bool? isBuffering,
}) {
return Participant(
peerId: peerId ?? this.peerId,
displayName: displayName ?? this.displayName,
isHost: isHost ?? this.isHost,
lastKnownPosition: lastKnownPosition ?? this.lastKnownPosition,
isBuffering: isBuffering ?? this.isBuffering,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) || other is Participant && runtimeType == other.runtimeType && peerId == other.peerId;
@override
int get hashCode => peerId.hashCode;
} }
class WatchSession { @freezed
final String sessionId; sealed class WatchSession with _$WatchSession {
final SessionRole role; const WatchSession._();
final ControlMode controlMode;
final SessionState state;
final String? errorMessage;
final String? mediaRatingKey;
final String? mediaServerId;
final String? mediaTitle;
final String? hostPeerId;
const WatchSession({ const factory WatchSession({
required this.sessionId, required String sessionId,
required this.role, required SessionRole role,
required this.controlMode, required ControlMode controlMode,
required this.state, required SessionState state,
this.errorMessage,
this.mediaRatingKey,
this.mediaServerId,
this.mediaTitle,
this.hostPeerId,
});
bool get isHost => role == SessionRole.host;
bool get isConnected => state == SessionState.connected;
WatchSession copyWith({
String? sessionId,
SessionRole? role,
ControlMode? controlMode,
SessionState? state,
String? errorMessage, String? errorMessage,
String? mediaRatingKey, String? mediaRatingKey,
String? mediaServerId, String? mediaServerId,
String? mediaTitle, String? mediaTitle,
String? hostPeerId, String? hostPeerId,
}) { }) = _WatchSession;
return WatchSession(
sessionId: sessionId ?? this.sessionId, bool get isHost => role == SessionRole.host;
role: role ?? this.role,
controlMode: controlMode ?? this.controlMode, bool get isConnected => state == SessionState.connected;
state: state ?? this.state,
errorMessage: errorMessage ?? this.errorMessage,
mediaRatingKey: mediaRatingKey ?? this.mediaRatingKey,
mediaServerId: mediaServerId ?? this.mediaServerId,
mediaTitle: mediaTitle ?? this.mediaTitle,
hostPeerId: hostPeerId ?? this.hostPeerId,
);
}
/// Create a new session as host /// Create a new session as host
factory WatchSession.createAsHost({ factory WatchSession.createAsHost({
@@ -102,8 +47,7 @@ class WatchSession {
String? mediaRatingKey, String? mediaRatingKey,
String? mediaServerId, String? mediaServerId,
String? mediaTitle, String? mediaTitle,
}) { }) => WatchSession(
return WatchSession(
sessionId: sessionId, sessionId: sessionId,
role: SessionRole.host, role: SessionRole.host,
controlMode: controlMode, controlMode: controlMode,
@@ -113,15 +57,12 @@ class WatchSession {
mediaServerId: mediaServerId, mediaServerId: mediaServerId,
mediaTitle: mediaTitle, mediaTitle: mediaTitle,
); );
}
/// Create a session as guest (joining) /// Create a session as guest (joining)
factory WatchSession.joinAsGuest({required String sessionId}) { factory WatchSession.joinAsGuest({required String sessionId}) => WatchSession(
return WatchSession(
sessionId: sessionId, sessionId: sessionId,
role: SessionRole.guest, role: SessionRole.guest,
controlMode: ControlMode.hostOnly, // Will be updated when connected controlMode: ControlMode.hostOnly, // Will be updated when connected
state: SessionState.connecting, state: SessionState.connecting,
); );
}
} }
@@ -0,0 +1,552 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'watch_session.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Participant {
String get peerId; String get displayName; bool get isHost; Duration get lastKnownPosition; bool get isBuffering;
/// Create a copy of Participant
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ParticipantCopyWith<Participant> get copyWith => _$ParticipantCopyWithImpl<Participant>(this as Participant, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Participant&&(identical(other.peerId, peerId) || other.peerId == peerId)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.isHost, isHost) || other.isHost == isHost)&&(identical(other.lastKnownPosition, lastKnownPosition) || other.lastKnownPosition == lastKnownPosition)&&(identical(other.isBuffering, isBuffering) || other.isBuffering == isBuffering));
}
@override
int get hashCode => Object.hash(runtimeType,peerId,displayName,isHost,lastKnownPosition,isBuffering);
@override
String toString() {
return 'Participant(peerId: $peerId, displayName: $displayName, isHost: $isHost, lastKnownPosition: $lastKnownPosition, isBuffering: $isBuffering)';
}
}
/// @nodoc
abstract mixin class $ParticipantCopyWith<$Res> {
factory $ParticipantCopyWith(Participant value, $Res Function(Participant) _then) = _$ParticipantCopyWithImpl;
@useResult
$Res call({
String peerId, String displayName, bool isHost, Duration lastKnownPosition, bool isBuffering
});
}
/// @nodoc
class _$ParticipantCopyWithImpl<$Res>
implements $ParticipantCopyWith<$Res> {
_$ParticipantCopyWithImpl(this._self, this._then);
final Participant _self;
final $Res Function(Participant) _then;
/// Create a copy of Participant
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? peerId = null,Object? displayName = null,Object? isHost = null,Object? lastKnownPosition = null,Object? isBuffering = null,}) {
return _then(_self.copyWith(
peerId: null == peerId ? _self.peerId : peerId // ignore: cast_nullable_to_non_nullable
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
as String,isHost: null == isHost ? _self.isHost : isHost // ignore: cast_nullable_to_non_nullable
as bool,lastKnownPosition: null == lastKnownPosition ? _self.lastKnownPosition : lastKnownPosition // ignore: cast_nullable_to_non_nullable
as Duration,isBuffering: null == isBuffering ? _self.isBuffering : isBuffering // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [Participant].
extension ParticipantPatterns on Participant {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Participant value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Participant() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Participant value) $default,){
final _that = this;
switch (_that) {
case _Participant():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Participant value)? $default,){
final _that = this;
switch (_that) {
case _Participant() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String peerId, String displayName, bool isHost, Duration lastKnownPosition, bool isBuffering)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Participant() when $default != null:
return $default(_that.peerId,_that.displayName,_that.isHost,_that.lastKnownPosition,_that.isBuffering);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String peerId, String displayName, bool isHost, Duration lastKnownPosition, bool isBuffering) $default,) {final _that = this;
switch (_that) {
case _Participant():
return $default(_that.peerId,_that.displayName,_that.isHost,_that.lastKnownPosition,_that.isBuffering);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String peerId, String displayName, bool isHost, Duration lastKnownPosition, bool isBuffering)? $default,) {final _that = this;
switch (_that) {
case _Participant() when $default != null:
return $default(_that.peerId,_that.displayName,_that.isHost,_that.lastKnownPosition,_that.isBuffering);case _:
return null;
}
}
}
/// @nodoc
class _Participant implements Participant {
const _Participant({required this.peerId, required this.displayName, required this.isHost, this.lastKnownPosition = Duration.zero, this.isBuffering = false});
@override final String peerId;
@override final String displayName;
@override final bool isHost;
@override@JsonKey() final Duration lastKnownPosition;
@override@JsonKey() final bool isBuffering;
/// Create a copy of Participant
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ParticipantCopyWith<_Participant> get copyWith => __$ParticipantCopyWithImpl<_Participant>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Participant&&(identical(other.peerId, peerId) || other.peerId == peerId)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.isHost, isHost) || other.isHost == isHost)&&(identical(other.lastKnownPosition, lastKnownPosition) || other.lastKnownPosition == lastKnownPosition)&&(identical(other.isBuffering, isBuffering) || other.isBuffering == isBuffering));
}
@override
int get hashCode => Object.hash(runtimeType,peerId,displayName,isHost,lastKnownPosition,isBuffering);
@override
String toString() {
return 'Participant(peerId: $peerId, displayName: $displayName, isHost: $isHost, lastKnownPosition: $lastKnownPosition, isBuffering: $isBuffering)';
}
}
/// @nodoc
abstract mixin class _$ParticipantCopyWith<$Res> implements $ParticipantCopyWith<$Res> {
factory _$ParticipantCopyWith(_Participant value, $Res Function(_Participant) _then) = __$ParticipantCopyWithImpl;
@override @useResult
$Res call({
String peerId, String displayName, bool isHost, Duration lastKnownPosition, bool isBuffering
});
}
/// @nodoc
class __$ParticipantCopyWithImpl<$Res>
implements _$ParticipantCopyWith<$Res> {
__$ParticipantCopyWithImpl(this._self, this._then);
final _Participant _self;
final $Res Function(_Participant) _then;
/// Create a copy of Participant
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? peerId = null,Object? displayName = null,Object? isHost = null,Object? lastKnownPosition = null,Object? isBuffering = null,}) {
return _then(_Participant(
peerId: null == peerId ? _self.peerId : peerId // ignore: cast_nullable_to_non_nullable
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
as String,isHost: null == isHost ? _self.isHost : isHost // ignore: cast_nullable_to_non_nullable
as bool,lastKnownPosition: null == lastKnownPosition ? _self.lastKnownPosition : lastKnownPosition // ignore: cast_nullable_to_non_nullable
as Duration,isBuffering: null == isBuffering ? _self.isBuffering : isBuffering // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
mixin _$WatchSession {
String get sessionId; SessionRole get role; ControlMode get controlMode; SessionState get state; String? get errorMessage; String? get mediaRatingKey; String? get mediaServerId; String? get mediaTitle; String? get hostPeerId;
/// Create a copy of WatchSession
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WatchSessionCopyWith<WatchSession> get copyWith => _$WatchSessionCopyWithImpl<WatchSession>(this as WatchSession, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WatchSession&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.role, role) || other.role == role)&&(identical(other.controlMode, controlMode) || other.controlMode == controlMode)&&(identical(other.state, state) || other.state == state)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.mediaRatingKey, mediaRatingKey) || other.mediaRatingKey == mediaRatingKey)&&(identical(other.mediaServerId, mediaServerId) || other.mediaServerId == mediaServerId)&&(identical(other.mediaTitle, mediaTitle) || other.mediaTitle == mediaTitle)&&(identical(other.hostPeerId, hostPeerId) || other.hostPeerId == hostPeerId));
}
@override
int get hashCode => Object.hash(runtimeType,sessionId,role,controlMode,state,errorMessage,mediaRatingKey,mediaServerId,mediaTitle,hostPeerId);
@override
String toString() {
return 'WatchSession(sessionId: $sessionId, role: $role, controlMode: $controlMode, state: $state, errorMessage: $errorMessage, mediaRatingKey: $mediaRatingKey, mediaServerId: $mediaServerId, mediaTitle: $mediaTitle, hostPeerId: $hostPeerId)';
}
}
/// @nodoc
abstract mixin class $WatchSessionCopyWith<$Res> {
factory $WatchSessionCopyWith(WatchSession value, $Res Function(WatchSession) _then) = _$WatchSessionCopyWithImpl;
@useResult
$Res call({
String sessionId, SessionRole role, ControlMode controlMode, SessionState state, String? errorMessage, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, String? hostPeerId
});
}
/// @nodoc
class _$WatchSessionCopyWithImpl<$Res>
implements $WatchSessionCopyWith<$Res> {
_$WatchSessionCopyWithImpl(this._self, this._then);
final WatchSession _self;
final $Res Function(WatchSession) _then;
/// Create a copy of WatchSession
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? sessionId = null,Object? role = null,Object? controlMode = null,Object? state = null,Object? errorMessage = freezed,Object? mediaRatingKey = freezed,Object? mediaServerId = freezed,Object? mediaTitle = freezed,Object? hostPeerId = freezed,}) {
return _then(_self.copyWith(
sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
as SessionRole,controlMode: null == controlMode ? _self.controlMode : controlMode // ignore: cast_nullable_to_non_nullable
as ControlMode,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
as SessionState,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,mediaRatingKey: freezed == mediaRatingKey ? _self.mediaRatingKey : mediaRatingKey // ignore: cast_nullable_to_non_nullable
as String?,mediaServerId: freezed == mediaServerId ? _self.mediaServerId : mediaServerId // ignore: cast_nullable_to_non_nullable
as String?,mediaTitle: freezed == mediaTitle ? _self.mediaTitle : mediaTitle // ignore: cast_nullable_to_non_nullable
as String?,hostPeerId: freezed == hostPeerId ? _self.hostPeerId : hostPeerId // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [WatchSession].
extension WatchSessionPatterns on WatchSession {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _WatchSession value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _WatchSession() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _WatchSession value) $default,){
final _that = this;
switch (_that) {
case _WatchSession():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _WatchSession value)? $default,){
final _that = this;
switch (_that) {
case _WatchSession() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String sessionId, SessionRole role, ControlMode controlMode, SessionState state, String? errorMessage, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, String? hostPeerId)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _WatchSession() when $default != null:
return $default(_that.sessionId,_that.role,_that.controlMode,_that.state,_that.errorMessage,_that.mediaRatingKey,_that.mediaServerId,_that.mediaTitle,_that.hostPeerId);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String sessionId, SessionRole role, ControlMode controlMode, SessionState state, String? errorMessage, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, String? hostPeerId) $default,) {final _that = this;
switch (_that) {
case _WatchSession():
return $default(_that.sessionId,_that.role,_that.controlMode,_that.state,_that.errorMessage,_that.mediaRatingKey,_that.mediaServerId,_that.mediaTitle,_that.hostPeerId);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String sessionId, SessionRole role, ControlMode controlMode, SessionState state, String? errorMessage, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, String? hostPeerId)? $default,) {final _that = this;
switch (_that) {
case _WatchSession() when $default != null:
return $default(_that.sessionId,_that.role,_that.controlMode,_that.state,_that.errorMessage,_that.mediaRatingKey,_that.mediaServerId,_that.mediaTitle,_that.hostPeerId);case _:
return null;
}
}
}
/// @nodoc
class _WatchSession extends WatchSession {
const _WatchSession({required this.sessionId, required this.role, required this.controlMode, required this.state, this.errorMessage, this.mediaRatingKey, this.mediaServerId, this.mediaTitle, this.hostPeerId}): super._();
@override final String sessionId;
@override final SessionRole role;
@override final ControlMode controlMode;
@override final SessionState state;
@override final String? errorMessage;
@override final String? mediaRatingKey;
@override final String? mediaServerId;
@override final String? mediaTitle;
@override final String? hostPeerId;
/// Create a copy of WatchSession
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WatchSessionCopyWith<_WatchSession> get copyWith => __$WatchSessionCopyWithImpl<_WatchSession>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WatchSession&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.role, role) || other.role == role)&&(identical(other.controlMode, controlMode) || other.controlMode == controlMode)&&(identical(other.state, state) || other.state == state)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.mediaRatingKey, mediaRatingKey) || other.mediaRatingKey == mediaRatingKey)&&(identical(other.mediaServerId, mediaServerId) || other.mediaServerId == mediaServerId)&&(identical(other.mediaTitle, mediaTitle) || other.mediaTitle == mediaTitle)&&(identical(other.hostPeerId, hostPeerId) || other.hostPeerId == hostPeerId));
}
@override
int get hashCode => Object.hash(runtimeType,sessionId,role,controlMode,state,errorMessage,mediaRatingKey,mediaServerId,mediaTitle,hostPeerId);
@override
String toString() {
return 'WatchSession(sessionId: $sessionId, role: $role, controlMode: $controlMode, state: $state, errorMessage: $errorMessage, mediaRatingKey: $mediaRatingKey, mediaServerId: $mediaServerId, mediaTitle: $mediaTitle, hostPeerId: $hostPeerId)';
}
}
/// @nodoc
abstract mixin class _$WatchSessionCopyWith<$Res> implements $WatchSessionCopyWith<$Res> {
factory _$WatchSessionCopyWith(_WatchSession value, $Res Function(_WatchSession) _then) = __$WatchSessionCopyWithImpl;
@override @useResult
$Res call({
String sessionId, SessionRole role, ControlMode controlMode, SessionState state, String? errorMessage, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, String? hostPeerId
});
}
/// @nodoc
class __$WatchSessionCopyWithImpl<$Res>
implements _$WatchSessionCopyWith<$Res> {
__$WatchSessionCopyWithImpl(this._self, this._then);
final _WatchSession _self;
final $Res Function(_WatchSession) _then;
/// Create a copy of WatchSession
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? sessionId = null,Object? role = null,Object? controlMode = null,Object? state = null,Object? errorMessage = freezed,Object? mediaRatingKey = freezed,Object? mediaServerId = freezed,Object? mediaTitle = freezed,Object? hostPeerId = freezed,}) {
return _then(_WatchSession(
sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
as SessionRole,controlMode: null == controlMode ? _self.controlMode : controlMode // ignore: cast_nullable_to_non_nullable
as ControlMode,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
as SessionState,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,mediaRatingKey: freezed == mediaRatingKey ? _self.mediaRatingKey : mediaRatingKey // ignore: cast_nullable_to_non_nullable
as String?,mediaServerId: freezed == mediaServerId ? _self.mediaServerId : mediaServerId // ignore: cast_nullable_to_non_nullable
as String?,mediaTitle: freezed == mediaTitle ? _self.mediaTitle : mediaTitle // ignore: cast_nullable_to_non_nullable
as String?,hostPeerId: freezed == hostPeerId ? _self.hostPeerId : hostPeerId // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on
@@ -544,7 +544,7 @@ class WatchTogetherProvider with ChangeNotifier {
if (message.peerId != null && message.position != null) { if (message.peerId != null && message.position != null) {
final index = _participants.indexWhere((p) => p.peerId == message.peerId); final index = _participants.indexWhere((p) => p.peerId == message.peerId);
if (index >= 0) { if (index >= 0) {
_participants[index] = _participants[index].copyWith(lastKnownPosition: message.position); _participants[index] = _participants[index].copyWith(lastKnownPosition: message.position!);
// Don't notify for position updates - too frequent // Don't notify for position updates - too frequent
} }
} }
@@ -606,7 +606,7 @@ class WatchTogetherProvider with ChangeNotifier {
if (message.controlMode != null) { if (message.controlMode != null) {
appLogger.d('WatchTogether: Received session config, controlMode: ${message.controlMode}'); appLogger.d('WatchTogether: Received session config, controlMode: ${message.controlMode}');
_session = _session!.copyWith(controlMode: message.controlMode); _session = _session!.copyWith(controlMode: message.controlMode!);
_syncManager?.updateSession(_session!); // Update sync manager if it exists _syncManager?.updateSession(_session!); // Update sync manager if it exists
notifyListeners(); notifyListeners();
} }
+21 -1
View File
@@ -59,7 +59,27 @@ else
rm -f "$out" rm -f "$out"
fi fi
# 2. Native formatting # 2. Codegen freshness (build_runner outputs newer than their sources)
section "codegen freshness"
stale=()
while IFS= read -r -d '' src; do
for gen in "${src%.dart}.g.dart" "${src%.dart}.freezed.dart"; do
if [ -f "$gen" ] && [ "$src" -nt "$gen" ]; then
stale+=("${src#./}")
break
fi
done
done < <(find lib -name "*.dart" ! -name "*.g.dart" ! -name "*.freezed.dart" -type f -print0 2>/dev/null)
if [ ${#stale[@]} -eq 0 ]; then
ok "no stale generated files"
else
fail "${#stale[@]} dart source(s) newer than their generated .g/.freezed:"
printf ' %s\n' "${stale[@]}"
echo " Run: scripts/codegen.sh"
FAILED=1
fi
# 3. Native formatting
section "native format" section "native format"
out="$(mktemp)" out="$(mktemp)"
if scripts/format_native.sh --check >"$out" 2>&1; then if scripts/format_native.sh --check >"$out" 2>&1; then
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
exec dart run build_runner build --delete-conflicting-outputs "$@"
+8 -2
View File
@@ -59,13 +59,19 @@ void main() {
}); });
group('MediaSort equality & hashCode', () { group('MediaSort equality & hashCode', () {
test('equality is based on key only (matches current contract)', () { test('value equality across all fields', () {
final a = MediaSort(key: 'k', descKey: 'k:desc', title: 'A', defaultDirection: 'asc'); final a = MediaSort(key: 'k', descKey: 'k:desc', title: 'A', defaultDirection: 'asc');
final b = MediaSort(key: 'k', descKey: 'other', title: 'B', defaultDirection: 'desc'); final b = MediaSort(key: 'k', descKey: 'k:desc', title: 'A', defaultDirection: 'asc');
expect(a, equals(b)); expect(a, equals(b));
expect(a.hashCode, b.hashCode); expect(a.hashCode, b.hashCode);
}); });
test('differing non-key fields make instances unequal', () {
final a = MediaSort(key: 'k', descKey: 'k:desc', title: 'A', defaultDirection: 'asc');
final b = MediaSort(key: 'k', descKey: 'other', title: 'B', defaultDirection: 'desc');
expect(a, isNot(equals(b)));
});
test('different keys are not equal', () { test('different keys are not equal', () {
final a = MediaSort(key: 'k1', title: 'A'); final a = MediaSort(key: 'k1', title: 'A');
final b = MediaSort(key: 'k2', title: 'A'); final b = MediaSort(key: 'k2', title: 'A');
+3 -13
View File
@@ -71,7 +71,7 @@ void main() {
}); });
Future<Profile> createActiveLocalProfile(String id) async { Future<Profile> createActiveLocalProfile(String id) async {
final profile = Profile(id: id, kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)); final profile = Profile.local(id: id, displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
await profiles.upsert(profile); await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id); await storage.setActiveProfileId(profile.id);
await activeProfile.initialize(); await activeProfile.initialize();
@@ -79,12 +79,7 @@ void main() {
} }
test('local profile with no connections binds successfully with empty visibility', () async { test('local profile with no connections binds successfully with empty visibility', () async {
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
await profiles.upsert(profile); await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id); await storage.setActiveProfileId(profile.id);
await activeProfile.initialize(); await activeProfile.initialize();
@@ -97,12 +92,7 @@ void main() {
}); });
test('started binder does not loop forever after empty local bind', () async { test('started binder does not loop forever after empty local bind', () async {
final profile = Profile( final profile = Profile.local(id: 'local-empty', displayName: 'Empty', createdAt: DateTime(2026, 1, 1));
id: 'local-empty',
kind: ProfileKind.local,
displayName: 'Empty',
createdAt: DateTime(2026, 1, 1),
);
await profiles.upsert(profile); await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id); await storage.setActiveProfileId(profile.id);
await activeProfile.initialize(); await activeProfile.initialize();
+10 -34
View File
@@ -97,9 +97,7 @@ void main() {
// Fresh state: no auto-fallback to the first profile so the UI can // Fresh state: no auto-fallback to the first profile so the UI can
// force the picker. The binder skips its rebind while active is null, // force the picker. The binder skips its rebind while active is null,
// which is what avoids the surprise PIN prompt at first sign-in. // which is what avoids the surprise PIN prompt at first sign-in.
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)),
);
await provider.initialize(); await provider.initialize();
expect(provider.profiles, hasLength(1)); expect(provider.profiles, hasLength(1));
expect(provider.activeId, isNull); expect(provider.activeId, isNull);
@@ -127,9 +125,7 @@ void main() {
test('initialize clears storage when stored id is stale', () async { test('initialize clears storage when stored id is stale', () async {
// A previously-active profile that was deleted should not keep // A previously-active profile that was deleted should not keep
// storage-scoped settings under the removed profile id. // storage-scoped settings under the removed profile id.
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)),
);
await storage.setActiveProfileId('ghost-id-no-longer-exists'); await storage.setActiveProfileId('ghost-id-no-longer-exists');
await provider.initialize(); await provider.initialize();
await Future<void>.delayed(Duration.zero); await Future<void>.delayed(Duration.zero);
@@ -138,24 +134,16 @@ void main() {
}); });
test('initialize resolves the stored active profile id', () async { test('initialize resolves the stored active profile id', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)), await registry.upsert(Profile.local(id: 'p2', displayName: 'Kids', createdAt: DateTime(2026, 1, 2)));
);
await registry.upsert(
Profile(id: 'p2', kind: ProfileKind.local, displayName: 'Kids', createdAt: DateTime(2026, 1, 2)),
);
await storage.setActiveProfileId('p2'); await storage.setActiveProfileId('p2');
await provider.initialize(); await provider.initialize();
expect(provider.activeId, 'p2'); expect(provider.activeId, 'p2');
}); });
test('activate without PIN switches a non-protected profile', () async { test('activate without PIN switches a non-protected profile', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)), await registry.upsert(Profile.local(id: 'p2', displayName: 'Kids', createdAt: DateTime(2026, 1, 2)));
);
await registry.upsert(
Profile(id: 'p2', kind: ProfileKind.local, displayName: 'Kids', createdAt: DateTime(2026, 1, 2)),
);
await provider.initialize(); await provider.initialize();
final p2 = provider.profiles.firstWhere((p) => p.id == 'p2'); final p2 = provider.profiles.firstWhere((p) => p.id == 'p2');
final ok = await provider.activate(p2); final ok = await provider.activate(p2);
@@ -164,9 +152,7 @@ void main() {
}); });
test('clearActiveProfile clears storage and in-memory active profile', () async { test('clearActiveProfile clears storage and in-memory active profile', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)),
);
await provider.initialize(); await provider.initialize();
await provider.activate(provider.profiles.single); await provider.activate(provider.profiles.single);
@@ -178,13 +164,7 @@ void main() {
test('activate rejects wrong PIN for a protected local profile', () async { test('activate rejects wrong PIN for a protected local profile', () async {
await registry.upsert( await registry.upsert(
Profile( Profile.local(id: 'p1', displayName: 'Kids', pinHash: computePinHash('1234'), createdAt: DateTime(2026, 1, 1)),
id: 'p1',
kind: ProfileKind.local,
displayName: 'Kids',
pinHash: computePinHash('1234'),
createdAt: DateTime(2026, 1, 1),
),
); );
await provider.initialize(); await provider.initialize();
final p1 = provider.profiles.first; final p1 = provider.profiles.first;
@@ -193,9 +173,7 @@ void main() {
}); });
test('hasMultipleProfiles reflects the registry size', () async { test('hasMultipleProfiles reflects the registry size', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)),
);
await provider.initialize(); await provider.initialize();
expect(provider.hasMultipleProfiles, isFalse); expect(provider.hasMultipleProfiles, isFalse);
// Latch onto the next provider notification that flips the flag, // Latch onto the next provider notification that flips the flag,
@@ -211,9 +189,7 @@ void main() {
provider.addListener(listener); provider.addListener(listener);
addTearDown(() => provider.removeListener(listener)); addTearDown(() => provider.removeListener(listener));
await registry.upsert( await registry.upsert(Profile.local(id: 'p2', displayName: 'Kids', createdAt: DateTime(2026, 1, 2)));
Profile(id: 'p2', kind: ProfileKind.local, displayName: 'Kids', createdAt: DateTime(2026, 1, 2)),
);
await flipped.future.timeout(const Duration(seconds: 2)); await flipped.future.timeout(const Duration(seconds: 2));
expect(provider.hasMultipleProfiles, isTrue); expect(provider.hasMultipleProfiles, isTrue);
}); });
+9 -25
View File
@@ -23,9 +23,8 @@ void main() {
}); });
test('upsert + get round-trips a local profile', () async { test('upsert + get round-trips a local profile', () async {
final profile = Profile( final profile = Profile.local(
id: 'local-1', id: 'local-1',
kind: ProfileKind.local,
displayName: 'Owner', displayName: 'Owner',
pinHash: computePinHash('1234'), pinHash: computePinHash('1234'),
createdAt: DateTime(2026, 1, 1), createdAt: DateTime(2026, 1, 1),
@@ -40,9 +39,8 @@ void main() {
}); });
test('upsert + get round-trips a plex_home profile', () async { test('upsert + get round-trips a plex_home profile', () async {
final profile = Profile( final profile = Profile.plexHome(
id: 'plex-home-acct-uuid', id: 'plex-home-acct-uuid',
kind: ProfileKind.plexHome,
displayName: 'Admin', displayName: 'Admin',
avatarThumbUrl: 'https://plex.tv/users/abc/avatar?', avatarThumbUrl: 'https://plex.tv/users/abc/avatar?',
parentConnectionId: 'acct', parentConnectionId: 'acct',
@@ -62,28 +60,20 @@ void main() {
}); });
test('list orders by sortOrder then createdAt', () async { test('list orders by sortOrder then createdAt', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'a', displayName: 'A', sortOrder: 1, createdAt: DateTime(2026, 1, 1)));
Profile(id: 'a', kind: ProfileKind.local, displayName: 'A', sortOrder: 1, createdAt: DateTime(2026, 1, 1)), await registry.upsert(Profile.local(id: 'b', displayName: 'B', sortOrder: 0, createdAt: DateTime(2026, 1, 2)));
);
await registry.upsert(
Profile(id: 'b', kind: ProfileKind.local, displayName: 'B', sortOrder: 0, createdAt: DateTime(2026, 1, 2)),
);
final list = await registry.list(); final list = await registry.list();
expect(list.map((p) => p.id).toList(), ['b', 'a']); expect(list.map((p) => p.id).toList(), ['b', 'a']);
}); });
test('remove deletes a profile', () async { test('remove deletes a profile', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p', displayName: 'P', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p', kind: ProfileKind.local, displayName: 'P', createdAt: DateTime(2026, 1, 1)),
);
await registry.remove('p'); await registry.remove('p');
expect(await registry.get('p'), isNull); expect(await registry.get('p'), isNull);
}); });
test('markUsed updates lastUsedAt', () async { test('markUsed updates lastUsedAt', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p', displayName: 'P', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p', kind: ProfileKind.local, displayName: 'P', createdAt: DateTime(2026, 1, 1)),
);
final ts = DateTime(2026, 1, 5, 12, 0); final ts = DateTime(2026, 1, 5, 12, 0);
await registry.markUsed('p', ts); await registry.markUsed('p', ts);
final fetched = await registry.get('p'); final fetched = await registry.get('p');
@@ -91,12 +81,8 @@ void main() {
}); });
test('upsert is idempotent (replaces existing row)', () async { test('upsert is idempotent (replaces existing row)', () async {
await registry.upsert( await registry.upsert(Profile.local(id: 'p', displayName: 'Original', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p', kind: ProfileKind.local, displayName: 'Original', createdAt: DateTime(2026, 1, 1)), await registry.upsert(Profile.local(id: 'p', displayName: 'Renamed', createdAt: DateTime(2026, 1, 1)));
);
await registry.upsert(
Profile(id: 'p', kind: ProfileKind.local, displayName: 'Renamed', createdAt: DateTime(2026, 1, 1)),
);
final fetched = await registry.get('p'); final fetched = await registry.get('p');
expect(fetched!.displayName, 'Renamed'); expect(fetched!.displayName, 'Renamed');
}); });
@@ -115,9 +101,7 @@ void main() {
emitsThrough(isEmpty), emitsThrough(isEmpty),
]), ]),
); );
await registry.upsert( await registry.upsert(Profile.local(id: 'p', displayName: 'P', createdAt: DateTime(2026, 1, 1)));
Profile(id: 'p', kind: ProfileKind.local, displayName: 'P', createdAt: DateTime(2026, 1, 1)),
);
await registry.remove('p'); await registry.remove('p');
await assertion; await assertion;
}); });
+5 -9
View File
@@ -4,7 +4,7 @@ import 'package:plezy/profiles/profile.dart';
void main() { void main() {
group('Profile', () { group('Profile', () {
test('local profile defaults', () { test('local profile defaults', () {
final p = Profile(id: 'local-1', kind: ProfileKind.local, displayName: 'Owner', createdAt: DateTime(2026, 1, 1)); final p = Profile.local(id: 'local-1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
expect(p.isLocal, isTrue); expect(p.isLocal, isTrue);
expect(p.isPlexHome, isFalse); expect(p.isPlexHome, isFalse);
expect(p.isPinProtected, isFalse); expect(p.isPinProtected, isFalse);
@@ -12,9 +12,8 @@ void main() {
}); });
test('local profile with PIN is pin-protected', () { test('local profile with PIN is pin-protected', () {
final p = Profile( final p = Profile.local(
id: 'local-1', id: 'local-1',
kind: ProfileKind.local,
displayName: 'Kids', displayName: 'Kids',
pinHash: computePinHash('1234'), pinHash: computePinHash('1234'),
createdAt: DateTime(2026, 1, 1), createdAt: DateTime(2026, 1, 1),
@@ -23,9 +22,8 @@ void main() {
}); });
test('plex_home profile pin protection follows the protected flag', () { test('plex_home profile pin protection follows the protected flag', () {
final p = Profile( final p = Profile.plexHome(
id: 'plex-home-acct1-uuid1', id: 'plex-home-acct1-uuid1',
kind: ProfileKind.plexHome,
displayName: 'Sarah', displayName: 'Sarah',
parentConnectionId: 'acct1', parentConnectionId: 'acct1',
plexProtected: true, plexProtected: true,
@@ -36,9 +34,8 @@ void main() {
}); });
test('local PIN hash is round-tripped via configJson', () { test('local PIN hash is round-tripped via configJson', () {
final p = Profile( final p = Profile.local(
id: 'local-1', id: 'local-1',
kind: ProfileKind.local,
displayName: 'Kids', displayName: 'Kids',
pinHash: computePinHash('1234'), pinHash: computePinHash('1234'),
createdAt: DateTime(2026, 1, 1), createdAt: DateTime(2026, 1, 1),
@@ -59,9 +56,8 @@ void main() {
}); });
test('plex_home configJson round-trips with all flags', () { test('plex_home configJson round-trips with all flags', () {
final p = Profile( final p = Profile.plexHome(
id: 'plex-home-acct1-uuid1', id: 'plex-home-acct1-uuid1',
kind: ProfileKind.plexHome,
displayName: 'Admin', displayName: 'Admin',
parentConnectionId: 'acct1', parentConnectionId: 'acct1',
plexAdmin: true, plexAdmin: true,
+2 -8
View File
@@ -6,12 +6,7 @@ import 'package:plezy/profiles/profiles_view.dart';
void main() { void main() {
group('visibleProfileConnections', () { group('visibleProfileConnections', () {
test('keeps all local profile connection rows', () { test('keeps all local profile connection rows', () {
final profile = Profile( final profile = Profile.local(id: 'local-1', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-1',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
const rows = [ const rows = [
ProfileConnection(profileId: 'local-1', connectionId: 'plex-1', userIdentifier: 'u1'), ProfileConnection(profileId: 'local-1', connectionId: 'plex-1', userIdentifier: 'u1'),
ProfileConnection(profileId: 'local-1', connectionId: 'jellyfin-1', userIdentifier: 'u2'), ProfileConnection(profileId: 'local-1', connectionId: 'jellyfin-1', userIdentifier: 'u2'),
@@ -21,9 +16,8 @@ void main() {
}); });
test('filters Plex Home parent token cache row', () { test('filters Plex Home parent token cache row', () {
final profile = Profile( final profile = Profile.plexHome(
id: 'plex-home-plex-1-user-1', id: 'plex-home-plex-1-user-1',
kind: ProfileKind.plexHome,
displayName: 'Kid', displayName: 'Kid',
parentConnectionId: 'plex-1', parentConnectionId: 'plex-1',
createdAt: DateTime(2026, 1, 1), createdAt: DateTime(2026, 1, 1),
@@ -461,7 +461,7 @@ JellyfinConnection _jellyfinConnection(String id) {
} }
Profile _localProfile(String id) { Profile _localProfile(String id) {
return Profile(id: id, kind: ProfileKind.local, displayName: id, createdAt: DateTime(2026, 1, 1)); return Profile.local(id: id, displayName: id, createdAt: DateTime(2026, 1, 1));
} }
PlexHome _home(String adminUuid) { PlexHome _home(String adminUuid) {
+2 -12
View File
@@ -80,12 +80,7 @@ void main() {
await db.close(); await db.close();
}); });
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
final plex = PlexAccountConnection( final plex = PlexAccountConnection(
id: 'plex-a', id: 'plex-a',
accountToken: 'plex-token', accountToken: 'plex-token',
@@ -217,12 +212,7 @@ void main() {
await db.close(); await db.close();
}); });
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
final accountA = PlexAccountConnection( final accountA = PlexAccountConnection(
id: 'plex-a', id: 'plex-a',
accountToken: 'wrong-owner-token', accountToken: 'wrong-owner-token',
+2 -12
View File
@@ -75,12 +75,7 @@ void main() {
}); });
test('initial profile selection is skipped when a profile was auto-selected', () { test('initial profile selection is skipped when a profile was auto-selected', () {
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
expect( expect(
shouldPromptForInitialProfileSelection( shouldPromptForInitialProfileSelection(
@@ -94,12 +89,7 @@ void main() {
}); });
test('initial profile selection is required when the launch setting is enabled', () { test('initial profile selection is required when the launch setting is enabled', () {
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
expect( expect(
shouldPromptForInitialProfileSelection( shouldPromptForInitialProfileSelection(
@@ -35,12 +35,7 @@ void main() {
testWidgets('remote back pops the manage profile page', (tester) async { testWidgets('remote back pops the manage profile page', (tester) async {
TvDetectionService.debugSetAppleTVOverride(true); TvDetectionService.debugSetAppleTVOverride(true);
final db = AppDatabase.forTesting(NativeDatabase.memory()); final db = AppDatabase.forTesting(NativeDatabase.memory());
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
final profiles = ProfileRegistry(db); final profiles = ProfileRegistry(db);
final connections = _FakeConnectionRegistry(db); final connections = _FakeConnectionRegistry(db);
final profileConnections = _FakeProfileConnectionRegistry(db); final profileConnections = _FakeProfileConnectionRegistry(db);
@@ -28,12 +28,7 @@ void main() {
testWidgets('D-pad can focus profile actions and open the manage menu', (tester) async { testWidgets('D-pad can focus profile actions and open the manage menu', (tester) async {
final db = AppDatabase.forTesting(NativeDatabase.memory()); final db = AppDatabase.forTesting(NativeDatabase.memory());
final profile = Profile( final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
id: 'local-owner',
kind: ProfileKind.local,
displayName: 'Owner',
createdAt: DateTime(2026, 1, 1),
);
final profiles = _FakeProfileRegistry(db, [profile]); final profiles = _FakeProfileRegistry(db, [profile]);
final connections = _FakeConnectionRegistry(db); final connections = _FakeConnectionRegistry(db);
final profileConnections = _FakeProfileConnectionRegistry(db); final profileConnections = _FakeProfileConnectionRegistry(db);
@@ -5,13 +5,8 @@ import 'package:plezy/profiles/profile.dart';
import 'package:plezy/screens/settings/add_jellyfin_screen.dart'; import 'package:plezy/screens/settings/add_jellyfin_screen.dart';
import 'package:plezy/utils/platform_detector.dart'; import 'package:plezy/utils/platform_detector.dart';
Profile _profile(String id) => Profile( Profile _profile(String id) =>
id: id, Profile.local(id: id, displayName: id, sortOrder: 0, createdAt: DateTime.fromMillisecondsSinceEpoch(0));
kind: ProfileKind.local,
displayName: id,
sortOrder: 0,
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
);
void main() { void main() {
tearDown(() { tearDown(() {