Init repo

This commit is contained in:
PartyDonut 2024-09-15 14:12:28 +02:00
commit 764b6034e3
566 changed files with 212335 additions and 0 deletions

View file

@ -0,0 +1,107 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first, invalid_annotation_target
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/credentials_model.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'account_model.freezed.dart';
part 'account_model.g.dart';
@freezed
class AccountModel with _$AccountModel {
const AccountModel._();
const factory AccountModel({
required String name,
required String id,
required String avatar,
required DateTime lastUsed,
@Default(Authentication.autoLogin) Authentication authMethod,
@Default("") String localPin,
required CredentialsModel credentials,
@Default([]) List<String> latestItemsExcludes,
@Default([]) List<String> searchQueryHistory,
@Default(false) bool quickConnectState,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
}) = _AccountModel;
factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);
String get server {
return credentials.server;
}
bool get canDownload {
return (policy?.enableContentDownloading ?? false) && !kIsWeb;
}
//Check if it's the same account on the same server
bool sameIdentity(AccountModel other) {
if (identical(this, other)) return true;
return other.id == id && other.credentials.serverId == credentials.serverId;
}
}
enum Authentication {
autoLogin(0),
biometrics(1),
passcode(2),
none(3);
const Authentication(this.value);
final int value;
bool available(BuildContext context) {
switch (this) {
case Authentication.none:
case Authentication.autoLogin:
case Authentication.passcode:
return true;
case Authentication.biometrics:
return !AdaptiveLayout.of(context).isDesktop;
}
}
String name(BuildContext context) {
switch (this) {
case Authentication.none:
return context.localized.none;
case Authentication.autoLogin:
return context.localized.appLockAutoLogin;
case Authentication.biometrics:
return context.localized.appLockBiometrics;
case Authentication.passcode:
return context.localized.appLockPasscode;
}
}
IconData get icon {
switch (this) {
case Authentication.none:
return IconsaxBold.arrow_bottom;
case Authentication.autoLogin:
return IconsaxOutline.login_1;
case Authentication.biometrics:
return IconsaxOutline.finger_scan;
case Authentication.passcode:
return IconsaxOutline.password_check;
}
}
static Authentication fromMap(int value) {
return Authentication.values[value];
}
int toMap() {
return value;
}
}

View file

@ -0,0 +1,450 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'account_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
AccountModel _$AccountModelFromJson(Map<String, dynamic> json) {
return _AccountModel.fromJson(json);
}
/// @nodoc
mixin _$AccountModel {
String get name => throw _privateConstructorUsedError;
String get id => throw _privateConstructorUsedError;
String get avatar => throw _privateConstructorUsedError;
DateTime get lastUsed => throw _privateConstructorUsedError;
Authentication get authMethod => throw _privateConstructorUsedError;
String get localPin => throw _privateConstructorUsedError;
CredentialsModel get credentials => throw _privateConstructorUsedError;
List<String> get latestItemsExcludes => throw _privateConstructorUsedError;
List<String> get searchQueryHistory => throw _privateConstructorUsedError;
bool get quickConnectState => throw _privateConstructorUsedError;
@JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? get policy => throw _privateConstructorUsedError;
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AccountModelCopyWith<AccountModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AccountModelCopyWith<$Res> {
factory $AccountModelCopyWith(
AccountModel value, $Res Function(AccountModel) then) =
_$AccountModelCopyWithImpl<$Res, AccountModel>;
@useResult
$Res call(
{String name,
String id,
String avatar,
DateTime lastUsed,
Authentication authMethod,
String localPin,
CredentialsModel credentials,
List<String> latestItemsExcludes,
List<String> searchQueryHistory,
bool quickConnectState,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration});
}
/// @nodoc
class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
implements $AccountModelCopyWith<$Res> {
_$AccountModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? id = null,
Object? avatar = null,
Object? lastUsed = null,
Object? authMethod = null,
Object? localPin = null,
Object? credentials = null,
Object? latestItemsExcludes = null,
Object? searchQueryHistory = null,
Object? quickConnectState = null,
Object? policy = freezed,
Object? serverConfiguration = freezed,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
avatar: null == avatar
? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable
as String,
lastUsed: null == lastUsed
? _value.lastUsed
: lastUsed // ignore: cast_nullable_to_non_nullable
as DateTime,
authMethod: null == authMethod
? _value.authMethod
: authMethod // ignore: cast_nullable_to_non_nullable
as Authentication,
localPin: null == localPin
? _value.localPin
: localPin // ignore: cast_nullable_to_non_nullable
as String,
credentials: null == credentials
? _value.credentials
: credentials // ignore: cast_nullable_to_non_nullable
as CredentialsModel,
latestItemsExcludes: null == latestItemsExcludes
? _value.latestItemsExcludes
: latestItemsExcludes // ignore: cast_nullable_to_non_nullable
as List<String>,
searchQueryHistory: null == searchQueryHistory
? _value.searchQueryHistory
: searchQueryHistory // ignore: cast_nullable_to_non_nullable
as List<String>,
quickConnectState: null == quickConnectState
? _value.quickConnectState
: quickConnectState // ignore: cast_nullable_to_non_nullable
as bool,
policy: freezed == policy
? _value.policy
: policy // ignore: cast_nullable_to_non_nullable
as UserPolicy?,
serverConfiguration: freezed == serverConfiguration
? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?,
) as $Val);
}
}
/// @nodoc
abstract class _$$AccountModelImplCopyWith<$Res>
implements $AccountModelCopyWith<$Res> {
factory _$$AccountModelImplCopyWith(
_$AccountModelImpl value, $Res Function(_$AccountModelImpl) then) =
__$$AccountModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String name,
String id,
String avatar,
DateTime lastUsed,
Authentication authMethod,
String localPin,
CredentialsModel credentials,
List<String> latestItemsExcludes,
List<String> searchQueryHistory,
bool quickConnectState,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration});
}
/// @nodoc
class __$$AccountModelImplCopyWithImpl<$Res>
extends _$AccountModelCopyWithImpl<$Res, _$AccountModelImpl>
implements _$$AccountModelImplCopyWith<$Res> {
__$$AccountModelImplCopyWithImpl(
_$AccountModelImpl _value, $Res Function(_$AccountModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? id = null,
Object? avatar = null,
Object? lastUsed = null,
Object? authMethod = null,
Object? localPin = null,
Object? credentials = null,
Object? latestItemsExcludes = null,
Object? searchQueryHistory = null,
Object? quickConnectState = null,
Object? policy = freezed,
Object? serverConfiguration = freezed,
}) {
return _then(_$AccountModelImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
avatar: null == avatar
? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable
as String,
lastUsed: null == lastUsed
? _value.lastUsed
: lastUsed // ignore: cast_nullable_to_non_nullable
as DateTime,
authMethod: null == authMethod
? _value.authMethod
: authMethod // ignore: cast_nullable_to_non_nullable
as Authentication,
localPin: null == localPin
? _value.localPin
: localPin // ignore: cast_nullable_to_non_nullable
as String,
credentials: null == credentials
? _value.credentials
: credentials // ignore: cast_nullable_to_non_nullable
as CredentialsModel,
latestItemsExcludes: null == latestItemsExcludes
? _value._latestItemsExcludes
: latestItemsExcludes // ignore: cast_nullable_to_non_nullable
as List<String>,
searchQueryHistory: null == searchQueryHistory
? _value._searchQueryHistory
: searchQueryHistory // ignore: cast_nullable_to_non_nullable
as List<String>,
quickConnectState: null == quickConnectState
? _value.quickConnectState
: quickConnectState // ignore: cast_nullable_to_non_nullable
as bool,
policy: freezed == policy
? _value.policy
: policy // ignore: cast_nullable_to_non_nullable
as UserPolicy?,
serverConfiguration: freezed == serverConfiguration
? _value.serverConfiguration
: serverConfiguration // ignore: cast_nullable_to_non_nullable
as ServerConfiguration?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
const _$AccountModelImpl(
{required this.name,
required this.id,
required this.avatar,
required this.lastUsed,
this.authMethod = Authentication.autoLogin,
this.localPin = "",
required this.credentials,
final List<String> latestItemsExcludes = const [],
final List<String> searchQueryHistory = const [],
this.quickConnectState = false,
@JsonKey(includeFromJson: false, includeToJson: false) this.policy,
@JsonKey(includeFromJson: false, includeToJson: false)
this.serverConfiguration})
: _latestItemsExcludes = latestItemsExcludes,
_searchQueryHistory = searchQueryHistory,
super._();
factory _$AccountModelImpl.fromJson(Map<String, dynamic> json) =>
_$$AccountModelImplFromJson(json);
@override
final String name;
@override
final String id;
@override
final String avatar;
@override
final DateTime lastUsed;
@override
@JsonKey()
final Authentication authMethod;
@override
@JsonKey()
final String localPin;
@override
final CredentialsModel credentials;
final List<String> _latestItemsExcludes;
@override
@JsonKey()
List<String> get latestItemsExcludes {
if (_latestItemsExcludes is EqualUnmodifiableListView)
return _latestItemsExcludes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_latestItemsExcludes);
}
final List<String> _searchQueryHistory;
@override
@JsonKey()
List<String> get searchQueryHistory {
if (_searchQueryHistory is EqualUnmodifiableListView)
return _searchQueryHistory;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_searchQueryHistory);
}
@override
@JsonKey()
final bool quickConnectState;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
final UserPolicy? policy;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, policy: $policy, serverConfiguration: $serverConfiguration)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'AccountModel'))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('id', id))
..add(DiagnosticsProperty('avatar', avatar))
..add(DiagnosticsProperty('lastUsed', lastUsed))
..add(DiagnosticsProperty('authMethod', authMethod))
..add(DiagnosticsProperty('localPin', localPin))
..add(DiagnosticsProperty('credentials', credentials))
..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes))
..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory))
..add(DiagnosticsProperty('quickConnectState', quickConnectState))
..add(DiagnosticsProperty('policy', policy))
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AccountModelImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.id, id) || other.id == id) &&
(identical(other.avatar, avatar) || other.avatar == avatar) &&
(identical(other.lastUsed, lastUsed) ||
other.lastUsed == lastUsed) &&
(identical(other.authMethod, authMethod) ||
other.authMethod == authMethod) &&
(identical(other.localPin, localPin) ||
other.localPin == localPin) &&
(identical(other.credentials, credentials) ||
other.credentials == credentials) &&
const DeepCollectionEquality()
.equals(other._latestItemsExcludes, _latestItemsExcludes) &&
const DeepCollectionEquality()
.equals(other._searchQueryHistory, _searchQueryHistory) &&
(identical(other.quickConnectState, quickConnectState) ||
other.quickConnectState == quickConnectState) &&
(identical(other.policy, policy) || other.policy == policy) &&
(identical(other.serverConfiguration, serverConfiguration) ||
other.serverConfiguration == serverConfiguration));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
name,
id,
avatar,
lastUsed,
authMethod,
localPin,
credentials,
const DeepCollectionEquality().hash(_latestItemsExcludes),
const DeepCollectionEquality().hash(_searchQueryHistory),
quickConnectState,
policy,
serverConfiguration);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith =>
__$$AccountModelImplCopyWithImpl<_$AccountModelImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$AccountModelImplToJson(
this,
);
}
}
abstract class _AccountModel extends AccountModel {
const factory _AccountModel(
{required final String name,
required final String id,
required final String avatar,
required final DateTime lastUsed,
final Authentication authMethod,
final String localPin,
required final CredentialsModel credentials,
final List<String> latestItemsExcludes,
final List<String> searchQueryHistory,
final bool quickConnectState,
@JsonKey(includeFromJson: false, includeToJson: false)
final UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false)
final ServerConfiguration? serverConfiguration}) = _$AccountModelImpl;
const _AccountModel._() : super._();
factory _AccountModel.fromJson(Map<String, dynamic> json) =
_$AccountModelImpl.fromJson;
@override
String get name;
@override
String get id;
@override
String get avatar;
@override
DateTime get lastUsed;
@override
Authentication get authMethod;
@override
String get localPin;
@override
CredentialsModel get credentials;
@override
List<String> get latestItemsExcludes;
@override
List<String> get searchQueryHistory;
@override
bool get quickConnectState;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? get policy;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? get serverConfiguration;
@override
@JsonKey(ignore: true)
_$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$AccountModelImpl _$$AccountModelImplFromJson(Map<String, dynamic> json) =>
_$AccountModelImpl(
name: json['name'] as String,
id: json['id'] as String,
avatar: json['avatar'] as String,
lastUsed: DateTime.parse(json['lastUsed'] as String),
authMethod:
$enumDecodeNullable(_$AuthenticationEnumMap, json['authMethod']) ??
Authentication.autoLogin,
localPin: json['localPin'] as String? ?? "",
credentials: CredentialsModel.fromJson(json['credentials'] as String),
latestItemsExcludes: (json['latestItemsExcludes'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
searchQueryHistory: (json['searchQueryHistory'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
quickConnectState: json['quickConnectState'] as bool? ?? false,
);
Map<String, dynamic> _$$AccountModelImplToJson(_$AccountModelImpl instance) =>
<String, dynamic>{
'name': instance.name,
'id': instance.id,
'avatar': instance.avatar,
'lastUsed': instance.lastUsed.toIso8601String(),
'authMethod': _$AuthenticationEnumMap[instance.authMethod]!,
'localPin': instance.localPin,
'credentials': instance.credentials,
'latestItemsExcludes': instance.latestItemsExcludes,
'searchQueryHistory': instance.searchQueryHistory,
'quickConnectState': instance.quickConnectState,
};
const _$AuthenticationEnumMap = {
Authentication.autoLogin: 'autoLogin',
Authentication.biometrics: 'biometrics',
Authentication.passcode: 'passcode',
Authentication.none: 'none',
};

View file

@ -0,0 +1,72 @@
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class BookModel extends ItemBaseModel {
final String? parentName;
final List<ItemBaseModel> items;
BookModel(
{this.items = const [],
required this.parentName,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
super.jellyType,
required super.canDownload,
required super.canDelete});
@override
String? get subText => parentName;
@override
String? detailedName(BuildContext context) => "$name ${parentName != null ? "\n ($parentName)" : ""} ";
@override
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
@override
bool get playAble => true;
int get currentPage => userData.playbackPositionTicks ~/ 10000;
@override
String playText(BuildContext context) => context.localized.read(name);
@override
double get progress => userData.progress != 0 ? 100 : 0;
@override
String playButtonLabel(BuildContext context) => progress != 0
? context.localized.continuePage(currentPage)
: userData.played == true
? "${context.localized.restart} $name"
: context.localized.read(name);
factory BookModel.fromBaseDto(BaseItemDto item, Ref ref) {
return BookModel(
name: item.name ?? "",
id: item.id ?? "",
parentName: item.seriesName ?? item.seasonName,
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
canDelete: item.canDelete,
canDownload: item.canDownload,
primaryRatio: item.primaryImageAspectRatio,
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'boxset_model.mapper.dart';
@MappableClass()
class BoxSetModel extends ItemBaseModel with BoxSetModelMappable {
final List<ItemBaseModel> items;
const BoxSetModel({
this.items = const [],
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.canDelete,
required super.canDownload,
super.jellyType,
});
factory BoxSetModel.fromBaseDto(BaseItemDto item, Ref ref) {
return BoxSetModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
canDelete: item.canDelete,
canDownload: item.canDownload,
jellyType: item.type,
);
}
}

View file

@ -0,0 +1,242 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'boxset_model.dart';
class BoxSetModelMapper extends SubClassMapperBase<BoxSetModel> {
BoxSetModelMapper._();
static BoxSetModelMapper? _instance;
static BoxSetModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = BoxSetModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
ItemBaseModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'BoxSetModel';
static List<ItemBaseModel> _$items(BoxSetModel v) => v.items;
static const Field<BoxSetModel, List<ItemBaseModel>> _f$items =
Field('items', _$items, opt: true, def: const []);
static String _$name(BoxSetModel v) => v.name;
static const Field<BoxSetModel, String> _f$name = Field('name', _$name);
static String _$id(BoxSetModel v) => v.id;
static const Field<BoxSetModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(BoxSetModel v) => v.overview;
static const Field<BoxSetModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(BoxSetModel v) => v.parentId;
static const Field<BoxSetModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(BoxSetModel v) => v.playlistId;
static const Field<BoxSetModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(BoxSetModel v) => v.images;
static const Field<BoxSetModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(BoxSetModel v) => v.childCount;
static const Field<BoxSetModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(BoxSetModel v) => v.primaryRatio;
static const Field<BoxSetModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(BoxSetModel v) => v.userData;
static const Field<BoxSetModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDelete(BoxSetModel v) => v.canDelete;
static const Field<BoxSetModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static bool? _$canDownload(BoxSetModel v) => v.canDownload;
static const Field<BoxSetModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static BaseItemKind? _$jellyType(BoxSetModel v) => v.jellyType;
static const Field<BoxSetModel, BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<BoxSetModel> fields = const {
#items: _f$items,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDelete: _f$canDelete,
#canDownload: _f$canDownload,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'BoxSetModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static BoxSetModel _instantiate(DecodingData data) {
return BoxSetModel(
items: data.dec(_f$items),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDelete: data.dec(_f$canDelete),
canDownload: data.dec(_f$canDownload),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static BoxSetModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<BoxSetModel>(map);
}
static BoxSetModel fromJson(String json) {
return ensureInitialized().decodeJson<BoxSetModel>(json);
}
}
mixin BoxSetModelMappable {
String toJson() {
return BoxSetModelMapper.ensureInitialized()
.encodeJson<BoxSetModel>(this as BoxSetModel);
}
Map<String, dynamic> toMap() {
return BoxSetModelMapper.ensureInitialized()
.encodeMap<BoxSetModel>(this as BoxSetModel);
}
BoxSetModelCopyWith<BoxSetModel, BoxSetModel, BoxSetModel> get copyWith =>
_BoxSetModelCopyWithImpl(this as BoxSetModel, $identity, $identity);
@override
String toString() {
return BoxSetModelMapper.ensureInitialized()
.stringifyValue(this as BoxSetModel);
}
}
extension BoxSetModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, BoxSetModel, $Out> {
BoxSetModelCopyWith<$R, BoxSetModel, $Out> get $asBoxSetModel =>
$base.as((v, t, t2) => _BoxSetModelCopyWithImpl(v, t, t2));
}
abstract class BoxSetModelCopyWith<$R, $In extends BoxSetModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get items;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{List<ItemBaseModel>? items,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDelete,
bool? canDownload,
BaseItemKind? jellyType});
BoxSetModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _BoxSetModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, BoxSetModel, $Out>
implements BoxSetModelCopyWith<$R, BoxSetModel, $Out> {
_BoxSetModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<BoxSetModel> $mapper =
BoxSetModelMapper.ensureInitialized();
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get items => ListCopyWith(
$value.items, (v, t) => v.copyWith.$chain(t), (v) => call(items: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{List<ItemBaseModel>? items,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDelete = $none,
Object? canDownload = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (items != null) #items: items,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDelete != $none) #canDelete: canDelete,
if (canDownload != $none) #canDownload: canDownload,
if (jellyType != $none) #jellyType: jellyType
}));
@override
BoxSetModel $make(CopyWithData data) => BoxSetModel(
items: data.get(#items, or: $value.items),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDelete: data.get(#canDelete, or: $value.canDelete),
canDownload: data.get(#canDownload, or: $value.canDownload),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
BoxSetModelCopyWith<$R2, BoxSetModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_BoxSetModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,49 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:flutter/material.dart';
extension CollectionTypeExtension on CollectionType {
IconData get iconOutlined {
return getIconType(true);
}
IconData get icon {
return getIconType(false);
}
Set<FladderItemType> get itemKinds {
switch (this) {
case CollectionType.movies:
return {FladderItemType.movie};
case CollectionType.tvshows:
return {FladderItemType.series};
case CollectionType.homevideos:
return {FladderItemType.photoalbum, FladderItemType.folder, FladderItemType.photo, FladderItemType.video};
case CollectionType.boxsets:
case CollectionType.folders:
case CollectionType.books:
default:
return {};
}
}
IconData getIconType(bool outlined) {
switch (this) {
case CollectionType.movies:
return outlined ? IconsaxOutline.video_horizontal : IconsaxBold.video_horizontal;
case CollectionType.tvshows:
return outlined ? IconsaxOutline.video_vertical : IconsaxBold.video_vertical;
case CollectionType.boxsets:
case CollectionType.folders:
return outlined ? IconsaxOutline.folder : IconsaxBold.folder;
case CollectionType.homevideos:
return outlined ? IconsaxOutline.gallery : IconsaxBold.gallery;
case CollectionType.books:
return outlined ? IconsaxOutline.book : Icons.book_rounded;
default:
return IconsaxOutline.info_circle;
}
}
}

View file

@ -0,0 +1,81 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/util/application_info.dart';
import 'package:xid/xid.dart';
class CredentialsModel {
final String token;
final String server;
final String serverName;
final String serverId;
final String deviceId;
CredentialsModel({
this.token = "",
this.server = "",
this.serverName = "",
this.serverId = "",
required this.deviceId,
});
factory CredentialsModel.createNewCredentials() {
return CredentialsModel(deviceId: Xid().toString());
}
Map<String, String> header(Ref ref) {
final application = ref.read(applicationInfoProvider);
final headers = {
'content-type': 'application/json',
'x-emby-token': token,
'x-emby-authorization':
'MediaBrowser Client="${application.name}", Device="${application.os}", DeviceId="$deviceId", Version="${application.version}"'
};
return headers;
}
CredentialsModel copyWith({
String? token,
String? server,
String? serverName,
String? serverId,
String? deviceId,
}) {
return CredentialsModel(
token: token ?? this.token,
server: server ?? this.server,
serverName: serverName ?? this.serverName,
serverId: serverId ?? this.serverId,
deviceId: deviceId ?? this.deviceId,
);
}
Map<String, dynamic> toMap() {
return {
'token': token,
'server': server,
'serverName': serverName,
'serverId': serverId,
'deviceId': deviceId,
};
}
factory CredentialsModel.fromMap(Map<String, dynamic> map) {
return CredentialsModel(
token: map['token'] ?? '',
server: map['server'] ?? '',
serverName: map['serverName'] ?? '',
serverId: map['serverId'] ?? '',
deviceId: map['deviceId'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory CredentialsModel.fromJson(String source) => CredentialsModel.fromMap(json.decode(source));
@override
String toString() {
return 'CredentialsModel(token: $token, server: $server, serverName: $serverName, serverId: $serverId, header: $header)';
}
}

View file

@ -0,0 +1,26 @@
import 'package:fladder/models/item_base_model.dart';
class FavouritesModel {
final bool loading;
final Map<FladderItemType, List<ItemBaseModel>> favourites;
final List<ItemBaseModel> people;
FavouritesModel({
this.loading = false,
this.favourites = const {},
this.people = const [],
});
FavouritesModel copyWith({
bool? loading,
String? searchQuery,
Map<FladderItemType, List<ItemBaseModel>>? favourites,
List<ItemBaseModel>? people,
}) {
return FavouritesModel(
loading: loading ?? this.loading,
favourites: favourites ?? this.favourites,
people: people ?? this.people,
);
}
}

View file

@ -0,0 +1,34 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/models/item_base_model.dart';
class HomeModel {
final bool loading;
final List<ItemBaseModel> resumeVideo;
final List<ItemBaseModel> resumeAudio;
final List<ItemBaseModel> resumeBooks;
final List<ItemBaseModel> nextUp;
HomeModel({
this.loading = false,
this.resumeVideo = const [],
this.resumeAudio = const [],
this.resumeBooks = const [],
this.nextUp = const [],
});
HomeModel copyWith({
bool? loading,
List<ItemBaseModel>? resumeVideo,
List<ItemBaseModel>? resumeAudio,
List<ItemBaseModel>? resumeBooks,
List<ItemBaseModel>? nextUp,
List<ItemBaseModel>? nextUpBooks,
}) {
return HomeModel(
loading: loading ?? this.loading,
resumeVideo: resumeVideo ?? this.resumeVideo,
resumeAudio: resumeAudio ?? this.resumeAudio,
resumeBooks: resumeBooks ?? this.resumeBooks,
nextUp: nextUp ?? this.nextUp,
);
}
}

View file

@ -0,0 +1,107 @@
// ignore_for_file: constant_identifier_names
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/util/size_formatting.dart';
class InformationModel {
final Map<String, dynamic> baseInformation;
final List<Map<String, dynamic>> videoStreams;
final List<Map<String, dynamic>> audioStreams;
final List<Map<String, dynamic>> subStreams;
InformationModel({
required this.baseInformation,
required this.videoStreams,
required this.audioStreams,
required this.subStreams,
});
static InformationModel? fromResponse(BaseItemDto? item) {
if (item == null) return null;
var videoStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.video).toList() ?? [];
var audioStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.audio).toList() ?? [];
var subStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.subtitle).toList() ?? [];
return InformationModel(
baseInformation: {
"Title": item.name,
"Container": item.container,
"Path": item.path,
"Size": item.mediaSources?.firstOrNull?.size.byteFormat,
},
videoStreams: videoStreams
.map(
(e) => {
"Title": e.displayTitle,
"Codec": e.codec,
"Profile": e.profile,
"Level": e.level,
"Resolution": "${e.width}x${e.height}",
"Aspect Ration": e.aspectRatio,
"Interlaced": e.isInterlaced,
"FrameRate": e.realFrameRate,
"Bitrate": "${e.bitRate} kbps",
"Bit depth": e.bitDepth,
"Video range": e.videoRange,
"Video range type": e.videoRangeType,
"Ref frames": e.refFrames,
},
)
.toList(),
audioStreams: audioStreams
.map(
(e) => {
"Title": e.displayTitle,
"Language": e.language,
"Codec": e.codec,
"Layout": e.channelLayout,
"Bitrate": "${e.bitRate} kbps",
"Sample Rate": "${e.sampleRate} Hz",
"Default": e.isDefault,
"Forced": e.isForced,
"External": e.isExternal,
},
)
.toList(),
subStreams: subStreams
.map(
(e) => {
"Title": e.displayTitle,
"Language": e.language,
"Codec": e.codec,
"Profile": e.profile,
"Default": e.isDefault,
"Forced": e.isForced,
"External": e.isExternal,
},
)
.toList(),
);
}
InformationModel copyWith({
Map<String, dynamic>? baseInformation,
List<Map<String, dynamic>>? videoStreams,
List<Map<String, dynamic>>? audioStreams,
List<Map<String, dynamic>>? subStreams,
}) {
return InformationModel(
baseInformation: baseInformation ?? this.baseInformation,
videoStreams: videoStreams ?? this.videoStreams,
audioStreams: audioStreams ?? this.audioStreams,
subStreams: subStreams ?? this.subStreams,
);
}
static String mapToString(Map<String, dynamic> map) {
return map.entries.map((e) => "${e.key}: ${e.value}").join("\n");
}
static String streamsToString(List<Map<String, dynamic>> streams) {
return streams.map((e) => mapToString(e)).join("\n");
}
@override
String toString() => "${mapToString(baseInformation)}\n\n"
"${streamsToString(videoStreams)}\n\n"
"${streamsToString(audioStreams)}\n\n"
"${streamsToString(subStreams)}\n\n";
}

View file

@ -0,0 +1,363 @@
import 'package:dart_mappable/dart_mappable.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/book_model.dart';
import 'package:fladder/models/boxset_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/playlist_model.dart';
import 'package:fladder/routes/build_routes/home_routes.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/details_screens/book_detail_screen.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/folder_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/movie_model.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/models/items/person_model.dart';
import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/models/items/season_model.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:fladder/screens/details_screens/details_screens.dart';
import 'package:fladder/screens/details_screens/episode_detail_screen.dart';
import 'package:fladder/screens/details_screens/season_detail_screen.dart';
import 'package:fladder/screens/library_search/library_search_screen.dart';
part 'item_base_model.mapper.dart';
@MappableClass()
class ItemBaseModel with ItemBaseModelMappable {
final String name;
final String id;
final OverviewModel overview;
final String? parentId;
final String? playlistId;
final ImagesData? images;
final int? childCount;
final double? primaryRatio;
final UserData userData;
final bool? canDownload;
final bool? canDelete;
final dto.BaseItemKind? jellyType;
const ItemBaseModel({
required this.name,
required this.id,
required this.overview,
required this.parentId,
required this.playlistId,
required this.images,
required this.childCount,
required this.primaryRatio,
required this.userData,
required this.canDownload,
required this.canDelete,
required this.jellyType,
});
String get title => name;
ItemBaseModel? setProgress(double progress) {
return copyWith(userData: userData.copyWith(progress: progress));
}
Widget? subTitle(SortingOptions options) => switch (options) {
SortingOptions.parentalRating => overview.parentalRating != null
? Row(
children: [
Icon(
IconsaxBold.star_1,
size: 14,
color: Colors.yellowAccent,
),
const SizedBox(width: 6),
Text((overview.parentalRating ?? 0.0).toString())
],
)
: null,
SortingOptions.communityRating => overview.communityRating != null
? Row(
children: [
Icon(
IconsaxBold.star_1,
size: 14,
color: Colors.yellowAccent,
),
const SizedBox(width: 6),
Text((overview.communityRating ?? 0.0).toString())
],
)
: null,
_ => null,
};
///Used for retrieving the correct id when fetching queue
String get streamId => id;
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
bool get emptyShow => false;
bool get identifiable => false;
int? get unPlayedItemCount => userData.unPlayedItemCount;
bool get unWatched => !userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0;
String? detailedName(BuildContext context) => null;
String? get subText => null;
String? subTextShort(BuildContext context) => null;
String? label(BuildContext context) => null;
ImagesData? get getPosters => images;
ImageData? get bannerImage => images?.primary ?? getPosters?.randomBackDrop ?? getPosters?.primary;
bool get playAble => false;
bool get syncAble => false;
bool get galleryItem => false;
MediaStreamsModel? get streamModel => null;
String playText(BuildContext context) => context.localized.play(name);
double get progress => userData.progress;
String playButtonLabel(BuildContext context) =>
progress != 0 ? context.localized.resume(name.maxLength()) : context.localized.play(name.maxLength());
Widget get detailScreenWidget {
switch (this) {
case PersonModel _:
return PersonDetailScreen(person: Person(id: id, image: images?.primary));
case SeasonModel _:
return SeasonDetailScreen(item: this);
case FolderModel _:
case PhotoAlbumModel _:
case BoxSetModel _:
case PlaylistModel _:
return LibrarySearchScreen(folderId: [id]);
case PhotoModel _:
final photo = this as PhotoModel;
return LibrarySearchScreen(
folderId: [photo.albumId ?? photo.parentId ?? ""],
photoToView: photo,
);
case BookModel book:
return BookDetailScreen(item: book);
case MovieModel _:
return MovieDetailScreen(item: this);
case EpisodeModel _:
return EpisodeDetailScreen(item: this);
case SeriesModel series:
return SeriesDetailScreen(item: series);
default:
return EmptyItem(item: this);
}
}
Future<void> navigateTo(BuildContext context) async => context.routePush(DetailsRoute(id: id), extra: this);
factory ItemBaseModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return switch (item.type) {
BaseItemKind.photo || BaseItemKind.video => PhotoModel.fromBaseDto(item, ref),
BaseItemKind.photoalbum => PhotoAlbumModel.fromBaseDto(item, ref),
BaseItemKind.folder ||
BaseItemKind.collectionfolder ||
BaseItemKind.aggregatefolder =>
FolderModel.fromBaseDto(item, ref),
BaseItemKind.episode => EpisodeModel.fromBaseDto(item, ref),
BaseItemKind.movie => MovieModel.fromBaseDto(item, ref),
BaseItemKind.series => SeriesModel.fromBaseDto(item, ref),
BaseItemKind.person => PersonModel.fromBaseDto(item, ref),
BaseItemKind.season => SeasonModel.fromBaseDto(item, ref),
BaseItemKind.boxset => BoxSetModel.fromBaseDto(item, ref),
BaseItemKind.book => BookModel.fromBaseDto(item, ref),
BaseItemKind.playlist => PlaylistModel.fromBaseDto(item, ref),
_ => ItemBaseModel._fromBaseDto(item, ref)
};
}
factory ItemBaseModel._fromBaseDto(dto.BaseItemDto item, Ref ref) {
return ItemBaseModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
canDelete: item.canDelete,
canDownload: item.canDownload,
jellyType: item.type,
);
}
FladderItemType get type => switch (this) {
MovieModel _ => FladderItemType.movie,
SeriesModel _ => FladderItemType.series,
SeasonModel _ => FladderItemType.season,
PhotoAlbumModel _ => FladderItemType.photoalbum,
PhotoModel model => model.internalType,
EpisodeModel _ => FladderItemType.episode,
BookModel _ => FladderItemType.book,
PlaylistModel _ => FladderItemType.playlist,
FolderModel _ => FladderItemType.folder,
ItemBaseModel _ => FladderItemType.baseType,
};
@override
bool operator ==(covariant ItemBaseModel other) {
if (identical(this, other)) return true;
return other.id == id;
}
@override
int get hashCode {
return id.hashCode ^ type.hashCode;
}
}
// Currently supported types
enum FladderItemType {
baseType(
icon: IconsaxOutline.folder_2,
selectedicon: IconsaxBold.folder_2,
),
audio(
icon: IconsaxOutline.music,
selectedicon: IconsaxBold.music,
),
musicAlbum(
icon: IconsaxOutline.music,
selectedicon: IconsaxBold.music,
),
musicVideo(
icon: IconsaxOutline.music,
selectedicon: IconsaxBold.music,
),
collectionFolder(
icon: IconsaxOutline.music,
selectedicon: IconsaxBold.music,
),
video(
icon: IconsaxOutline.video,
selectedicon: IconsaxBold.video,
),
movie(
icon: IconsaxOutline.video_horizontal,
selectedicon: IconsaxBold.video_horizontal,
),
series(
icon: IconsaxOutline.video_vertical,
selectedicon: IconsaxBold.video_vertical,
),
season(
icon: IconsaxOutline.video_vertical,
selectedicon: IconsaxBold.video_vertical,
),
episode(
icon: IconsaxOutline.video_vertical,
selectedicon: IconsaxBold.video_vertical,
),
photo(
icon: IconsaxOutline.picture_frame,
selectedicon: IconsaxBold.picture_frame,
),
person(
icon: IconsaxOutline.user,
selectedicon: IconsaxBold.user,
),
photoalbum(
icon: IconsaxOutline.gallery,
selectedicon: IconsaxBold.gallery,
),
folder(
icon: IconsaxOutline.folder,
selectedicon: IconsaxBold.folder,
),
boxset(
icon: IconsaxOutline.bookmark,
selectedicon: IconsaxBold.bookmark,
),
playlist(
icon: IconsaxOutline.archive_book,
selectedicon: IconsaxBold.archive_book,
),
book(
icon: IconsaxOutline.book,
selectedicon: IconsaxBold.book,
);
const FladderItemType({required this.icon, required this.selectedicon});
static Set<FladderItemType> get playable => {
FladderItemType.series,
FladderItemType.episode,
FladderItemType.season,
FladderItemType.movie,
FladderItemType.musicVideo,
};
static Set<FladderItemType> get galleryItem => {
FladderItemType.photo,
FladderItemType.video,
};
String label(BuildContext context) {
return switch (this) {
FladderItemType.baseType => context.localized.mediaTypeBase,
FladderItemType.audio => context.localized.audio,
FladderItemType.collectionFolder => context.localized.collectionFolder,
FladderItemType.musicAlbum => context.localized.musicAlbum,
FladderItemType.musicVideo => context.localized.video,
FladderItemType.video => context.localized.video,
FladderItemType.movie => context.localized.mediaTypeMovie,
FladderItemType.series => context.localized.mediaTypeSeries,
FladderItemType.season => context.localized.mediaTypeSeason,
FladderItemType.episode => context.localized.mediaTypeEpisode,
FladderItemType.photo => context.localized.mediaTypePhoto,
FladderItemType.person => context.localized.mediaTypePerson,
FladderItemType.photoalbum => context.localized.mediaTypePhotoAlbum,
FladderItemType.folder => context.localized.mediaTypeFolder,
FladderItemType.boxset => context.localized.mediaTypeBoxset,
FladderItemType.playlist => context.localized.mediaTypePlaylist,
FladderItemType.book => context.localized.mediaTypeBook,
};
}
BaseItemKind get dtoKind => switch (this) {
FladderItemType.baseType => BaseItemKind.userrootfolder,
FladderItemType.audio => BaseItemKind.audio,
FladderItemType.collectionFolder => BaseItemKind.collectionfolder,
FladderItemType.musicAlbum => BaseItemKind.musicalbum,
FladderItemType.musicVideo => BaseItemKind.video,
FladderItemType.video => BaseItemKind.video,
FladderItemType.movie => BaseItemKind.movie,
FladderItemType.series => BaseItemKind.series,
FladderItemType.season => BaseItemKind.season,
FladderItemType.episode => BaseItemKind.episode,
FladderItemType.photo => BaseItemKind.photo,
FladderItemType.person => BaseItemKind.person,
FladderItemType.photoalbum => BaseItemKind.photoalbum,
FladderItemType.folder => BaseItemKind.folder,
FladderItemType.boxset => BaseItemKind.boxset,
FladderItemType.playlist => BaseItemKind.playlist,
FladderItemType.book => BaseItemKind.book,
};
final IconData icon;
final IconData selectedicon;
}

View file

@ -0,0 +1,214 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'item_base_model.dart';
class ItemBaseModelMapper extends ClassMapperBase<ItemBaseModel> {
ItemBaseModelMapper._();
static ItemBaseModelMapper? _instance;
static ItemBaseModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = ItemBaseModelMapper._());
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'ItemBaseModel';
static String _$name(ItemBaseModel v) => v.name;
static const Field<ItemBaseModel, String> _f$name = Field('name', _$name);
static String _$id(ItemBaseModel v) => v.id;
static const Field<ItemBaseModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(ItemBaseModel v) => v.overview;
static const Field<ItemBaseModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(ItemBaseModel v) => v.parentId;
static const Field<ItemBaseModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(ItemBaseModel v) => v.playlistId;
static const Field<ItemBaseModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(ItemBaseModel v) => v.images;
static const Field<ItemBaseModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(ItemBaseModel v) => v.childCount;
static const Field<ItemBaseModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(ItemBaseModel v) => v.primaryRatio;
static const Field<ItemBaseModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(ItemBaseModel v) => v.userData;
static const Field<ItemBaseModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDownload(ItemBaseModel v) => v.canDownload;
static const Field<ItemBaseModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static bool? _$canDelete(ItemBaseModel v) => v.canDelete;
static const Field<ItemBaseModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static dto.BaseItemKind? _$jellyType(ItemBaseModel v) => v.jellyType;
static const Field<ItemBaseModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType);
@override
final MappableFields<ItemBaseModel> fields = const {
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
static ItemBaseModel _instantiate(DecodingData data) {
return ItemBaseModel(
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static ItemBaseModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<ItemBaseModel>(map);
}
static ItemBaseModel fromJson(String json) {
return ensureInitialized().decodeJson<ItemBaseModel>(json);
}
}
mixin ItemBaseModelMappable {
String toJson() {
return ItemBaseModelMapper.ensureInitialized()
.encodeJson<ItemBaseModel>(this as ItemBaseModel);
}
Map<String, dynamic> toMap() {
return ItemBaseModelMapper.ensureInitialized()
.encodeMap<ItemBaseModel>(this as ItemBaseModel);
}
ItemBaseModelCopyWith<ItemBaseModel, ItemBaseModel, ItemBaseModel>
get copyWith => _ItemBaseModelCopyWithImpl(
this as ItemBaseModel, $identity, $identity);
@override
String toString() {
return ItemBaseModelMapper.ensureInitialized()
.stringifyValue(this as ItemBaseModel);
}
}
extension ItemBaseModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, ItemBaseModel, $Out> {
ItemBaseModelCopyWith<$R, ItemBaseModel, $Out> get $asItemBaseModel =>
$base.as((v, t, t2) => _ItemBaseModelCopyWithImpl(v, t, t2));
}
abstract class ItemBaseModelCopyWith<$R, $In extends ItemBaseModel, $Out>
implements ClassCopyWith<$R, $In, $Out> {
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
UserDataCopyWith<$R, UserData, UserData> get userData;
$R call(
{String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDownload,
bool? canDelete,
dto.BaseItemKind? jellyType});
ItemBaseModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _ItemBaseModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, ItemBaseModel, $Out>
implements ItemBaseModelCopyWith<$R, ItemBaseModel, $Out> {
_ItemBaseModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<ItemBaseModel> $mapper =
ItemBaseModelMapper.ensureInitialized();
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
ItemBaseModel $make(CopyWithData data) => ItemBaseModel(
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
ItemBaseModelCopyWith<$R2, ItemBaseModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_ItemBaseModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,318 @@
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jelly;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/providers/image_provider.dart';
class EditItemsProvider {
final List<EditingImageModel> serverImages;
final List<EditingImageModel> images;
final List<EditingImageModel> customImages;
final EditingImageModel? selected;
final List<EditingImageModel> selection;
const EditItemsProvider({
this.serverImages = const [],
this.images = const [],
this.customImages = const [],
this.selected,
this.selection = const [],
});
Future<void> setImage(
jelly.ImageType type, {
required Function(EditingImageModel? imageModel) uploadData,
required Function(EditingImageModel? imageModel) uploadUrl,
}) async {
switch (type) {
case jelly.ImageType.primary:
case jelly.ImageType.logo:
{
if (selected == null) return;
if (selected?.imageData != null) {
await uploadData(selected!.copyWith(type: type));
} else if (selected?.url != null) {
await uploadUrl(selected!.copyWith(type: type));
}
}
case jelly.ImageType.backdrop:
{
for (var element in selection) {
if (element.imageData != null) {
await uploadData(element.copyWith(type: type));
} else if (element.url != null) {
await uploadUrl(element.copyWith(type: type));
}
}
}
default:
}
return;
}
EditItemsProvider copyWith({
List<EditingImageModel>? serverImages,
List<EditingImageModel>? images,
List<EditingImageModel>? customImages,
ValueGetter<EditingImageModel?>? selected,
List<EditingImageModel>? selection,
}) {
return EditItemsProvider(
serverImages: serverImages ?? this.serverImages,
images: images ?? this.images,
customImages: customImages ?? this.customImages,
selected: selected != null ? selected() : this.selected,
selection: selection ?? this.selection,
);
}
}
class ItemEditingModel {
final ItemBaseModel? item;
final jelly.MetadataEditorInfo? editorInfo;
final Map<String, dynamic>? json;
final Map<String, dynamic>? editedJson;
final bool includeAllImages;
final EditItemsProvider primary;
final EditItemsProvider logo;
final EditItemsProvider backdrop;
final bool saving;
ItemEditingModel({
this.item,
this.editorInfo,
this.json,
this.editedJson,
this.includeAllImages = false,
this.primary = const EditItemsProvider(),
this.logo = const EditItemsProvider(),
this.backdrop = const EditItemsProvider(),
this.saving = false,
});
Map<String, dynamic>? editAbleFields() {
return editedJson == null
? {}
: {
"Name": editedJson?["Name"] as String?,
"OriginalTitle": editedJson?["OriginalTitle"] as String?,
"PremiereDate": editedJson?["PremiereDate"] != null ? DateTime.tryParse(editedJson!["PremiereDate"]) : null,
"DateCreated": editedJson?["DateCreated"] != null ? DateTime.tryParse(editedJson!["DateCreated"]) : null,
"ProductionYear": editedJson?["ProductionYear"] as int?,
"Path": editedJson?["Path"] as String?,
"Overview": editedJson?["Overview"] as String? ?? "",
}
..removeWhere((key, value) => value == null);
}
Map<String, dynamic>? editAdvancedAbleFields(Ref ref) => editedJson == null
? {}
: {
if (item is SeriesModel) "DisplayOrder": DisplayOrder.fromMap(editedJson?["DisplayOrder"]),
if (item is SeriesModel) ...{
"OfficialRating": {
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
?..add(json?["OfficialRating"] as String?))
?.whereNotNull()
.toList() ??
[])
element: (editedJson?["OfficialRating"] as String?) == element
},
"CustomRating": {
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
?..add(json?["CustomRating"] as String?))
?.whereNotNull()
.toList() ??
[])
element: (editedJson?["CustomRating"] as String?) == element
},
},
"People": editedJson?["People"] != null
? (editedJson!["People"] as List<dynamic>)
.map((e) => Person.fromBasePerson(jelly.BaseItemPerson.fromJson(e), ref))
.toList()
: null,
"ExternalUrls": editedJson?["ExternalUrls"] != null
? (editedJson!["ExternalUrls"] as List<dynamic>).map((e) => ExternalUrls.fromMap(e)).toList()
: null,
"CommunityRating": double.tryParse((editedJson?["CommunityRating"] as num?).toString()),
"SeriesName": editedJson?["SeriesName"] as String?,
"IndexNumber": editedJson?["IndexNumber"] as int?,
"RunTimeTicks": (editedJson?["RunTimeTicks"] == null)
? null
: Duration(milliseconds: editedJson?["RunTimeTicks"] ~/ 10000),
"ParentIndexNumber": editedJson?["ParentIndexNumber"] as int?,
if (item is SeriesModel) "Status": ShowStatus.fromMap(editedJson?["Status"] as String?),
"Genres": editedJson?["Genres"] != null ? (List<String>.from(editedJson!["Genres"])) : null,
"Tags": editedJson?["Tags"] != null ? (List<String>.from(editedJson?["Tags"])) : null,
"Studios": editedJson?["Studios"] != null
? (editedJson!["Studios"] as List<dynamic>).map((e) => Studio.fromMap(e)).toList()
: null,
"SeriesStudio": editedJson?["SeriesStudio"] as String?,
"LockData": editedJson?["LockData"] as bool? ?? false,
"LockedFields": ((editedJson?["LockData"] as bool?) == false)
? EditorLockedFields.enabled(List<String>.from(editedJson?["LockedFields"]))
: null,
}
..removeWhere((key, value) => value == null);
ItemEditingModel copyWith({
ValueGetter<ItemBaseModel?>? item,
ValueGetter<jelly.MetadataEditorInfo?>? editorInfo,
ValueGetter<Map<String, dynamic>?>? json,
ValueGetter<Map<String, dynamic>?>? editedJson,
bool? includeAllImages,
EditItemsProvider? primary,
EditItemsProvider? logo,
EditItemsProvider? backdrop,
bool? saving,
}) {
return ItemEditingModel(
item: item != null ? item() : this.item,
editorInfo: editorInfo != null ? editorInfo() : this.editorInfo,
json: json != null ? json() : this.json,
editedJson: editedJson != null ? editedJson() : this.editedJson,
includeAllImages: includeAllImages ?? this.includeAllImages,
primary: primary ?? this.primary,
logo: logo ?? this.logo,
backdrop: backdrop ?? this.backdrop,
saving: saving ?? this.saving,
);
}
}
class EditingImageModel {
final String providerName;
final String? url;
final Uint8List? imageData;
final int? index;
final int height;
final int width;
final double communityRating;
final int voteCount;
final String language;
final jelly.ImageType type;
final jelly.RatingType ratingType;
EditingImageModel({
required this.providerName,
this.url,
this.imageData,
this.index,
this.height = 0,
this.width = 0,
this.communityRating = 0.0,
this.voteCount = 0,
this.language = "",
this.type = jelly.ImageType.primary,
this.ratingType = jelly.RatingType.likes,
});
double get ratio {
if (width == 0 && height == 0) return 1;
final ratio = (width.toDouble() / height.toDouble()).clamp(0.1, 5).toDouble();
if (ratio < 0) {
return 1;
} else {
return ratio;
}
}
factory EditingImageModel.fromDto(jelly.RemoteImageInfo info) {
return EditingImageModel(
providerName: info.providerName ?? "",
url: info.url ?? "",
height: info.height ?? 0,
width: info.width ?? 0,
communityRating: info.communityRating ?? 0.0,
voteCount: info.voteCount ?? 0,
language: info.language ?? "",
type: info.type ?? jelly.ImageType.primary,
ratingType: info.ratingType ?? jelly.RatingType.likes,
);
}
factory EditingImageModel.fromImage(jelly.ImageInfo info, String itemId, Ref ref) {
return EditingImageModel(
providerName: "",
url: switch (info.imageType ?? ImageType.primary) {
ImageType.backdrop => ref.read(imageUtilityProvider).getBackdropOrigImage(
itemId,
info.imageIndex ?? 0,
info.hashCode.toString(),
),
_ => ref.read(imageUtilityProvider).getItemsOrigImageUrl(
itemId,
type: info.imageType ?? ImageType.primary,
),
},
index: info.imageIndex,
height: info.height ?? 0,
width: info.width ?? 0,
type: info.imageType ?? ImageType.primary,
);
}
EditingImageModel copyWith({
String? providerName,
ValueGetter<String?>? url,
ValueGetter<Uint8List?>? imageData,
ValueGetter<int?>? index,
int? height,
int? width,
double? communityRating,
int? voteCount,
String? language,
jelly.ImageType? type,
jelly.RatingType? ratingType,
}) {
return EditingImageModel(
providerName: providerName ?? this.providerName,
url: url != null ? url() : this.url,
imageData: imageData != null ? imageData() : this.imageData,
index: index != null ? index() : this.index,
height: height ?? this.height,
width: width ?? this.width,
communityRating: communityRating ?? this.communityRating,
voteCount: voteCount ?? this.voteCount,
language: language ?? this.language,
type: type ?? this.type,
ratingType: ratingType ?? this.ratingType,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is EditingImageModel &&
other.providerName == providerName &&
other.url == url &&
other.imageData == imageData &&
other.height == height &&
other.width == width &&
other.communityRating == communityRating &&
other.voteCount == voteCount &&
other.language == language &&
other.type == type &&
other.ratingType == ratingType;
}
@override
int get hashCode {
return providerName.hashCode ^
url.hashCode ^
imageData.hashCode ^
height.hashCode ^
width.hashCode ^
communityRating.hashCode ^
voteCount.hashCode ^
language.hashCode ^
type.hashCode ^
ratingType.hashCode;
}
}

View file

@ -0,0 +1,90 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:fladder/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/providers/image_provider.dart';
class Chapter {
final String name;
final String imageUrl;
final Uint8List? imageData;
final Duration startPosition;
Chapter({
required this.name,
required this.imageUrl,
this.imageData,
required this.startPosition,
});
ImageProvider get imageProvider {
if (imageData != null) {
return Image.memory(imageData!).image;
}
if (imageUrl.startsWith("http")) {
return CachedNetworkImageProvider(
cacheKey: name + imageUrl,
cacheManager: CustomCacheManager.instance,
imageUrl,
);
} else {
return Image.file(
key: Key(name + imageUrl),
File(imageUrl),
).image;
}
}
static List<Chapter> chaptersFromInfo(String itemId, List<dto.ChapterInfo> chapters, Ref ref) {
return chapters
.mapIndexed((index, element) => Chapter(
name: element.name ?? "",
imageUrl: ref.read(imageUtilityProvider).getChapterUrl(itemId, index),
startPosition: Duration(milliseconds: (element.startPositionTicks ?? 0) ~/ 10000)))
.toList();
}
Chapter copyWith({
String? name,
String? imageUrl,
Duration? startPosition,
}) {
return Chapter(
name: name ?? this.name,
imageUrl: imageUrl ?? this.imageUrl,
startPosition: startPosition ?? this.startPosition,
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'imageUrl': imageUrl,
'startPosition': startPosition.inMilliseconds,
};
}
factory Chapter.fromMap(Map<String, dynamic> map) {
return Chapter(
name: map['name'] ?? '',
imageUrl: map['imageUrl'] ?? '',
startPosition: Duration(milliseconds: map['startPosition'] as int),
);
}
String toJson() => json.encode(toMap());
factory Chapter.fromJson(String source) => Chapter.fromMap(json.decode(source));
}
extension ChapterExtension on List<Chapter> {
Chapter? getChapterFromDuration(Duration duration) {
return lastWhereOrNull((element) => element.startPosition < duration);
}
}

View file

@ -0,0 +1,201 @@
import 'package:collection/collection.dart';
import 'package:fladder/jellyfin/enum_models.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/item_stream_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'episode_model.mapper.dart';
enum EpisodeStatus { available, unaired, missing }
@MappableClass()
class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
final String? seriesName;
final int season;
final int episode;
final List<Chapter> chapters;
final ItemLocation? location;
final DateTime? dateAired;
const EpisodeModel({
required this.seriesName,
required this.season,
required this.episode,
this.chapters = const [],
this.location,
this.dateAired,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.parentImages,
required super.mediaStreams,
super.canDelete,
super.canDownload,
super.jellyType,
});
EpisodeStatus get status {
return switch (location) {
ItemLocation.filesystem => EpisodeStatus.available,
ItemLocation.virtual =>
(dateAired?.isBefore(DateTime.now()) == true) ? EpisodeStatus.missing : EpisodeStatus.unaired,
_ => EpisodeStatus.missing
};
}
@override
String? detailedName(BuildContext context) => "${subTextShort(context)} - $name";
@override
SeriesModel get parentBaseModel => SeriesModel(
originalTitle: '',
sortName: '',
status: "",
name: seriesName ?? "",
id: parentId ?? "",
playlistId: playlistId,
overview: overview,
parentId: parentId,
images: images,
childCount: childCount,
primaryRatio: primaryRatio,
userData: UserData(),
);
@override
String get streamId => parentId ?? "";
@override
String get title => seriesName ?? name;
@override
MediaStreamsModel? get streamModel => mediaStreams;
@override
ImagesData? get getPosters => parentImages;
@override
String? get subText => name.isEmpty ? "TBA" : name;
@override
String? subTextShort(BuildContext context) => seasonEpisodeLabel(context);
@override
String? label(BuildContext context) => "${subTextShort(context)} - $name";
@override
bool get playAble => switch (status) {
EpisodeStatus.available => true,
_ => false,
};
@override
String playButtonLabel(BuildContext context) {
final string = seasonEpisodeLabel(context).maxLength();
return progress != 0 ? context.localized.resume(string) : context.localized.play(string);
}
String seasonAnnotation(BuildContext context) => context.localized.season(1)[0];
String episodeAnnotation(BuildContext context) => context.localized.episode(1)[0];
String seasonEpisodeLabel(BuildContext context) {
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episode";
}
String seasonEpisodeLabelFull(BuildContext context) {
return "${context.localized.season(1)} $season - ${context.localized.episode(1)} $episode";
}
String episodeLabel(BuildContext context) {
return "${seasonEpisodeLabel(context)} - $subText";
}
String get fullName {
return "$episode. $subText";
}
@override
bool get syncAble => playAble;
@override
factory EpisodeModel.fromBaseDto(dto.BaseItemDto item, Ref ref) => EpisodeModel(
seriesName: item.seriesName,
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.seriesId,
playlistId: item.playlistItemId,
dateAired: item.premiereDate,
chapters: Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref),
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
primaryRatio: item.primaryImageAspectRatio,
season: item.parentIndexNumber ?? 0,
episode: item.indexNumber ?? 0,
location: ItemLocation.fromDto(item.locationType),
parentImages: ImagesData.fromBaseItemParent(item, ref),
canDelete: item.canDelete,
canDownload: item.canDownload,
mediaStreams:
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
jellyType: item.type,
);
static List<EpisodeModel> episodesFromDto(List<dto.BaseItemDto>? dto, Ref ref) {
return dto?.map((e) => EpisodeModel.fromBaseDto(e, ref)).toList() ?? [];
}
}
extension EpisodeListExtensions on List<EpisodeModel> {
Map<int, List<EpisodeModel>> get episodesBySeason {
Map<int, List<EpisodeModel>> groupedItems = {};
for (int i = 0; i < length; i++) {
int seasonIndex = this[i].season;
if (!groupedItems.containsKey(seasonIndex)) {
groupedItems[seasonIndex] = [this[i]];
} else {
groupedItems[seasonIndex]?.add(this[i]);
}
}
return groupedItems;
}
EpisodeModel? get nextUp {
final lastProgress =
lastIndexWhere((element) => element.userData.progress != 0 && element.status == EpisodeStatus.available);
final lastPlayed =
lastIndexWhere((element) => element.userData.played && element.status == EpisodeStatus.available);
if (lastProgress == -1 && lastPlayed == -1) {
return firstWhereOrNull((element) => element.status == EpisodeStatus.available);
} else {
return getRange(lastProgress > lastPlayed ? lastProgress : lastPlayed + 1, length)
.firstWhereOrNull((element) => element.status == EpisodeStatus.available);
}
}
bool get allPlayed {
for (var element in this) {
if (!element.userData.played) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,301 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'episode_model.dart';
class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
EpisodeModelMapper._();
static EpisodeModelMapper? _instance;
static EpisodeModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = EpisodeModelMapper._());
ItemStreamModelMapper.ensureInitialized().addSubMapper(_instance!);
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'EpisodeModel';
static String? _$seriesName(EpisodeModel v) => v.seriesName;
static const Field<EpisodeModel, String> _f$seriesName =
Field('seriesName', _$seriesName);
static int _$season(EpisodeModel v) => v.season;
static const Field<EpisodeModel, int> _f$season = Field('season', _$season);
static int _$episode(EpisodeModel v) => v.episode;
static const Field<EpisodeModel, int> _f$episode =
Field('episode', _$episode);
static List<Chapter> _$chapters(EpisodeModel v) => v.chapters;
static const Field<EpisodeModel, List<Chapter>> _f$chapters =
Field('chapters', _$chapters, opt: true, def: const []);
static ItemLocation? _$location(EpisodeModel v) => v.location;
static const Field<EpisodeModel, ItemLocation> _f$location =
Field('location', _$location, opt: true);
static DateTime? _$dateAired(EpisodeModel v) => v.dateAired;
static const Field<EpisodeModel, DateTime> _f$dateAired =
Field('dateAired', _$dateAired, opt: true);
static String _$name(EpisodeModel v) => v.name;
static const Field<EpisodeModel, String> _f$name = Field('name', _$name);
static String _$id(EpisodeModel v) => v.id;
static const Field<EpisodeModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(EpisodeModel v) => v.overview;
static const Field<EpisodeModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(EpisodeModel v) => v.parentId;
static const Field<EpisodeModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(EpisodeModel v) => v.playlistId;
static const Field<EpisodeModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(EpisodeModel v) => v.images;
static const Field<EpisodeModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(EpisodeModel v) => v.childCount;
static const Field<EpisodeModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(EpisodeModel v) => v.primaryRatio;
static const Field<EpisodeModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(EpisodeModel v) => v.userData;
static const Field<EpisodeModel, UserData> _f$userData =
Field('userData', _$userData);
static ImagesData? _$parentImages(EpisodeModel v) => v.parentImages;
static const Field<EpisodeModel, ImagesData> _f$parentImages =
Field('parentImages', _$parentImages);
static MediaStreamsModel _$mediaStreams(EpisodeModel v) => v.mediaStreams;
static const Field<EpisodeModel, MediaStreamsModel> _f$mediaStreams =
Field('mediaStreams', _$mediaStreams);
static bool? _$canDelete(EpisodeModel v) => v.canDelete;
static const Field<EpisodeModel, bool> _f$canDelete =
Field('canDelete', _$canDelete, opt: true);
static bool? _$canDownload(EpisodeModel v) => v.canDownload;
static const Field<EpisodeModel, bool> _f$canDownload =
Field('canDownload', _$canDownload, opt: true);
static dto.BaseItemKind? _$jellyType(EpisodeModel v) => v.jellyType;
static const Field<EpisodeModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<EpisodeModel> fields = const {
#seriesName: _f$seriesName,
#season: _f$season,
#episode: _f$episode,
#chapters: _f$chapters,
#location: _f$location,
#dateAired: _f$dateAired,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#parentImages: _f$parentImages,
#mediaStreams: _f$mediaStreams,
#canDelete: _f$canDelete,
#canDownload: _f$canDownload,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'EpisodeModel';
@override
late final ClassMapperBase superMapper =
ItemStreamModelMapper.ensureInitialized();
static EpisodeModel _instantiate(DecodingData data) {
return EpisodeModel(
seriesName: data.dec(_f$seriesName),
season: data.dec(_f$season),
episode: data.dec(_f$episode),
chapters: data.dec(_f$chapters),
location: data.dec(_f$location),
dateAired: data.dec(_f$dateAired),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
parentImages: data.dec(_f$parentImages),
mediaStreams: data.dec(_f$mediaStreams),
canDelete: data.dec(_f$canDelete),
canDownload: data.dec(_f$canDownload),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static EpisodeModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<EpisodeModel>(map);
}
static EpisodeModel fromJson(String json) {
return ensureInitialized().decodeJson<EpisodeModel>(json);
}
}
mixin EpisodeModelMappable {
String toJson() {
return EpisodeModelMapper.ensureInitialized()
.encodeJson<EpisodeModel>(this as EpisodeModel);
}
Map<String, dynamic> toMap() {
return EpisodeModelMapper.ensureInitialized()
.encodeMap<EpisodeModel>(this as EpisodeModel);
}
EpisodeModelCopyWith<EpisodeModel, EpisodeModel, EpisodeModel> get copyWith =>
_EpisodeModelCopyWithImpl(this as EpisodeModel, $identity, $identity);
@override
String toString() {
return EpisodeModelMapper.ensureInitialized()
.stringifyValue(this as EpisodeModel);
}
}
extension EpisodeModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, EpisodeModel, $Out> {
EpisodeModelCopyWith<$R, EpisodeModel, $Out> get $asEpisodeModel =>
$base.as((v, t, t2) => _EpisodeModelCopyWithImpl(v, t, t2));
}
abstract class EpisodeModelCopyWith<$R, $In extends EpisodeModel, $Out>
implements ItemStreamModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>> get chapters;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{String? seriesName,
int? season,
int? episode,
List<Chapter>? chapters,
ItemLocation? location,
DateTime? dateAired,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
ImagesData? parentImages,
MediaStreamsModel? mediaStreams,
bool? canDelete,
bool? canDownload,
dto.BaseItemKind? jellyType});
EpisodeModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _EpisodeModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, EpisodeModel, $Out>
implements EpisodeModelCopyWith<$R, EpisodeModel, $Out> {
_EpisodeModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<EpisodeModel> $mapper =
EpisodeModelMapper.ensureInitialized();
@override
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>
get chapters => ListCopyWith($value.chapters,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(chapters: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? seriesName = $none,
int? season,
int? episode,
List<Chapter>? chapters,
Object? location = $none,
Object? dateAired = $none,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? parentImages = $none,
MediaStreamsModel? mediaStreams,
Object? canDelete = $none,
Object? canDownload = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (seriesName != $none) #seriesName: seriesName,
if (season != null) #season: season,
if (episode != null) #episode: episode,
if (chapters != null) #chapters: chapters,
if (location != $none) #location: location,
if (dateAired != $none) #dateAired: dateAired,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (parentImages != $none) #parentImages: parentImages,
if (mediaStreams != null) #mediaStreams: mediaStreams,
if (canDelete != $none) #canDelete: canDelete,
if (canDownload != $none) #canDownload: canDownload,
if (jellyType != $none) #jellyType: jellyType
}));
@override
EpisodeModel $make(CopyWithData data) => EpisodeModel(
seriesName: data.get(#seriesName, or: $value.seriesName),
season: data.get(#season, or: $value.season),
episode: data.get(#episode, or: $value.episode),
chapters: data.get(#chapters, or: $value.chapters),
location: data.get(#location, or: $value.location),
dateAired: data.get(#dateAired, or: $value.dateAired),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
parentImages: data.get(#parentImages, or: $value.parentImages),
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
canDelete: data.get(#canDelete, or: $value.canDelete),
canDownload: data.get(#canDownload, or: $value.canDownload),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
EpisodeModelCopyWith<$R2, EpisodeModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_EpisodeModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,50 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'folder_model.mapper.dart';
@MappableClass()
class FolderModel extends ItemBaseModel with FolderModelMappable {
final List<ItemBaseModel> items;
const FolderModel({
required this.items,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.name,
required super.id,
super.canDownload,
super.canDelete,
super.jellyType,
});
factory FolderModel.fromBaseDto(BaseItemDto item, Ref ref) {
return FolderModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
items: [],
canDelete: item.canDelete,
canDownload: item.canDownload,
);
}
}

View file

@ -0,0 +1,242 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'folder_model.dart';
class FolderModelMapper extends SubClassMapperBase<FolderModel> {
FolderModelMapper._();
static FolderModelMapper? _instance;
static FolderModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = FolderModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
ItemBaseModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'FolderModel';
static List<ItemBaseModel> _$items(FolderModel v) => v.items;
static const Field<FolderModel, List<ItemBaseModel>> _f$items =
Field('items', _$items);
static OverviewModel _$overview(FolderModel v) => v.overview;
static const Field<FolderModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(FolderModel v) => v.parentId;
static const Field<FolderModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(FolderModel v) => v.playlistId;
static const Field<FolderModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(FolderModel v) => v.images;
static const Field<FolderModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(FolderModel v) => v.childCount;
static const Field<FolderModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(FolderModel v) => v.primaryRatio;
static const Field<FolderModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(FolderModel v) => v.userData;
static const Field<FolderModel, UserData> _f$userData =
Field('userData', _$userData);
static String _$name(FolderModel v) => v.name;
static const Field<FolderModel, String> _f$name = Field('name', _$name);
static String _$id(FolderModel v) => v.id;
static const Field<FolderModel, String> _f$id = Field('id', _$id);
static bool? _$canDownload(FolderModel v) => v.canDownload;
static const Field<FolderModel, bool> _f$canDownload =
Field('canDownload', _$canDownload, opt: true);
static bool? _$canDelete(FolderModel v) => v.canDelete;
static const Field<FolderModel, bool> _f$canDelete =
Field('canDelete', _$canDelete, opt: true);
static BaseItemKind? _$jellyType(FolderModel v) => v.jellyType;
static const Field<FolderModel, BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<FolderModel> fields = const {
#items: _f$items,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#name: _f$name,
#id: _f$id,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'FolderModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static FolderModel _instantiate(DecodingData data) {
return FolderModel(
items: data.dec(_f$items),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
name: data.dec(_f$name),
id: data.dec(_f$id),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static FolderModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<FolderModel>(map);
}
static FolderModel fromJson(String json) {
return ensureInitialized().decodeJson<FolderModel>(json);
}
}
mixin FolderModelMappable {
String toJson() {
return FolderModelMapper.ensureInitialized()
.encodeJson<FolderModel>(this as FolderModel);
}
Map<String, dynamic> toMap() {
return FolderModelMapper.ensureInitialized()
.encodeMap<FolderModel>(this as FolderModel);
}
FolderModelCopyWith<FolderModel, FolderModel, FolderModel> get copyWith =>
_FolderModelCopyWithImpl(this as FolderModel, $identity, $identity);
@override
String toString() {
return FolderModelMapper.ensureInitialized()
.stringifyValue(this as FolderModel);
}
}
extension FolderModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, FolderModel, $Out> {
FolderModelCopyWith<$R, FolderModel, $Out> get $asFolderModel =>
$base.as((v, t, t2) => _FolderModelCopyWithImpl(v, t, t2));
}
abstract class FolderModelCopyWith<$R, $In extends FolderModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get items;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{List<ItemBaseModel>? items,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
String? name,
String? id,
bool? canDownload,
bool? canDelete,
BaseItemKind? jellyType});
FolderModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _FolderModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, FolderModel, $Out>
implements FolderModelCopyWith<$R, FolderModel, $Out> {
_FolderModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<FolderModel> $mapper =
FolderModelMapper.ensureInitialized();
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get items => ListCopyWith(
$value.items, (v, t) => v.copyWith.$chain(t), (v) => call(items: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{List<ItemBaseModel>? items,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
String? name,
String? id,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (items != null) #items: items,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (name != null) #name: name,
if (id != null) #id: id,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
FolderModel $make(CopyWithData data) => FolderModel(
items: data.get(#items, or: $value.items),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
FolderModelCopyWith<$R2, FolderModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_FolderModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,286 @@
import 'dart:convert';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:fladder/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart' as enums;
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/providers/image_provider.dart';
import 'package:fladder/util/jelly_id.dart';
class ImagesData {
final ImageData? primary;
final List<ImageData>? backDrop;
final ImageData? logo;
ImagesData({
this.primary,
this.backDrop,
this.logo,
});
bool get isEmpty {
if (primary == null && backDrop == null) return true;
return false;
}
ImageData? get firstOrNull {
return primary ?? backDrop?[0];
}
ImageData? get randomBackDrop => (backDrop?..shuffle())?.firstOrNull ?? primary;
factory ImagesData.fromBaseItem(
dto.BaseItemDto item,
Ref ref, {
Size backDrop = const Size(2000, 2000),
Size logo = const Size(1000, 1000),
Size primary = const Size(600, 600),
bool getOriginalSize = false,
int quality = 90,
}) {
final newImgesData = ImagesData(
primary: item.imageTags?['Primary'] != null
? ImageData(
path: getOriginalSize
? ref.read(imageUtilityProvider).getItemsOrigImageUrl(
item.id!,
type: enums.ImageType.primary,
)
: ref.read(imageUtilityProvider).getItemsImageUrl(
(item.id!),
type: enums.ImageType.primary,
maxHeight: primary.height.toInt(),
maxWidth: primary.width.toInt(),
quality: quality,
),
key: item.imageTags?['Primary'],
hash: item.imageBlurHashes?.primary?[item.imageTags?['Primary']] as String? ?? "",
)
: null,
logo: item.imageTags?['Logo'] != null
? ImageData(
path: getOriginalSize
? ref.read(imageUtilityProvider).getItemsOrigImageUrl(
item.id!,
type: enums.ImageType.logo,
)
: ref.read(imageUtilityProvider).getItemsImageUrl(
(item.id!),
type: enums.ImageType.logo,
maxHeight: logo.height.toInt(),
maxWidth: logo.width.toInt(),
quality: quality,
),
key: item.imageTags?['Logo'],
hash: item.imageBlurHashes?.logo?[item.imageTags?['Logo']] as String? ?? "")
: null,
backDrop: (item.backdropImageTags ?? []).mapIndexed(
(index, backdrop) {
final image = ImageData(
path: getOriginalSize
? ref.read(imageUtilityProvider).getBackdropOrigImage(
item.id!,
index,
backdrop,
)
: ref.read(imageUtilityProvider).getBackdropImage(
(item.id!),
index,
backdrop,
maxHeight: backDrop.height.toInt(),
maxWidth: backDrop.width.toInt(),
quality: quality,
),
key: backdrop,
hash: item.imageBlurHashes?.backdrop?[backdrop] ?? jellyId,
);
return image;
},
).toList(),
);
return newImgesData;
}
static ImagesData? fromBaseItemParent(
dto.BaseItemDto item,
Ref ref, {
Size backDrop = const Size(2000, 2000),
Size logo = const Size(1000, 1000),
Size primary = const Size(600, 600),
int quality = 90,
}) {
if (item.seriesId == null && item.parentId == null) return null;
final newImgesData = ImagesData(
primary: (item.seriesPrimaryImageTag != null)
? ImageData(
path: ref.read(imageUtilityProvider).getItemsImageUrl(
(item.seriesId!),
type: enums.ImageType.primary,
maxHeight: primary.height.toInt(),
maxWidth: primary.width.toInt(),
quality: quality,
),
key: item.seriesPrimaryImageTag ?? "",
hash: item.imageBlurHashes?.primary?[item.seriesPrimaryImageTag] as String? ?? "")
: null,
logo: (item.parentLogoImageTag != null)
? ImageData(
path: ref.read(imageUtilityProvider).getItemsImageUrl(
(item.seriesId!),
type: enums.ImageType.logo,
maxHeight: logo.height.toInt(),
maxWidth: logo.width.toInt(),
quality: quality,
),
key: item.parentLogoImageTag ?? "",
hash: item.imageBlurHashes?.logo?[item.parentLogoImageTag] as String? ?? "")
: null,
backDrop: (item.backdropImageTags ?? []).mapIndexed(
(index, backdrop) {
final image = ImageData(
path: ref.read(imageUtilityProvider).getBackdropImage(
((item.seriesId ?? item.parentId)!),
index,
backdrop,
maxHeight: backDrop.height.toInt(),
maxWidth: backDrop.width.toInt(),
quality: quality,
),
key: backdrop,
hash: item.imageBlurHashes?.backdrop?[backdrop],
);
return image;
},
).toList(),
);
return newImgesData;
}
static ImagesData? fromPersonDto(
dto.BaseItemPerson item,
Ref ref, {
Size backDrop = const Size(2000, 2000),
Size logo = const Size(1000, 1000),
Size primary = const Size(2000, 2000),
int quality = 90,
}) {
return ImagesData(
primary: (item.primaryImageTag != null && item.imageBlurHashes != null)
? ImageData(
path: ref.read(imageUtilityProvider).getItemsImageUrl(
item.id ?? "",
type: enums.ImageType.primary,
maxHeight: primary.height.toInt(),
maxWidth: primary.width.toInt(),
quality: quality,
),
key: item.primaryImageTag ?? "",
hash: item.imageBlurHashes?.primary?[item.primaryImageTag] as String? ?? jellyId)
: null,
logo: null,
backDrop: null,
);
}
@override
String toString() => 'ImagesData(primary: $primary, backDrop: $backDrop, logo: $logo)';
ImagesData copyWith({
ValueGetter<ImageData?>? primary,
ValueGetter<List<ImageData>?>? backDrop,
ValueGetter<ImageData?>? logo,
}) {
return ImagesData(
primary: primary != null ? primary() : this.primary,
backDrop: backDrop != null ? backDrop() : this.backDrop,
logo: logo != null ? logo() : this.logo,
);
}
Map<String, dynamic> toMap() {
return {
'primary': primary?.toMap(),
'backDrop': backDrop?.map((x) => x.toMap()).toList(),
'logo': logo?.toMap(),
};
}
factory ImagesData.fromMap(Map<String, dynamic> map) {
return ImagesData(
primary: map['primary'] != null ? ImageData.fromMap(map['primary']) : null,
backDrop:
map['backDrop'] != null ? List<ImageData>.from(map['backDrop']?.map((x) => ImageData.fromMap(x))) : null,
logo: map['logo'] != null ? ImageData.fromMap(map['logo']) : null,
);
}
String toJson() => json.encode(toMap());
factory ImagesData.fromJson(String source) => ImagesData.fromMap(json.decode(source));
}
class ImageData {
final String path;
final String hash;
final String key;
ImageData({
this.path = '',
this.hash = '',
this.key = '',
});
ImageProvider get imageProvider {
if (path.startsWith("http")) {
return CachedNetworkImageProvider(
cacheKey: key,
cacheManager: CustomCacheManager.instance,
path,
);
} else {
return Image.file(
key: Key(key),
File(path),
).image;
}
}
@override
String toString() => 'ImageData(path: $path, hash: $hash, key: $key)';
ImageData copyWith({
String? path,
String? hash,
String? key,
}) {
return ImageData(
path: path ?? this.path,
hash: hash ?? this.hash,
key: key ?? this.key,
);
}
Map<String, dynamic> toMap() {
return {
'path': path,
'hash': hash,
'key': key,
};
}
factory ImageData.fromMap(Map<String, dynamic> map) {
return ImageData(
path: map['path'] ?? '',
hash: map['hash'] ?? '',
key: map['key'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory ImageData.fromJson(String source) => ImageData.fromMap(json.decode(source));
}

View file

@ -0,0 +1,47 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first, invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
part 'intro_skip_model.freezed.dart';
part 'intro_skip_model.g.dart';
@freezed
class IntroOutSkipModel with _$IntroOutSkipModel {
const IntroOutSkipModel._();
factory IntroOutSkipModel({
IntroSkipModel? intro,
IntroSkipModel? credits,
}) = _IntroOutSkipModel;
factory IntroOutSkipModel.fromJson(Map<String, dynamic> json) => _$IntroOutSkipModelFromJson(json);
bool introInRange(Duration position) {
if (intro == null) return false;
return (position.compareTo(intro!.showTime) >= 0 && position.compareTo(intro!.hideTime) <= 0);
}
bool creditsInRange(Duration position) {
if (credits == null) return false;
return (position.compareTo(credits!.showTime) >= 0 && position.compareTo(credits!.hideTime) <= 0);
}
}
@freezed
class IntroSkipModel with _$IntroSkipModel {
factory IntroSkipModel({
@JsonKey(name: "EpisodeId") required String id,
@JsonKey(name: "Valid") required bool valid,
@JsonKey(name: "IntroStart", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
required Duration start,
@JsonKey(name: "IntroEnd", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
required Duration end,
@JsonKey(name: "ShowSkipPromptAt", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
required Duration showTime,
@JsonKey(name: "HideSkipPromptAt", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
required Duration hideTime,
}) = _IntroSkipModel;
factory IntroSkipModel.fromJson(Map<String, dynamic> json) => _$IntroSkipModelFromJson(json);
}
Duration _durationFromMilliseconds(num milliseconds) => Duration(milliseconds: (milliseconds * 1000).toInt());
num _durationToMilliseconds(Duration duration) => duration.inMilliseconds.toDouble() / 1000.0;

View file

@ -0,0 +1,565 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'intro_skip_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
IntroOutSkipModel _$IntroOutSkipModelFromJson(Map<String, dynamic> json) {
return _IntroOutSkipModel.fromJson(json);
}
/// @nodoc
mixin _$IntroOutSkipModel {
IntroSkipModel? get intro => throw _privateConstructorUsedError;
IntroSkipModel? get credits => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IntroOutSkipModelCopyWith<IntroOutSkipModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IntroOutSkipModelCopyWith<$Res> {
factory $IntroOutSkipModelCopyWith(
IntroOutSkipModel value, $Res Function(IntroOutSkipModel) then) =
_$IntroOutSkipModelCopyWithImpl<$Res, IntroOutSkipModel>;
@useResult
$Res call({IntroSkipModel? intro, IntroSkipModel? credits});
$IntroSkipModelCopyWith<$Res>? get intro;
$IntroSkipModelCopyWith<$Res>? get credits;
}
/// @nodoc
class _$IntroOutSkipModelCopyWithImpl<$Res, $Val extends IntroOutSkipModel>
implements $IntroOutSkipModelCopyWith<$Res> {
_$IntroOutSkipModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? intro = freezed,
Object? credits = freezed,
}) {
return _then(_value.copyWith(
intro: freezed == intro
? _value.intro
: intro // ignore: cast_nullable_to_non_nullable
as IntroSkipModel?,
credits: freezed == credits
? _value.credits
: credits // ignore: cast_nullable_to_non_nullable
as IntroSkipModel?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$IntroSkipModelCopyWith<$Res>? get intro {
if (_value.intro == null) {
return null;
}
return $IntroSkipModelCopyWith<$Res>(_value.intro!, (value) {
return _then(_value.copyWith(intro: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$IntroSkipModelCopyWith<$Res>? get credits {
if (_value.credits == null) {
return null;
}
return $IntroSkipModelCopyWith<$Res>(_value.credits!, (value) {
return _then(_value.copyWith(credits: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$IntroOutSkipModelImplCopyWith<$Res>
implements $IntroOutSkipModelCopyWith<$Res> {
factory _$$IntroOutSkipModelImplCopyWith(_$IntroOutSkipModelImpl value,
$Res Function(_$IntroOutSkipModelImpl) then) =
__$$IntroOutSkipModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({IntroSkipModel? intro, IntroSkipModel? credits});
@override
$IntroSkipModelCopyWith<$Res>? get intro;
@override
$IntroSkipModelCopyWith<$Res>? get credits;
}
/// @nodoc
class __$$IntroOutSkipModelImplCopyWithImpl<$Res>
extends _$IntroOutSkipModelCopyWithImpl<$Res, _$IntroOutSkipModelImpl>
implements _$$IntroOutSkipModelImplCopyWith<$Res> {
__$$IntroOutSkipModelImplCopyWithImpl(_$IntroOutSkipModelImpl _value,
$Res Function(_$IntroOutSkipModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? intro = freezed,
Object? credits = freezed,
}) {
return _then(_$IntroOutSkipModelImpl(
intro: freezed == intro
? _value.intro
: intro // ignore: cast_nullable_to_non_nullable
as IntroSkipModel?,
credits: freezed == credits
? _value.credits
: credits // ignore: cast_nullable_to_non_nullable
as IntroSkipModel?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IntroOutSkipModelImpl extends _IntroOutSkipModel {
_$IntroOutSkipModelImpl({this.intro, this.credits}) : super._();
factory _$IntroOutSkipModelImpl.fromJson(Map<String, dynamic> json) =>
_$$IntroOutSkipModelImplFromJson(json);
@override
final IntroSkipModel? intro;
@override
final IntroSkipModel? credits;
@override
String toString() {
return 'IntroOutSkipModel(intro: $intro, credits: $credits)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IntroOutSkipModelImpl &&
(identical(other.intro, intro) || other.intro == intro) &&
(identical(other.credits, credits) || other.credits == credits));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, intro, credits);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith =>
__$$IntroOutSkipModelImplCopyWithImpl<_$IntroOutSkipModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IntroOutSkipModelImplToJson(
this,
);
}
}
abstract class _IntroOutSkipModel extends IntroOutSkipModel {
factory _IntroOutSkipModel(
{final IntroSkipModel? intro,
final IntroSkipModel? credits}) = _$IntroOutSkipModelImpl;
_IntroOutSkipModel._() : super._();
factory _IntroOutSkipModel.fromJson(Map<String, dynamic> json) =
_$IntroOutSkipModelImpl.fromJson;
@override
IntroSkipModel? get intro;
@override
IntroSkipModel? get credits;
@override
@JsonKey(ignore: true)
_$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}
IntroSkipModel _$IntroSkipModelFromJson(Map<String, dynamic> json) {
return _IntroSkipModel.fromJson(json);
}
/// @nodoc
mixin _$IntroSkipModel {
@JsonKey(name: "EpisodeId")
String get id => throw _privateConstructorUsedError;
@JsonKey(name: "Valid")
bool get valid => throw _privateConstructorUsedError;
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get start => throw _privateConstructorUsedError;
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get end => throw _privateConstructorUsedError;
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get showTime => throw _privateConstructorUsedError;
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get hideTime => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IntroSkipModelCopyWith<IntroSkipModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IntroSkipModelCopyWith<$Res> {
factory $IntroSkipModelCopyWith(
IntroSkipModel value, $Res Function(IntroSkipModel) then) =
_$IntroSkipModelCopyWithImpl<$Res, IntroSkipModel>;
@useResult
$Res call(
{@JsonKey(name: "EpisodeId") String id,
@JsonKey(name: "Valid") bool valid,
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration start,
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration end,
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration showTime,
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration hideTime});
}
/// @nodoc
class _$IntroSkipModelCopyWithImpl<$Res, $Val extends IntroSkipModel>
implements $IntroSkipModelCopyWith<$Res> {
_$IntroSkipModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? valid = null,
Object? start = null,
Object? end = null,
Object? showTime = null,
Object? hideTime = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
valid: null == valid
? _value.valid
: valid // ignore: cast_nullable_to_non_nullable
as bool,
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as Duration,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as Duration,
showTime: null == showTime
? _value.showTime
: showTime // ignore: cast_nullable_to_non_nullable
as Duration,
hideTime: null == hideTime
? _value.hideTime
: hideTime // ignore: cast_nullable_to_non_nullable
as Duration,
) as $Val);
}
}
/// @nodoc
abstract class _$$IntroSkipModelImplCopyWith<$Res>
implements $IntroSkipModelCopyWith<$Res> {
factory _$$IntroSkipModelImplCopyWith(_$IntroSkipModelImpl value,
$Res Function(_$IntroSkipModelImpl) then) =
__$$IntroSkipModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@JsonKey(name: "EpisodeId") String id,
@JsonKey(name: "Valid") bool valid,
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration start,
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration end,
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration showTime,
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration hideTime});
}
/// @nodoc
class __$$IntroSkipModelImplCopyWithImpl<$Res>
extends _$IntroSkipModelCopyWithImpl<$Res, _$IntroSkipModelImpl>
implements _$$IntroSkipModelImplCopyWith<$Res> {
__$$IntroSkipModelImplCopyWithImpl(
_$IntroSkipModelImpl _value, $Res Function(_$IntroSkipModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? valid = null,
Object? start = null,
Object? end = null,
Object? showTime = null,
Object? hideTime = null,
}) {
return _then(_$IntroSkipModelImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
valid: null == valid
? _value.valid
: valid // ignore: cast_nullable_to_non_nullable
as bool,
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as Duration,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as Duration,
showTime: null == showTime
? _value.showTime
: showTime // ignore: cast_nullable_to_non_nullable
as Duration,
hideTime: null == hideTime
? _value.hideTime
: hideTime // ignore: cast_nullable_to_non_nullable
as Duration,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IntroSkipModelImpl implements _IntroSkipModel {
_$IntroSkipModelImpl(
{@JsonKey(name: "EpisodeId") required this.id,
@JsonKey(name: "Valid") required this.valid,
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required this.start,
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required this.end,
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required this.showTime,
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required this.hideTime});
factory _$IntroSkipModelImpl.fromJson(Map<String, dynamic> json) =>
_$$IntroSkipModelImplFromJson(json);
@override
@JsonKey(name: "EpisodeId")
final String id;
@override
@JsonKey(name: "Valid")
final bool valid;
@override
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
final Duration start;
@override
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
final Duration end;
@override
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
final Duration showTime;
@override
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
final Duration hideTime;
@override
String toString() {
return 'IntroSkipModel(id: $id, valid: $valid, start: $start, end: $end, showTime: $showTime, hideTime: $hideTime)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IntroSkipModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.valid, valid) || other.valid == valid) &&
(identical(other.start, start) || other.start == start) &&
(identical(other.end, end) || other.end == end) &&
(identical(other.showTime, showTime) ||
other.showTime == showTime) &&
(identical(other.hideTime, hideTime) ||
other.hideTime == hideTime));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, id, valid, start, end, showTime, hideTime);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith =>
__$$IntroSkipModelImplCopyWithImpl<_$IntroSkipModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IntroSkipModelImplToJson(
this,
);
}
}
abstract class _IntroSkipModel implements IntroSkipModel {
factory _IntroSkipModel(
{@JsonKey(name: "EpisodeId") required final String id,
@JsonKey(name: "Valid") required final bool valid,
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required final Duration start,
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required final Duration end,
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required final Duration showTime,
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
required final Duration hideTime}) = _$IntroSkipModelImpl;
factory _IntroSkipModel.fromJson(Map<String, dynamic> json) =
_$IntroSkipModelImpl.fromJson;
@override
@JsonKey(name: "EpisodeId")
String get id;
@override
@JsonKey(name: "Valid")
bool get valid;
@override
@JsonKey(
name: "IntroStart",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get start;
@override
@JsonKey(
name: "IntroEnd",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get end;
@override
@JsonKey(
name: "ShowSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get showTime;
@override
@JsonKey(
name: "HideSkipPromptAt",
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration get hideTime;
@override
@JsonKey(ignore: true)
_$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,46 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'intro_skip_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$IntroOutSkipModelImpl _$$IntroOutSkipModelImplFromJson(
Map<String, dynamic> json) =>
_$IntroOutSkipModelImpl(
intro: json['intro'] == null
? null
: IntroSkipModel.fromJson(json['intro'] as Map<String, dynamic>),
credits: json['credits'] == null
? null
: IntroSkipModel.fromJson(json['credits'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$IntroOutSkipModelImplToJson(
_$IntroOutSkipModelImpl instance) =>
<String, dynamic>{
'intro': instance.intro,
'credits': instance.credits,
};
_$IntroSkipModelImpl _$$IntroSkipModelImplFromJson(Map<String, dynamic> json) =>
_$IntroSkipModelImpl(
id: json['EpisodeId'] as String,
valid: json['Valid'] as bool,
start: _durationFromMilliseconds(json['IntroStart'] as num),
end: _durationFromMilliseconds(json['IntroEnd'] as num),
showTime: _durationFromMilliseconds(json['ShowSkipPromptAt'] as num),
hideTime: _durationFromMilliseconds(json['HideSkipPromptAt'] as num),
);
Map<String, dynamic> _$$IntroSkipModelImplToJson(
_$IntroSkipModelImpl instance) =>
<String, dynamic>{
'EpisodeId': instance.id,
'Valid': instance.valid,
'IntroStart': _durationToMilliseconds(instance.start),
'IntroEnd': _durationToMilliseconds(instance.end),
'ShowSkipPromptAt': _durationToMilliseconds(instance.showTime),
'HideSkipPromptAt': _durationToMilliseconds(instance.hideTime),
};

View file

@ -0,0 +1,21 @@
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:freezed_annotation/freezed_annotation.dart';
part 'item_properties_model.freezed.dart';
@Freezed(fromJson: false, toJson: false)
class ItemPropertiesModel with _$ItemPropertiesModel {
const ItemPropertiesModel._();
factory ItemPropertiesModel._internal({
required bool canDelete,
required bool canDownload,
}) = _ItemPropertiesModel;
factory ItemPropertiesModel.fromBaseDto(dto.BaseItemDto dtoItem) {
return ItemPropertiesModel._internal(
canDelete: dtoItem.canDelete ?? false,
canDownload: dtoItem.canDownload ?? false,
);
}
}

View file

@ -0,0 +1,156 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'item_properties_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ItemPropertiesModel {
bool get canDelete => throw _privateConstructorUsedError;
bool get canDownload => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ItemPropertiesModelCopyWith<ItemPropertiesModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ItemPropertiesModelCopyWith<$Res> {
factory $ItemPropertiesModelCopyWith(
ItemPropertiesModel value, $Res Function(ItemPropertiesModel) then) =
_$ItemPropertiesModelCopyWithImpl<$Res, ItemPropertiesModel>;
@useResult
$Res call({bool canDelete, bool canDownload});
}
/// @nodoc
class _$ItemPropertiesModelCopyWithImpl<$Res, $Val extends ItemPropertiesModel>
implements $ItemPropertiesModelCopyWith<$Res> {
_$ItemPropertiesModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? canDelete = null,
Object? canDownload = null,
}) {
return _then(_value.copyWith(
canDelete: null == canDelete
? _value.canDelete
: canDelete // ignore: cast_nullable_to_non_nullable
as bool,
canDownload: null == canDownload
? _value.canDownload
: canDownload // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$ItemPropertiesModelImplCopyWith<$Res>
implements $ItemPropertiesModelCopyWith<$Res> {
factory _$$ItemPropertiesModelImplCopyWith(_$ItemPropertiesModelImpl value,
$Res Function(_$ItemPropertiesModelImpl) then) =
__$$ItemPropertiesModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool canDelete, bool canDownload});
}
/// @nodoc
class __$$ItemPropertiesModelImplCopyWithImpl<$Res>
extends _$ItemPropertiesModelCopyWithImpl<$Res, _$ItemPropertiesModelImpl>
implements _$$ItemPropertiesModelImplCopyWith<$Res> {
__$$ItemPropertiesModelImplCopyWithImpl(_$ItemPropertiesModelImpl _value,
$Res Function(_$ItemPropertiesModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? canDelete = null,
Object? canDownload = null,
}) {
return _then(_$ItemPropertiesModelImpl(
canDelete: null == canDelete
? _value.canDelete
: canDelete // ignore: cast_nullable_to_non_nullable
as bool,
canDownload: null == canDownload
? _value.canDownload
: canDownload // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
class _$ItemPropertiesModelImpl extends _ItemPropertiesModel {
_$ItemPropertiesModelImpl(
{required this.canDelete, required this.canDownload})
: super._();
@override
final bool canDelete;
@override
final bool canDownload;
@override
String toString() {
return 'ItemPropertiesModel._internal(canDelete: $canDelete, canDownload: $canDownload)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ItemPropertiesModelImpl &&
(identical(other.canDelete, canDelete) ||
other.canDelete == canDelete) &&
(identical(other.canDownload, canDownload) ||
other.canDownload == canDownload));
}
@override
int get hashCode => Object.hash(runtimeType, canDelete, canDownload);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
__$$ItemPropertiesModelImplCopyWithImpl<_$ItemPropertiesModelImpl>(
this, _$identity);
}
abstract class _ItemPropertiesModel extends ItemPropertiesModel {
factory _ItemPropertiesModel(
{required final bool canDelete,
required final bool canDownload}) = _$ItemPropertiesModelImpl;
_ItemPropertiesModel._() : super._();
@override
bool get canDelete;
@override
bool get canDownload;
@override
@JsonKey(ignore: true)
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,380 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/items/images_models.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'item_shared_models.mapper.dart';
@MappableClass()
class UserData with UserDataMappable {
final bool isFavourite;
final int playCount;
final int? unPlayedItemCount;
final int playbackPositionTicks;
final double progress;
final bool played;
final DateTime? lastPlayed;
const UserData({
this.isFavourite = false,
this.playCount = 0,
this.unPlayedItemCount,
this.playbackPositionTicks = 0,
this.progress = 0,
this.lastPlayed,
this.played = false,
});
factory UserData.fromDto(dto.UserItemDataDto? dto) {
if (dto == null) {
return UserData();
}
return UserData(
isFavourite: dto.isFavorite ?? false,
playCount: dto.playCount ?? 0,
playbackPositionTicks: dto.playbackPositionTicks ?? 0,
played: dto.played ?? false,
unPlayedItemCount: dto.unplayedItemCount ?? 0,
lastPlayed: dto.lastPlayedDate,
progress: dto.playedPercentage ?? 0,
);
}
Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
factory UserData.fromMap(Map<String, dynamic> map) => UserDataMapper.fromMap(map);
factory UserData.fromJson(String json) => UserDataMapper.fromJson(json);
}
class UserDataJsonSerializer extends JsonConverter<UserData, String> {
const UserDataJsonSerializer();
@override
UserData fromJson(String json) {
return UserData.fromJson(json);
}
@override
String toJson(UserData object) {
return object.toJson();
}
}
enum EditorLockedFields {
name("Name"),
overView("Overview"),
genres("Genres"),
officialRating("OfficialRating"),
cast("Cast"),
productionLocations("ProductionLocations"),
runTime("Runtime"),
studios("Studios"),
tags("Tags"),
;
const EditorLockedFields(this.value);
static Map<EditorLockedFields, bool> enabled(List<String> fromStrings) => Map.fromEntries(
EditorLockedFields.values.map(
(e) => MapEntry(e, fromStrings.contains(e.value)),
),
);
final String value;
}
enum DisplayOrder {
empty(""),
aired("aired"),
originalAirDate("originalAirDate"),
absolute("absolute"),
dvd("dvd"),
digital("digital"),
storyArc("storyArc"),
production("production"),
tv("tv"),
;
const DisplayOrder(this.value);
static DisplayOrder? fromMap(String? value) {
return DisplayOrder.values.firstWhereOrNull((element) => element.value == value) ?? DisplayOrder.empty;
}
final String value;
}
enum ShowStatus {
empty(""),
ended("Ended"),
continuing("Continuing");
const ShowStatus(this.value);
static ShowStatus? fromMap(String? value) {
return ShowStatus.values.firstWhereOrNull((element) => element.value == value) ?? ShowStatus.empty;
}
final String value;
}
class ExternalUrls {
final String name;
final String url;
ExternalUrls({
required this.name,
required this.url,
});
static List<ExternalUrls> fromDto(List<dto.ExternalUrl> dto) {
return dto.map((e) => ExternalUrls(name: e.name ?? "", url: e.url ?? "")).toList();
}
Map<String, dynamic> toMap() {
return {
'Name': name,
'Url': url,
};
}
factory ExternalUrls.fromMap(Map<String, dynamic> map) {
return ExternalUrls(
name: map['Name'] ?? '',
url: map['Url'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory ExternalUrls.fromJson(String source) => ExternalUrls.fromMap(json.decode(source));
}
class GenreItems {
final String id;
final String name;
GenreItems({
required this.id,
required this.name,
});
@override
String toString() => 'GenreItems(id: $id, name: $name)';
}
class Person {
final String id;
final String name;
final ImageData? image;
final String role;
final PersonKind? type;
Person({
required this.id,
this.name = "",
this.image,
this.role = "",
this.type,
});
static Person fromBaseDto(dto.BaseItemDto item, Ref ref) {
return Person(
id: item.id ?? "",
name: item.name ?? "",
image: ImagesData.fromBaseItem(item, ref).primary,
);
}
static Person fromBasePerson(dto.BaseItemPerson person, Ref ref) {
return Person(
id: person.id ?? "",
name: person.name ?? "",
image: ImagesData.fromPersonDto(person, ref)?.primary,
role: person.role ?? "",
type: person.type);
}
dto.BaseItemPerson toPerson() {
return dto.BaseItemPerson(
id: id,
name: name,
type: type,
role: role,
);
}
static List<Person> peopleFromDto(List<dto.BaseItemPerson>? people, Ref ref) {
return people
?.mapIndexed(
(index, person) => fromBasePerson(person, ref),
)
.toList() ??
[];
}
@override
String toString() {
return 'People(id: $id, name: $name, imageUrl: $image, role: $role, type: $type)';
}
}
class Studio {
final String id;
final String name;
Studio({
required this.id,
required this.name,
});
Studio copyWith({
String? id,
String? name,
ValueGetter<String?>? image,
}) {
return Studio(
id: id ?? this.id,
name: name ?? this.name,
);
}
@override
String toString() => 'Studio(name: $name, id: $id)';
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
};
}
factory Studio.fromMap(Map<String, dynamic> map) {
return Studio(
id: map['id'] ?? map['Id'] ?? '',
name: map['name'] ?? map['Name'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory Studio.fromJson(String source) => Studio.fromMap(json.decode(source));
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Studio && other.id == id && other.name == name;
}
@override
int get hashCode => id.hashCode ^ name.hashCode;
}
// class UserData {
// final bool isFavourite;
// final int playCount;
// final int? unPlayedItemCount;
// final int playbackPositionTicks;
// final double progress;
// final bool played;
// UserData({
// this.isFavourite = false,
// this.playCount = 0,
// this.unPlayedItemCount,
// this.playbackPositionTicks = 0,
// this.progress = 0,
// this.played = false,
// });
// factory UserData.fromDto(dto.UserItemDataDto? dto) {
// if (dto == null) {
// return UserData();
// }
// return UserData(
// isFavourite: dto.isFavorite ?? false,
// playCount: dto.playCount ?? 0,
// playbackPositionTicks: dto.playbackPositionTicks ?? 0,
// played: dto.played ?? false,
// unPlayedItemCount: dto.unplayedItemCount ?? 0,
// progress: dto.playedPercentage ?? 0,
// );
// }
// Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
// @override
// String toString() {
// return 'UserData(isFavourite: $isFavourite, playCount: $playCount, unPlayedItemCount: $unPlayedItemCount, playbackPositionTicks: $playbackPositionTicks, progress: $progress, played: $played)';
// }
// UserData copyWith({
// bool? isFavourite,
// int? playCount,
// int? unPlayedItemCount,
// int? playbackPositionTicks,
// double? progress,
// bool? played,
// }) {
// return UserData(
// isFavourite: isFavourite ?? this.isFavourite,
// playCount: playCount ?? this.playCount,
// unPlayedItemCount: unPlayedItemCount ?? this.unPlayedItemCount,
// playbackPositionTicks: playbackPositionTicks ?? this.playbackPositionTicks,
// progress: progress ?? this.progress,
// played: played ?? this.played,
// );
// }
// Map<String, dynamic> toMap() {
// return <String, dynamic>{
// 'isFavourite': isFavourite,
// 'playCount': playCount,
// 'unPlayedItemCount': unPlayedItemCount,
// 'playbackPositionTicks': playbackPositionTicks,
// 'progress': progress,
// 'played': played,
// };
// }
// factory UserData.fromMap(Map<String, dynamic> map) {
// return UserData(
// isFavourite: (map['isFavourite'] ?? false) as bool,
// playCount: (map['playCount'] ?? 0) as int,
// unPlayedItemCount: (map['unPlayedItemCount'] ?? 0) as int,
// playbackPositionTicks: (map['playbackPositionTicks'] ?? 0) as int,
// progress: (map['progress'] ?? 0.0) as double,
// played: (map['played'] ?? false) as bool,
// );
// }
// String toJson() => json.encode(toMap());
// factory UserData.fromJson(String source) => UserData.fromMap(json.decode(source) as Map<String, dynamic>);
// @override
// bool operator ==(covariant UserData other) {
// if (identical(this, other)) return true;
// return other.isFavourite == isFavourite &&
// other.playCount == playCount &&
// other.unPlayedItemCount == unPlayedItemCount &&
// other.playbackPositionTicks == playbackPositionTicks &&
// other.progress == progress &&
// other.played == played;
// }
// @override
// int get hashCode {
// return isFavourite.hashCode ^
// playCount.hashCode ^
// unPlayedItemCount.hashCode ^
// playbackPositionTicks.hashCode ^
// progress.hashCode ^
// played.hashCode;
// }
// }

View file

@ -0,0 +1,162 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'item_shared_models.dart';
class UserDataMapper extends ClassMapperBase<UserData> {
UserDataMapper._();
static UserDataMapper? _instance;
static UserDataMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = UserDataMapper._());
}
return _instance!;
}
@override
final String id = 'UserData';
static bool _$isFavourite(UserData v) => v.isFavourite;
static const Field<UserData, bool> _f$isFavourite =
Field('isFavourite', _$isFavourite, opt: true, def: false);
static int _$playCount(UserData v) => v.playCount;
static const Field<UserData, int> _f$playCount =
Field('playCount', _$playCount, opt: true, def: 0);
static int? _$unPlayedItemCount(UserData v) => v.unPlayedItemCount;
static const Field<UserData, int> _f$unPlayedItemCount =
Field('unPlayedItemCount', _$unPlayedItemCount, opt: true);
static int _$playbackPositionTicks(UserData v) => v.playbackPositionTicks;
static const Field<UserData, int> _f$playbackPositionTicks = Field(
'playbackPositionTicks', _$playbackPositionTicks,
opt: true, def: 0);
static double _$progress(UserData v) => v.progress;
static const Field<UserData, double> _f$progress =
Field('progress', _$progress, opt: true, def: 0);
static DateTime? _$lastPlayed(UserData v) => v.lastPlayed;
static const Field<UserData, DateTime> _f$lastPlayed =
Field('lastPlayed', _$lastPlayed, opt: true);
static bool _$played(UserData v) => v.played;
static const Field<UserData, bool> _f$played =
Field('played', _$played, opt: true, def: false);
@override
final MappableFields<UserData> fields = const {
#isFavourite: _f$isFavourite,
#playCount: _f$playCount,
#unPlayedItemCount: _f$unPlayedItemCount,
#playbackPositionTicks: _f$playbackPositionTicks,
#progress: _f$progress,
#lastPlayed: _f$lastPlayed,
#played: _f$played,
};
@override
final bool ignoreNull = true;
static UserData _instantiate(DecodingData data) {
return UserData(
isFavourite: data.dec(_f$isFavourite),
playCount: data.dec(_f$playCount),
unPlayedItemCount: data.dec(_f$unPlayedItemCount),
playbackPositionTicks: data.dec(_f$playbackPositionTicks),
progress: data.dec(_f$progress),
lastPlayed: data.dec(_f$lastPlayed),
played: data.dec(_f$played));
}
@override
final Function instantiate = _instantiate;
static UserData fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<UserData>(map);
}
static UserData fromJson(String json) {
return ensureInitialized().decodeJson<UserData>(json);
}
}
mixin UserDataMappable {
String toJson() {
return UserDataMapper.ensureInitialized()
.encodeJson<UserData>(this as UserData);
}
Map<String, dynamic> toMap() {
return UserDataMapper.ensureInitialized()
.encodeMap<UserData>(this as UserData);
}
UserDataCopyWith<UserData, UserData, UserData> get copyWith =>
_UserDataCopyWithImpl(this as UserData, $identity, $identity);
@override
String toString() {
return UserDataMapper.ensureInitialized().stringifyValue(this as UserData);
}
}
extension UserDataValueCopy<$R, $Out> on ObjectCopyWith<$R, UserData, $Out> {
UserDataCopyWith<$R, UserData, $Out> get $asUserData =>
$base.as((v, t, t2) => _UserDataCopyWithImpl(v, t, t2));
}
abstract class UserDataCopyWith<$R, $In extends UserData, $Out>
implements ClassCopyWith<$R, $In, $Out> {
$R call(
{bool? isFavourite,
int? playCount,
int? unPlayedItemCount,
int? playbackPositionTicks,
double? progress,
DateTime? lastPlayed,
bool? played});
UserDataCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _UserDataCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, UserData, $Out>
implements UserDataCopyWith<$R, UserData, $Out> {
_UserDataCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<UserData> $mapper =
UserDataMapper.ensureInitialized();
@override
$R call(
{bool? isFavourite,
int? playCount,
Object? unPlayedItemCount = $none,
int? playbackPositionTicks,
double? progress,
Object? lastPlayed = $none,
bool? played}) =>
$apply(FieldCopyWithData({
if (isFavourite != null) #isFavourite: isFavourite,
if (playCount != null) #playCount: playCount,
if (unPlayedItemCount != $none) #unPlayedItemCount: unPlayedItemCount,
if (playbackPositionTicks != null)
#playbackPositionTicks: playbackPositionTicks,
if (progress != null) #progress: progress,
if (lastPlayed != $none) #lastPlayed: lastPlayed,
if (played != null) #played: played
}));
@override
UserData $make(CopyWithData data) => UserData(
isFavourite: data.get(#isFavourite, or: $value.isFavourite),
playCount: data.get(#playCount, or: $value.playCount),
unPlayedItemCount:
data.get(#unPlayedItemCount, or: $value.unPlayedItemCount),
playbackPositionTicks:
data.get(#playbackPositionTicks, or: $value.playbackPositionTicks),
progress: data.get(#progress, or: $value.progress),
lastPlayed: data.get(#lastPlayed, or: $value.lastPlayed),
played: data.get(#played, or: $value.played));
@override
UserDataCopyWith<$R2, UserData, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_UserDataCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,67 @@
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/movie_model.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'item_stream_model.mapper.dart';
@MappableClass()
class ItemStreamModel extends ItemBaseModel with ItemStreamModelMappable {
final ImagesData? parentImages;
final MediaStreamsModel mediaStreams;
const ItemStreamModel({
required this.parentImages,
required this.mediaStreams,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.canDelete,
required super.canDownload,
super.jellyType,
});
factory ItemStreamModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return switch (item.type) {
BaseItemKind.episode => EpisodeModel.fromBaseDto(item, ref),
BaseItemKind.movie => MovieModel.fromBaseDto(item, ref),
_ => ItemStreamModel._fromBaseDto(item, ref)
};
}
factory ItemStreamModel._fromBaseDto(dto.BaseItemDto item, Ref ref) {
return ItemStreamModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
parentImages: ImagesData.fromBaseItemParent(item, ref),
canDelete: item.canDelete,
canDownload: item.canDownload,
mediaStreams:
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
);
}
String? get videoPropertiesLabel {
if (mediaStreams.displayProfile == null && mediaStreams.resolution == null) return null;
return "${mediaStreams.displayProfile?.value}";
}
}

View file

@ -0,0 +1,245 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'item_stream_model.dart';
class ItemStreamModelMapper extends SubClassMapperBase<ItemStreamModel> {
ItemStreamModelMapper._();
static ItemStreamModelMapper? _instance;
static ItemStreamModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = ItemStreamModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'ItemStreamModel';
static ImagesData? _$parentImages(ItemStreamModel v) => v.parentImages;
static const Field<ItemStreamModel, ImagesData> _f$parentImages =
Field('parentImages', _$parentImages);
static MediaStreamsModel _$mediaStreams(ItemStreamModel v) => v.mediaStreams;
static const Field<ItemStreamModel, MediaStreamsModel> _f$mediaStreams =
Field('mediaStreams', _$mediaStreams);
static String _$name(ItemStreamModel v) => v.name;
static const Field<ItemStreamModel, String> _f$name = Field('name', _$name);
static String _$id(ItemStreamModel v) => v.id;
static const Field<ItemStreamModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(ItemStreamModel v) => v.overview;
static const Field<ItemStreamModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(ItemStreamModel v) => v.parentId;
static const Field<ItemStreamModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(ItemStreamModel v) => v.playlistId;
static const Field<ItemStreamModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(ItemStreamModel v) => v.images;
static const Field<ItemStreamModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(ItemStreamModel v) => v.childCount;
static const Field<ItemStreamModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(ItemStreamModel v) => v.primaryRatio;
static const Field<ItemStreamModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(ItemStreamModel v) => v.userData;
static const Field<ItemStreamModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDelete(ItemStreamModel v) => v.canDelete;
static const Field<ItemStreamModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static bool? _$canDownload(ItemStreamModel v) => v.canDownload;
static const Field<ItemStreamModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static dto.BaseItemKind? _$jellyType(ItemStreamModel v) => v.jellyType;
static const Field<ItemStreamModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<ItemStreamModel> fields = const {
#parentImages: _f$parentImages,
#mediaStreams: _f$mediaStreams,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDelete: _f$canDelete,
#canDownload: _f$canDownload,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'ItemStreamModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static ItemStreamModel _instantiate(DecodingData data) {
return ItemStreamModel(
parentImages: data.dec(_f$parentImages),
mediaStreams: data.dec(_f$mediaStreams),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDelete: data.dec(_f$canDelete),
canDownload: data.dec(_f$canDownload),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static ItemStreamModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<ItemStreamModel>(map);
}
static ItemStreamModel fromJson(String json) {
return ensureInitialized().decodeJson<ItemStreamModel>(json);
}
}
mixin ItemStreamModelMappable {
String toJson() {
return ItemStreamModelMapper.ensureInitialized()
.encodeJson<ItemStreamModel>(this as ItemStreamModel);
}
Map<String, dynamic> toMap() {
return ItemStreamModelMapper.ensureInitialized()
.encodeMap<ItemStreamModel>(this as ItemStreamModel);
}
ItemStreamModelCopyWith<ItemStreamModel, ItemStreamModel, ItemStreamModel>
get copyWith => _ItemStreamModelCopyWithImpl(
this as ItemStreamModel, $identity, $identity);
@override
String toString() {
return ItemStreamModelMapper.ensureInitialized()
.stringifyValue(this as ItemStreamModel);
}
}
extension ItemStreamModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, ItemStreamModel, $Out> {
ItemStreamModelCopyWith<$R, ItemStreamModel, $Out> get $asItemStreamModel =>
$base.as((v, t, t2) => _ItemStreamModelCopyWithImpl(v, t, t2));
}
abstract class ItemStreamModelCopyWith<$R, $In extends ItemStreamModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{ImagesData? parentImages,
MediaStreamsModel? mediaStreams,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDelete,
bool? canDownload,
dto.BaseItemKind? jellyType});
ItemStreamModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t);
}
class _ItemStreamModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, ItemStreamModel, $Out>
implements ItemStreamModelCopyWith<$R, ItemStreamModel, $Out> {
_ItemStreamModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<ItemStreamModel> $mapper =
ItemStreamModelMapper.ensureInitialized();
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? parentImages = $none,
MediaStreamsModel? mediaStreams,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDelete = $none,
Object? canDownload = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (parentImages != $none) #parentImages: parentImages,
if (mediaStreams != null) #mediaStreams: mediaStreams,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDelete != $none) #canDelete: canDelete,
if (canDownload != $none) #canDownload: canDownload,
if (jellyType != $none) #jellyType: jellyType
}));
@override
ItemStreamModel $make(CopyWithData data) => ItemStreamModel(
parentImages: data.get(#parentImages, or: $value.parentImages),
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDelete: data.get(#canDelete, or: $value.canDelete),
canDownload: data.get(#canDownload, or: $value.canDownload),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
ItemStreamModelCopyWith<$R2, ItemStreamModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_ItemStreamModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,372 @@
import 'dart:convert';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:flutter/material.dart';
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/util/video_properties.dart';
class MediaStreamsModel {
final int? defaultAudioStreamIndex;
final int? defaultSubStreamIndex;
final List<VideoStreamModel> videoStreams;
final List<AudioStreamModel> audioStreams;
final List<SubStreamModel> subStreams;
MediaStreamsModel({
this.defaultAudioStreamIndex,
this.defaultSubStreamIndex,
required this.videoStreams,
required this.audioStreams,
required this.subStreams,
});
bool get isNull {
return defaultAudioStreamIndex == null ||
defaultSubStreamIndex == null ||
audioStreams.isEmpty ||
subStreams.isEmpty;
}
bool get isNotEmpty {
return audioStreams.isNotEmpty && subStreams.isNotEmpty;
}
AudioStreamModel? get currentAudioStream {
if (defaultAudioStreamIndex == -1) {
return AudioStreamModel.no();
}
return audioStreams.firstWhereOrNull((element) => element.index == defaultAudioStreamIndex) ??
audioStreams.firstOrNull;
}
SubStreamModel? get currentSubStream {
if (defaultSubStreamIndex == -1) {
return SubStreamModel.no();
}
return subStreams.firstWhereOrNull((element) => element.index == defaultSubStreamIndex) ?? subStreams.firstOrNull;
}
DisplayProfile? get displayProfile {
return DisplayProfile.fromVideoStreams(videoStreams);
}
Resolution? get resolution {
return Resolution.fromVideoStream(videoStreams.firstOrNull);
}
String? get resolutionText {
final stream = videoStreams.firstOrNull;
if (stream == null) return null;
return "${stream.width}x${stream.height}";
}
Widget? audioIcon(
BuildContext context,
Function()? onTap,
) {
final audioStream = audioStreams.firstWhereOrNull((element) => element.isDefault) ?? audioStreams.firstOrNull;
if (audioStream == null) return null;
return DefaultVideoInformationBox(
onTap: onTap,
child: Text(
audioStream.title,
),
);
}
Widget subtitleIcon(
BuildContext context,
Function()? onTap,
) {
return DefaultVideoInformationBox(
onTap: onTap,
child: Icon(
subStreams.isNotEmpty ? Icons.subtitles_rounded : Icons.subtitles_off_outlined,
),
);
}
static MediaStreamsModel fromMediaStreamsList(
dto.MediaSourceInfo? mediaSource, List<dto.MediaStream> streams, Ref ref) {
return MediaStreamsModel(
defaultAudioStreamIndex: mediaSource?.defaultAudioStreamIndex,
defaultSubStreamIndex: mediaSource?.defaultSubtitleStreamIndex,
videoStreams: streams
.where((element) => element.type == dto.MediaStreamType.video)
.map(
(e) => VideoStreamModel.fromMediaStream(e),
)
.sortByExternal(),
audioStreams: streams
.where((element) => element.type == dto.MediaStreamType.audio)
.map(
(e) => AudioStreamModel.fromMediaStream(e),
)
.sortByExternal(),
subStreams: streams
.where((element) => element.type == dto.MediaStreamType.subtitle)
.map(
(sub) => SubStreamModel.fromMediaStream(sub, ref),
)
.sortByExternal(),
);
}
MediaStreamsModel copyWith({
int? defaultAudioStreamIndex,
int? defaultSubStreamIndex,
List<VideoStreamModel>? videoStreams,
List<AudioStreamModel>? audioStreams,
List<SubStreamModel>? subStreams,
}) {
return MediaStreamsModel(
defaultAudioStreamIndex: defaultAudioStreamIndex ?? this.defaultAudioStreamIndex,
defaultSubStreamIndex: defaultSubStreamIndex ?? this.defaultSubStreamIndex,
videoStreams: videoStreams ?? this.videoStreams,
audioStreams: audioStreams ?? this.audioStreams,
subStreams: subStreams ?? this.subStreams,
);
}
@override
String toString() {
return 'MediaStreamsModel(defaultAudioStreamIndex: $defaultAudioStreamIndex, defaultSubStreamIndex: $defaultSubStreamIndex, videoStreams: $videoStreams, audioStreams: $audioStreams, subStreams: $subStreams)';
}
}
class StreamModel {
final String name;
final String codec;
final bool isDefault;
final bool isExternal;
final int index;
StreamModel({
required this.name,
required this.codec,
required this.isDefault,
required this.isExternal,
required this.index,
});
}
class VideoStreamModel extends StreamModel {
final int width;
final int height;
final double frameRate;
final String? videoDoViTitle;
final VideoRangeType? videoRangeType;
VideoStreamModel({
required super.name,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
required this.videoDoViTitle,
required this.videoRangeType,
required this.width,
required this.height,
required this.frameRate,
});
factory VideoStreamModel.fromMediaStream(dto.MediaStream stream) {
return VideoStreamModel(
name: stream.title ?? "",
isDefault: stream.isDefault ?? false,
codec: stream.codec ?? "",
videoDoViTitle: stream.videoDoViTitle,
videoRangeType: stream.videoRangeType,
width: stream.width ?? 0,
height: stream.height ?? 0,
frameRate: stream.realFrameRate ?? 24,
isExternal: stream.isExternal ?? false,
index: stream.index ?? -1,
);
}
String get prettyName {
return "${Resolution.fromVideoStream(this)?.value} - ${DisplayProfile.fromVideoStream(this).value} - (${codec.toUpperCase()})";
}
@override
String toString() {
return 'VideoStreamModel(width: $width, height: $height, frameRate: $frameRate, videoDoViTitle: $videoDoViTitle, videoRangeType: $videoRangeType)';
}
}
//Instead of using sortBy(a.isExternal etc..) this one seems to be more consistent for some reason
extension SortByExternalExtension<T extends StreamModel> on Iterable<T> {
List<T> sortByExternal() {
return [...where((element) => !element.isExternal), ...where((element) => element.isExternal)];
}
}
class AudioStreamModel extends StreamModel {
final String displayTitle;
final String language;
final String channelLayout;
AudioStreamModel({
required this.displayTitle,
required super.name,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
required this.language,
required this.channelLayout,
});
factory AudioStreamModel.fromMediaStream(dto.MediaStream stream) {
return AudioStreamModel(
displayTitle: stream.displayTitle ?? "",
name: stream.title ?? "",
isDefault: stream.isDefault ?? false,
codec: stream.codec ?? "",
language: stream.language ?? "Unknown",
channelLayout: stream.channelLayout ?? "",
isExternal: stream.isExternal ?? false,
index: stream.index ?? -1,
);
}
String get title =>
[name, language, codec, channelLayout].whereNotNull().where((element) => element.isNotEmpty).join(' - ');
AudioStreamModel.no({
super.name = 'Off',
this.displayTitle = 'Off',
this.language = '',
super.codec = '',
this.channelLayout = '',
super.isDefault = false,
super.isExternal = false,
super.index = -1,
});
}
class SubStreamModel extends StreamModel {
String id;
String title;
String displayTitle;
String language;
String? url;
bool supportsExternalStream;
SubStreamModel({
required super.name,
required this.id,
required this.title,
required this.displayTitle,
required this.language,
this.url,
required super.codec,
required super.isDefault,
required super.isExternal,
required super.index,
this.supportsExternalStream = false,
});
SubStreamModel.no({
super.name = 'Off',
this.id = 'Off',
this.title = 'Off',
this.displayTitle = 'Off',
this.language = '',
this.url = '',
super.codec = '',
super.isDefault = false,
super.isExternal = false,
super.index = -1,
this.supportsExternalStream = false,
});
factory SubStreamModel.fromMediaStream(dto.MediaStream stream, Ref ref) {
return SubStreamModel(
name: stream.title ?? "",
title: stream.title ?? "",
displayTitle: stream.displayTitle ?? "",
language: stream.language ?? "Unknown",
isDefault: stream.isDefault ?? false,
codec: stream.codec ?? "",
id: stream.hashCode.toString(),
supportsExternalStream: stream.supportsExternalStream ?? false,
url: stream.deliveryUrl != null
? "${ref.read(userProvider)?.server ?? ""}${stream.deliveryUrl}}".replaceAll(".vtt", ".srt")
: null,
isExternal: stream.isExternal ?? false,
index: stream.index ?? -1,
);
}
SubStreamModel copyWith({
String? name,
String? id,
String? title,
String? displayTitle,
String? language,
ValueGetter<String?>? url,
String? codec,
bool? isDefault,
bool? isExternal,
int? index,
bool? supportsExternalStream,
}) {
return SubStreamModel(
name: name ?? this.name,
id: id ?? this.id,
title: title ?? this.title,
displayTitle: displayTitle ?? this.displayTitle,
language: language ?? this.language,
url: url != null ? url() : this.url,
supportsExternalStream: supportsExternalStream ?? this.supportsExternalStream,
codec: codec ?? this.codec,
isDefault: isDefault ?? this.isDefault,
isExternal: isExternal ?? this.isExternal,
index: index ?? this.index,
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'id': id,
'title': title,
'displayTitle': displayTitle,
'language': language,
'url': url,
'supportsExternalStream': supportsExternalStream,
'codec': codec,
'isExternal': isExternal,
'isDefault': isDefault,
'index': index,
};
}
factory SubStreamModel.fromMap(Map<String, dynamic> map) {
return SubStreamModel(
name: map['name'] ?? '',
id: map['id'] ?? '',
title: map['title'] ?? '',
displayTitle: map['displayTitle'] ?? '',
language: map['language'] ?? '',
url: map['url'],
supportsExternalStream: map['supportsExternalStream'] ?? false,
codec: map['codec'] ?? '',
isDefault: map['isDefault'] ?? false,
isExternal: map['isExternal'] ?? false,
index: map['index'] ?? -1,
);
}
String toJson() => json.encode(toMap());
factory SubStreamModel.fromJson(String source) => SubStreamModel.fromMap(json.decode(source));
@override
String toString() {
return 'SubFile(title: $title, displayTitle: $displayTitle, language: $language, url: $url, isExternal: $isExternal)';
}
}

View file

@ -0,0 +1,107 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/util/humanize_duration.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/item_stream_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/screens/details_screens/movie_detail_screen.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'movie_model.mapper.dart';
@MappableClass()
class MovieModel extends ItemStreamModel with MovieModelMappable {
final String originalTitle;
final String? path;
final DateTime premiereDate;
final String sortName;
final String status;
final List<ItemBaseModel> related;
final List<Chapter> chapters;
const MovieModel({
required this.originalTitle,
this.path,
this.chapters = const [],
required this.premiereDate,
required this.sortName,
required this.status,
this.related = const [],
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.parentImages,
required super.mediaStreams,
required super.canDownload,
required super.canDelete,
super.jellyType,
});
@override
String? detailedName(BuildContext context) => "$name${overview.yearAired != null ? " (${overview.yearAired})" : ""}";
@override
Widget get detailScreenWidget => MovieDetailScreen(item: this);
@override
ItemBaseModel get parentBaseModel => this;
@override
String? get subText => overview.yearAired?.toString() ?? overview.runTime.humanize;
@override
bool get playAble => true;
@override
bool get identifiable => true;
@override
String? label(BuildContext context) =>
overview.yearAired == null ? overview.runTime.humanize : "$name (${overview.yearAired})";
@override
ImageData? get bannerImage => images?.backDrop?.firstOrNull ?? images?.primary ?? getPosters?.primary;
@override
MediaStreamsModel? get streamModel => mediaStreams;
@override
bool get syncAble => true;
factory MovieModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return MovieModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
sortName: item.sortName ?? "",
status: item.status ?? "",
originalTitle: item.originalTitle ?? "",
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
chapters: Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref),
premiereDate: item.premiereDate ?? DateTime.now(),
parentImages: ImagesData.fromBaseItemParent(item, ref),
canDelete: item.canDelete,
canDownload: item.canDownload,
mediaStreams:
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
);
}
}

View file

@ -0,0 +1,318 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'movie_model.dart';
class MovieModelMapper extends SubClassMapperBase<MovieModel> {
MovieModelMapper._();
static MovieModelMapper? _instance;
static MovieModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = MovieModelMapper._());
ItemStreamModelMapper.ensureInitialized().addSubMapper(_instance!);
ItemBaseModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'MovieModel';
static String _$originalTitle(MovieModel v) => v.originalTitle;
static const Field<MovieModel, String> _f$originalTitle =
Field('originalTitle', _$originalTitle);
static String? _$path(MovieModel v) => v.path;
static const Field<MovieModel, String> _f$path =
Field('path', _$path, opt: true);
static List<Chapter> _$chapters(MovieModel v) => v.chapters;
static const Field<MovieModel, List<Chapter>> _f$chapters =
Field('chapters', _$chapters, opt: true, def: const []);
static DateTime _$premiereDate(MovieModel v) => v.premiereDate;
static const Field<MovieModel, DateTime> _f$premiereDate =
Field('premiereDate', _$premiereDate);
static String _$sortName(MovieModel v) => v.sortName;
static const Field<MovieModel, String> _f$sortName =
Field('sortName', _$sortName);
static String _$status(MovieModel v) => v.status;
static const Field<MovieModel, String> _f$status = Field('status', _$status);
static List<ItemBaseModel> _$related(MovieModel v) => v.related;
static const Field<MovieModel, List<ItemBaseModel>> _f$related =
Field('related', _$related, opt: true, def: const []);
static String _$name(MovieModel v) => v.name;
static const Field<MovieModel, String> _f$name = Field('name', _$name);
static String _$id(MovieModel v) => v.id;
static const Field<MovieModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(MovieModel v) => v.overview;
static const Field<MovieModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(MovieModel v) => v.parentId;
static const Field<MovieModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(MovieModel v) => v.playlistId;
static const Field<MovieModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(MovieModel v) => v.images;
static const Field<MovieModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(MovieModel v) => v.childCount;
static const Field<MovieModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(MovieModel v) => v.primaryRatio;
static const Field<MovieModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(MovieModel v) => v.userData;
static const Field<MovieModel, UserData> _f$userData =
Field('userData', _$userData);
static ImagesData? _$parentImages(MovieModel v) => v.parentImages;
static const Field<MovieModel, ImagesData> _f$parentImages =
Field('parentImages', _$parentImages);
static MediaStreamsModel _$mediaStreams(MovieModel v) => v.mediaStreams;
static const Field<MovieModel, MediaStreamsModel> _f$mediaStreams =
Field('mediaStreams', _$mediaStreams);
static bool? _$canDownload(MovieModel v) => v.canDownload;
static const Field<MovieModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static bool? _$canDelete(MovieModel v) => v.canDelete;
static const Field<MovieModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static dto.BaseItemKind? _$jellyType(MovieModel v) => v.jellyType;
static const Field<MovieModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<MovieModel> fields = const {
#originalTitle: _f$originalTitle,
#path: _f$path,
#chapters: _f$chapters,
#premiereDate: _f$premiereDate,
#sortName: _f$sortName,
#status: _f$status,
#related: _f$related,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#parentImages: _f$parentImages,
#mediaStreams: _f$mediaStreams,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'MovieModel';
@override
late final ClassMapperBase superMapper =
ItemStreamModelMapper.ensureInitialized();
static MovieModel _instantiate(DecodingData data) {
return MovieModel(
originalTitle: data.dec(_f$originalTitle),
path: data.dec(_f$path),
chapters: data.dec(_f$chapters),
premiereDate: data.dec(_f$premiereDate),
sortName: data.dec(_f$sortName),
status: data.dec(_f$status),
related: data.dec(_f$related),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
parentImages: data.dec(_f$parentImages),
mediaStreams: data.dec(_f$mediaStreams),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static MovieModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<MovieModel>(map);
}
static MovieModel fromJson(String json) {
return ensureInitialized().decodeJson<MovieModel>(json);
}
}
mixin MovieModelMappable {
String toJson() {
return MovieModelMapper.ensureInitialized()
.encodeJson<MovieModel>(this as MovieModel);
}
Map<String, dynamic> toMap() {
return MovieModelMapper.ensureInitialized()
.encodeMap<MovieModel>(this as MovieModel);
}
MovieModelCopyWith<MovieModel, MovieModel, MovieModel> get copyWith =>
_MovieModelCopyWithImpl(this as MovieModel, $identity, $identity);
@override
String toString() {
return MovieModelMapper.ensureInitialized()
.stringifyValue(this as MovieModel);
}
}
extension MovieModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, MovieModel, $Out> {
MovieModelCopyWith<$R, MovieModel, $Out> get $asMovieModel =>
$base.as((v, t, t2) => _MovieModelCopyWithImpl(v, t, t2));
}
abstract class MovieModelCopyWith<$R, $In extends MovieModel, $Out>
implements ItemStreamModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>> get chapters;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get related;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{String? originalTitle,
String? path,
List<Chapter>? chapters,
DateTime? premiereDate,
String? sortName,
String? status,
List<ItemBaseModel>? related,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
ImagesData? parentImages,
MediaStreamsModel? mediaStreams,
bool? canDownload,
bool? canDelete,
dto.BaseItemKind? jellyType});
MovieModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _MovieModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, MovieModel, $Out>
implements MovieModelCopyWith<$R, MovieModel, $Out> {
_MovieModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<MovieModel> $mapper =
MovieModelMapper.ensureInitialized();
@override
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>
get chapters => ListCopyWith($value.chapters,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(chapters: v));
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get related => ListCopyWith($value.related,
(v, t) => v.copyWith.$chain(t), (v) => call(related: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{String? originalTitle,
Object? path = $none,
List<Chapter>? chapters,
DateTime? premiereDate,
String? sortName,
String? status,
List<ItemBaseModel>? related,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? parentImages = $none,
MediaStreamsModel? mediaStreams,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (originalTitle != null) #originalTitle: originalTitle,
if (path != $none) #path: path,
if (chapters != null) #chapters: chapters,
if (premiereDate != null) #premiereDate: premiereDate,
if (sortName != null) #sortName: sortName,
if (status != null) #status: status,
if (related != null) #related: related,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (parentImages != $none) #parentImages: parentImages,
if (mediaStreams != null) #mediaStreams: mediaStreams,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
MovieModel $make(CopyWithData data) => MovieModel(
originalTitle: data.get(#originalTitle, or: $value.originalTitle),
path: data.get(#path, or: $value.path),
chapters: data.get(#chapters, or: $value.chapters),
premiereDate: data.get(#premiereDate, or: $value.premiereDate),
sortName: data.get(#sortName, or: $value.sortName),
status: data.get(#status, or: $value.status),
related: data.get(#related, or: $value.related),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
parentImages: data.get(#parentImages, or: $value.parentImages),
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
MovieModelCopyWith<$R2, MovieModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_MovieModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,82 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/util/duration_extensions.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'overview_model.mapper.dart';
@MappableClass()
class OverviewModel with OverviewModelMappable {
final Duration? runTime;
final String summary;
final int? yearAired;
final DateTime? dateAdded;
final String? parentalRating;
final int? productionYear;
final double? criticRating;
final double? communityRating;
final Map<String, TrickPlayModel>? trickPlayInfo;
final List<Chapter>? chapters;
final List<ExternalUrls>? externalUrls;
final List<Studio> studios;
final List<String> genres;
final List<GenreItems> genreItems;
final List<String> tags;
final List<Person> people;
const OverviewModel({
this.runTime,
this.summary = "",
this.yearAired,
this.dateAdded,
this.parentalRating,
this.productionYear,
this.criticRating,
this.communityRating,
this.trickPlayInfo,
this.chapters,
this.externalUrls,
this.studios = const [],
this.genres = const [],
this.genreItems = const [],
this.tags = const [],
this.people = const [],
});
List<Person> get directors {
return people.where((element) => element.type == PersonKind.director).toList();
}
List<Person> get writers {
return people.where((element) => element.type == PersonKind.writer).toList();
}
factory OverviewModel.fromBaseItemDto(BaseItemDto item, Ref ref) {
final trickPlayItem = item.trickplay;
return OverviewModel(
runTime: item.runTimeDuration,
yearAired: item.productionYear,
parentalRating: item.officialRating,
summary: item.overview ?? "",
genres: item.genres ?? [],
criticRating: item.criticRating,
communityRating: item.communityRating,
tags: item.tags ?? [],
dateAdded: item.dateCreated,
trickPlayInfo:
trickPlayItem != null && trickPlayItem.isNotEmpty ? TrickPlayModel.toTrickPlayMap(trickPlayItem) : null,
chapters: item.id != null ? Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref) : null,
studios: item.studios?.map((e) => Studio(id: e.id ?? "", name: e.name ?? "")).toList() ?? [],
genreItems: item.genreItems?.map((e) => GenreItems(id: e.id ?? "", name: e.name ?? "")).toList() ?? [],
externalUrls: ExternalUrls.fromDto(item.externalUrls ?? []),
people: Person.peopleFromDto(item.people ?? [], ref),
);
}
factory OverviewModel.fromMap(Map<String, dynamic> map) => OverviewModelMapper.fromMap(map);
factory OverviewModel.fromJson(String json) => OverviewModelMapper.fromJson(json);
}

View file

@ -0,0 +1,302 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'overview_model.dart';
class OverviewModelMapper extends ClassMapperBase<OverviewModel> {
OverviewModelMapper._();
static OverviewModelMapper? _instance;
static OverviewModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = OverviewModelMapper._());
}
return _instance!;
}
@override
final String id = 'OverviewModel';
static Duration? _$runTime(OverviewModel v) => v.runTime;
static const Field<OverviewModel, Duration> _f$runTime =
Field('runTime', _$runTime, opt: true);
static String _$summary(OverviewModel v) => v.summary;
static const Field<OverviewModel, String> _f$summary =
Field('summary', _$summary, opt: true, def: "");
static int? _$yearAired(OverviewModel v) => v.yearAired;
static const Field<OverviewModel, int> _f$yearAired =
Field('yearAired', _$yearAired, opt: true);
static DateTime? _$dateAdded(OverviewModel v) => v.dateAdded;
static const Field<OverviewModel, DateTime> _f$dateAdded =
Field('dateAdded', _$dateAdded, opt: true);
static String? _$parentalRating(OverviewModel v) => v.parentalRating;
static const Field<OverviewModel, String> _f$parentalRating =
Field('parentalRating', _$parentalRating, opt: true);
static int? _$productionYear(OverviewModel v) => v.productionYear;
static const Field<OverviewModel, int> _f$productionYear =
Field('productionYear', _$productionYear, opt: true);
static double? _$criticRating(OverviewModel v) => v.criticRating;
static const Field<OverviewModel, double> _f$criticRating =
Field('criticRating', _$criticRating, opt: true);
static double? _$communityRating(OverviewModel v) => v.communityRating;
static const Field<OverviewModel, double> _f$communityRating =
Field('communityRating', _$communityRating, opt: true);
static Map<String, TrickPlayModel>? _$trickPlayInfo(OverviewModel v) =>
v.trickPlayInfo;
static const Field<OverviewModel, Map<String, TrickPlayModel>>
_f$trickPlayInfo = Field('trickPlayInfo', _$trickPlayInfo, opt: true);
static List<Chapter>? _$chapters(OverviewModel v) => v.chapters;
static const Field<OverviewModel, List<Chapter>> _f$chapters =
Field('chapters', _$chapters, opt: true);
static List<ExternalUrls>? _$externalUrls(OverviewModel v) => v.externalUrls;
static const Field<OverviewModel, List<ExternalUrls>> _f$externalUrls =
Field('externalUrls', _$externalUrls, opt: true);
static List<Studio> _$studios(OverviewModel v) => v.studios;
static const Field<OverviewModel, List<Studio>> _f$studios =
Field('studios', _$studios, opt: true, def: const []);
static List<String> _$genres(OverviewModel v) => v.genres;
static const Field<OverviewModel, List<String>> _f$genres =
Field('genres', _$genres, opt: true, def: const []);
static List<GenreItems> _$genreItems(OverviewModel v) => v.genreItems;
static const Field<OverviewModel, List<GenreItems>> _f$genreItems =
Field('genreItems', _$genreItems, opt: true, def: const []);
static List<String> _$tags(OverviewModel v) => v.tags;
static const Field<OverviewModel, List<String>> _f$tags =
Field('tags', _$tags, opt: true, def: const []);
static List<Person> _$people(OverviewModel v) => v.people;
static const Field<OverviewModel, List<Person>> _f$people =
Field('people', _$people, opt: true, def: const []);
@override
final MappableFields<OverviewModel> fields = const {
#runTime: _f$runTime,
#summary: _f$summary,
#yearAired: _f$yearAired,
#dateAdded: _f$dateAdded,
#parentalRating: _f$parentalRating,
#productionYear: _f$productionYear,
#criticRating: _f$criticRating,
#communityRating: _f$communityRating,
#trickPlayInfo: _f$trickPlayInfo,
#chapters: _f$chapters,
#externalUrls: _f$externalUrls,
#studios: _f$studios,
#genres: _f$genres,
#genreItems: _f$genreItems,
#tags: _f$tags,
#people: _f$people,
};
@override
final bool ignoreNull = true;
static OverviewModel _instantiate(DecodingData data) {
return OverviewModel(
runTime: data.dec(_f$runTime),
summary: data.dec(_f$summary),
yearAired: data.dec(_f$yearAired),
dateAdded: data.dec(_f$dateAdded),
parentalRating: data.dec(_f$parentalRating),
productionYear: data.dec(_f$productionYear),
criticRating: data.dec(_f$criticRating),
communityRating: data.dec(_f$communityRating),
trickPlayInfo: data.dec(_f$trickPlayInfo),
chapters: data.dec(_f$chapters),
externalUrls: data.dec(_f$externalUrls),
studios: data.dec(_f$studios),
genres: data.dec(_f$genres),
genreItems: data.dec(_f$genreItems),
tags: data.dec(_f$tags),
people: data.dec(_f$people));
}
@override
final Function instantiate = _instantiate;
static OverviewModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<OverviewModel>(map);
}
static OverviewModel fromJson(String json) {
return ensureInitialized().decodeJson<OverviewModel>(json);
}
}
mixin OverviewModelMappable {
String toJson() {
return OverviewModelMapper.ensureInitialized()
.encodeJson<OverviewModel>(this as OverviewModel);
}
Map<String, dynamic> toMap() {
return OverviewModelMapper.ensureInitialized()
.encodeMap<OverviewModel>(this as OverviewModel);
}
OverviewModelCopyWith<OverviewModel, OverviewModel, OverviewModel>
get copyWith => _OverviewModelCopyWithImpl(
this as OverviewModel, $identity, $identity);
@override
String toString() {
return OverviewModelMapper.ensureInitialized()
.stringifyValue(this as OverviewModel);
}
}
extension OverviewModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, OverviewModel, $Out> {
OverviewModelCopyWith<$R, OverviewModel, $Out> get $asOverviewModel =>
$base.as((v, t, t2) => _OverviewModelCopyWithImpl(v, t, t2));
}
abstract class OverviewModelCopyWith<$R, $In extends OverviewModel, $Out>
implements ClassCopyWith<$R, $In, $Out> {
MapCopyWith<$R, String, TrickPlayModel,
ObjectCopyWith<$R, TrickPlayModel, TrickPlayModel>>? get trickPlayInfo;
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>? get chapters;
ListCopyWith<$R, ExternalUrls,
ObjectCopyWith<$R, ExternalUrls, ExternalUrls>>? get externalUrls;
ListCopyWith<$R, Studio, ObjectCopyWith<$R, Studio, Studio>> get studios;
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get genres;
ListCopyWith<$R, GenreItems, ObjectCopyWith<$R, GenreItems, GenreItems>>
get genreItems;
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get tags;
ListCopyWith<$R, Person, ObjectCopyWith<$R, Person, Person>> get people;
$R call(
{Duration? runTime,
String? summary,
int? yearAired,
DateTime? dateAdded,
String? parentalRating,
int? productionYear,
double? criticRating,
double? communityRating,
Map<String, TrickPlayModel>? trickPlayInfo,
List<Chapter>? chapters,
List<ExternalUrls>? externalUrls,
List<Studio>? studios,
List<String>? genres,
List<GenreItems>? genreItems,
List<String>? tags,
List<Person>? people});
OverviewModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _OverviewModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, OverviewModel, $Out>
implements OverviewModelCopyWith<$R, OverviewModel, $Out> {
_OverviewModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<OverviewModel> $mapper =
OverviewModelMapper.ensureInitialized();
@override
MapCopyWith<$R, String, TrickPlayModel,
ObjectCopyWith<$R, TrickPlayModel, TrickPlayModel>>?
get trickPlayInfo => $value.trickPlayInfo != null
? MapCopyWith(
$value.trickPlayInfo!,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(trickPlayInfo: v))
: null;
@override
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>?
get chapters => $value.chapters != null
? ListCopyWith(
$value.chapters!,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(chapters: v))
: null;
@override
ListCopyWith<$R, ExternalUrls,
ObjectCopyWith<$R, ExternalUrls, ExternalUrls>>?
get externalUrls => $value.externalUrls != null
? ListCopyWith(
$value.externalUrls!,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(externalUrls: v))
: null;
@override
ListCopyWith<$R, Studio, ObjectCopyWith<$R, Studio, Studio>> get studios =>
ListCopyWith($value.studios, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(studios: v));
@override
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get genres =>
ListCopyWith($value.genres, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(genres: v));
@override
ListCopyWith<$R, GenreItems, ObjectCopyWith<$R, GenreItems, GenreItems>>
get genreItems => ListCopyWith(
$value.genreItems,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(genreItems: v));
@override
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get tags =>
ListCopyWith($value.tags, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(tags: v));
@override
ListCopyWith<$R, Person, ObjectCopyWith<$R, Person, Person>> get people =>
ListCopyWith($value.people, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(people: v));
@override
$R call(
{Object? runTime = $none,
String? summary,
Object? yearAired = $none,
Object? dateAdded = $none,
Object? parentalRating = $none,
Object? productionYear = $none,
Object? criticRating = $none,
Object? communityRating = $none,
Object? trickPlayInfo = $none,
Object? chapters = $none,
Object? externalUrls = $none,
List<Studio>? studios,
List<String>? genres,
List<GenreItems>? genreItems,
List<String>? tags,
List<Person>? people}) =>
$apply(FieldCopyWithData({
if (runTime != $none) #runTime: runTime,
if (summary != null) #summary: summary,
if (yearAired != $none) #yearAired: yearAired,
if (dateAdded != $none) #dateAdded: dateAdded,
if (parentalRating != $none) #parentalRating: parentalRating,
if (productionYear != $none) #productionYear: productionYear,
if (criticRating != $none) #criticRating: criticRating,
if (communityRating != $none) #communityRating: communityRating,
if (trickPlayInfo != $none) #trickPlayInfo: trickPlayInfo,
if (chapters != $none) #chapters: chapters,
if (externalUrls != $none) #externalUrls: externalUrls,
if (studios != null) #studios: studios,
if (genres != null) #genres: genres,
if (genreItems != null) #genreItems: genreItems,
if (tags != null) #tags: tags,
if (people != null) #people: people
}));
@override
OverviewModel $make(CopyWithData data) => OverviewModel(
runTime: data.get(#runTime, or: $value.runTime),
summary: data.get(#summary, or: $value.summary),
yearAired: data.get(#yearAired, or: $value.yearAired),
dateAdded: data.get(#dateAdded, or: $value.dateAdded),
parentalRating: data.get(#parentalRating, or: $value.parentalRating),
productionYear: data.get(#productionYear, or: $value.productionYear),
criticRating: data.get(#criticRating, or: $value.criticRating),
communityRating: data.get(#communityRating, or: $value.communityRating),
trickPlayInfo: data.get(#trickPlayInfo, or: $value.trickPlayInfo),
chapters: data.get(#chapters, or: $value.chapters),
externalUrls: data.get(#externalUrls, or: $value.externalUrls),
studios: data.get(#studios, or: $value.studios),
genres: data.get(#genres, or: $value.genres),
genreItems: data.get(#genreItems, or: $value.genreItems),
tags: data.get(#tags, or: $value.tags),
people: data.get(#people, or: $value.people));
@override
OverviewModelCopyWith<$R2, OverviewModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_OverviewModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,68 @@
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/movie_model.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'person_model.mapper.dart';
@MappableClass()
class PersonModel extends ItemBaseModel with PersonModelMappable {
final DateTime? dateOfBirth;
final List<String> birthPlace;
final List<MovieModel> movies;
final List<SeriesModel> series;
const PersonModel({
this.dateOfBirth,
required this.birthPlace,
required this.movies,
required this.series,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
super.canDownload,
super.canDelete,
super.jellyType,
});
static PersonModel fromBaseDto(BaseItemDto item, Ref ref) {
return PersonModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
primaryRatio: item.primaryImageAspectRatio,
dateOfBirth: item.premiereDate,
birthPlace: item.productionLocations ?? [],
movies: [],
series: [],
);
}
int? get age {
if (dateOfBirth == null) return null;
final today = DateTime.now();
final months = today.month - dateOfBirth!.month;
if (months < 0) {
return (dateOfBirth!.year - (DateTime.now().year - 1)).abs();
} else {
return (dateOfBirth!.year - DateTime.now().year).abs();
}
}
}

View file

@ -0,0 +1,281 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'person_model.dart';
class PersonModelMapper extends SubClassMapperBase<PersonModel> {
PersonModelMapper._();
static PersonModelMapper? _instance;
static PersonModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = PersonModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
MovieModelMapper.ensureInitialized();
SeriesModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'PersonModel';
static DateTime? _$dateOfBirth(PersonModel v) => v.dateOfBirth;
static const Field<PersonModel, DateTime> _f$dateOfBirth =
Field('dateOfBirth', _$dateOfBirth, opt: true);
static List<String> _$birthPlace(PersonModel v) => v.birthPlace;
static const Field<PersonModel, List<String>> _f$birthPlace =
Field('birthPlace', _$birthPlace);
static List<MovieModel> _$movies(PersonModel v) => v.movies;
static const Field<PersonModel, List<MovieModel>> _f$movies =
Field('movies', _$movies);
static List<SeriesModel> _$series(PersonModel v) => v.series;
static const Field<PersonModel, List<SeriesModel>> _f$series =
Field('series', _$series);
static String _$name(PersonModel v) => v.name;
static const Field<PersonModel, String> _f$name = Field('name', _$name);
static String _$id(PersonModel v) => v.id;
static const Field<PersonModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(PersonModel v) => v.overview;
static const Field<PersonModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(PersonModel v) => v.parentId;
static const Field<PersonModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(PersonModel v) => v.playlistId;
static const Field<PersonModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(PersonModel v) => v.images;
static const Field<PersonModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(PersonModel v) => v.childCount;
static const Field<PersonModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(PersonModel v) => v.primaryRatio;
static const Field<PersonModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(PersonModel v) => v.userData;
static const Field<PersonModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDownload(PersonModel v) => v.canDownload;
static const Field<PersonModel, bool> _f$canDownload =
Field('canDownload', _$canDownload, opt: true);
static bool? _$canDelete(PersonModel v) => v.canDelete;
static const Field<PersonModel, bool> _f$canDelete =
Field('canDelete', _$canDelete, opt: true);
static BaseItemKind? _$jellyType(PersonModel v) => v.jellyType;
static const Field<PersonModel, BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<PersonModel> fields = const {
#dateOfBirth: _f$dateOfBirth,
#birthPlace: _f$birthPlace,
#movies: _f$movies,
#series: _f$series,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'PersonModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static PersonModel _instantiate(DecodingData data) {
return PersonModel(
dateOfBirth: data.dec(_f$dateOfBirth),
birthPlace: data.dec(_f$birthPlace),
movies: data.dec(_f$movies),
series: data.dec(_f$series),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static PersonModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PersonModel>(map);
}
static PersonModel fromJson(String json) {
return ensureInitialized().decodeJson<PersonModel>(json);
}
}
mixin PersonModelMappable {
String toJson() {
return PersonModelMapper.ensureInitialized()
.encodeJson<PersonModel>(this as PersonModel);
}
Map<String, dynamic> toMap() {
return PersonModelMapper.ensureInitialized()
.encodeMap<PersonModel>(this as PersonModel);
}
PersonModelCopyWith<PersonModel, PersonModel, PersonModel> get copyWith =>
_PersonModelCopyWithImpl(this as PersonModel, $identity, $identity);
@override
String toString() {
return PersonModelMapper.ensureInitialized()
.stringifyValue(this as PersonModel);
}
}
extension PersonModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, PersonModel, $Out> {
PersonModelCopyWith<$R, PersonModel, $Out> get $asPersonModel =>
$base.as((v, t, t2) => _PersonModelCopyWithImpl(v, t, t2));
}
abstract class PersonModelCopyWith<$R, $In extends PersonModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get birthPlace;
ListCopyWith<$R, MovieModel, MovieModelCopyWith<$R, MovieModel, MovieModel>>
get movies;
ListCopyWith<$R, SeriesModel,
SeriesModelCopyWith<$R, SeriesModel, SeriesModel>> get series;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{DateTime? dateOfBirth,
List<String>? birthPlace,
List<MovieModel>? movies,
List<SeriesModel>? series,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDownload,
bool? canDelete,
BaseItemKind? jellyType});
PersonModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _PersonModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, PersonModel, $Out>
implements PersonModelCopyWith<$R, PersonModel, $Out> {
_PersonModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<PersonModel> $mapper =
PersonModelMapper.ensureInitialized();
@override
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get birthPlace =>
ListCopyWith($value.birthPlace, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(birthPlace: v));
@override
ListCopyWith<$R, MovieModel, MovieModelCopyWith<$R, MovieModel, MovieModel>>
get movies => ListCopyWith($value.movies, (v, t) => v.copyWith.$chain(t),
(v) => call(movies: v));
@override
ListCopyWith<$R, SeriesModel,
SeriesModelCopyWith<$R, SeriesModel, SeriesModel>>
get series => ListCopyWith($value.series, (v, t) => v.copyWith.$chain(t),
(v) => call(series: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? dateOfBirth = $none,
List<String>? birthPlace,
List<MovieModel>? movies,
List<SeriesModel>? series,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (dateOfBirth != $none) #dateOfBirth: dateOfBirth,
if (birthPlace != null) #birthPlace: birthPlace,
if (movies != null) #movies: movies,
if (series != null) #series: series,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
PersonModel $make(CopyWithData data) => PersonModel(
dateOfBirth: data.get(#dateOfBirth, or: $value.dateOfBirth),
birthPlace: data.get(#birthPlace, or: $value.birthPlace),
movies: data.get(#movies, or: $value.movies),
series: data.get(#series, or: $value.series),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
PersonModelCopyWith<$R2, PersonModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_PersonModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,154 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/util/refresh_state.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'photos_model.mapper.dart';
@MappableClass()
class PhotoAlbumModel extends ItemBaseModel with PhotoAlbumModelMappable {
final List<ItemBaseModel> photos;
const PhotoAlbumModel({
required this.photos,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
super.canDelete,
super.canDownload,
super.jellyType,
});
@override
bool get unWatched => userData.unPlayedItemCount != 0;
@override
bool get playAble => true;
@override
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
factory PhotoAlbumModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return PhotoAlbumModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
canDelete: item.canDelete,
canDownload: item.canDownload,
photos: [],
);
}
}
@MappableClass()
class PhotoModel extends ItemBaseModel with PhotoModelMappable {
final String? albumId;
final DateTime? dateTaken;
final ImagesData? thumbnail;
final FladderItemType internalType;
const PhotoModel({
required this.albumId,
required this.dateTaken,
required this.thumbnail,
required this.internalType,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.canDownload,
required super.canDelete,
super.jellyType,
});
@override
PhotoAlbumModel get parentBaseModel => PhotoAlbumModel(
photos: [],
name: "",
id: parentId ?? "",
overview: overview,
parentId: parentId,
playlistId: playlistId,
images: images,
childCount: childCount,
primaryRatio: primaryRatio,
userData: userData,
);
@override
ImagesData? get getPosters => thumbnail;
@override
bool get galleryItem => switch (internalType) {
FladderItemType.photo => albumId?.isNotEmpty == true,
FladderItemType.video => parentId?.isNotEmpty == true,
_ => false,
};
@override
bool get unWatched => false;
factory PhotoModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return PhotoModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
primaryRatio: double.tryParse(item.aspectRatio ?? "") ?? item.primaryImageAspectRatio ?? 1.0,
dateTaken: item.dateCreated,
albumId: item.albumId,
thumbnail: ImagesData.fromBaseItem(item, ref),
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
canDelete: item.canDelete,
canDownload: item.canDownload,
internalType: switch (item.type) {
BaseItemKind.video => FladderItemType.video,
_ => FladderItemType.photo,
},
);
}
String downloadPath(WidgetRef ref) {
return "${ref.read(userProvider)?.server}/Items/$id/Download?api_key=${ref.read(userProvider)?.credentials.token}";
}
Future<void> navigateToAlbum(BuildContext context) async {
if ((albumId ?? parentId) == null) {
fladderSnackbar(context, title: context.localized.notPartOfAlbum);
return;
}
await parentBaseModel.navigateTo(context);
if (context.mounted) context.refreshData();
}
}

View file

@ -0,0 +1,498 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'photos_model.dart';
class PhotoAlbumModelMapper extends SubClassMapperBase<PhotoAlbumModel> {
PhotoAlbumModelMapper._();
static PhotoAlbumModelMapper? _instance;
static PhotoAlbumModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = PhotoAlbumModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
ItemBaseModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'PhotoAlbumModel';
static List<ItemBaseModel> _$photos(PhotoAlbumModel v) => v.photos;
static const Field<PhotoAlbumModel, List<ItemBaseModel>> _f$photos =
Field('photos', _$photos);
static String _$name(PhotoAlbumModel v) => v.name;
static const Field<PhotoAlbumModel, String> _f$name = Field('name', _$name);
static String _$id(PhotoAlbumModel v) => v.id;
static const Field<PhotoAlbumModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(PhotoAlbumModel v) => v.overview;
static const Field<PhotoAlbumModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(PhotoAlbumModel v) => v.parentId;
static const Field<PhotoAlbumModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(PhotoAlbumModel v) => v.playlistId;
static const Field<PhotoAlbumModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(PhotoAlbumModel v) => v.images;
static const Field<PhotoAlbumModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(PhotoAlbumModel v) => v.childCount;
static const Field<PhotoAlbumModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(PhotoAlbumModel v) => v.primaryRatio;
static const Field<PhotoAlbumModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(PhotoAlbumModel v) => v.userData;
static const Field<PhotoAlbumModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDelete(PhotoAlbumModel v) => v.canDelete;
static const Field<PhotoAlbumModel, bool> _f$canDelete =
Field('canDelete', _$canDelete, opt: true);
static bool? _$canDownload(PhotoAlbumModel v) => v.canDownload;
static const Field<PhotoAlbumModel, bool> _f$canDownload =
Field('canDownload', _$canDownload, opt: true);
static dto.BaseItemKind? _$jellyType(PhotoAlbumModel v) => v.jellyType;
static const Field<PhotoAlbumModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<PhotoAlbumModel> fields = const {
#photos: _f$photos,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDelete: _f$canDelete,
#canDownload: _f$canDownload,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'PhotoAlbumModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static PhotoAlbumModel _instantiate(DecodingData data) {
return PhotoAlbumModel(
photos: data.dec(_f$photos),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDelete: data.dec(_f$canDelete),
canDownload: data.dec(_f$canDownload),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static PhotoAlbumModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PhotoAlbumModel>(map);
}
static PhotoAlbumModel fromJson(String json) {
return ensureInitialized().decodeJson<PhotoAlbumModel>(json);
}
}
mixin PhotoAlbumModelMappable {
String toJson() {
return PhotoAlbumModelMapper.ensureInitialized()
.encodeJson<PhotoAlbumModel>(this as PhotoAlbumModel);
}
Map<String, dynamic> toMap() {
return PhotoAlbumModelMapper.ensureInitialized()
.encodeMap<PhotoAlbumModel>(this as PhotoAlbumModel);
}
PhotoAlbumModelCopyWith<PhotoAlbumModel, PhotoAlbumModel, PhotoAlbumModel>
get copyWith => _PhotoAlbumModelCopyWithImpl(
this as PhotoAlbumModel, $identity, $identity);
@override
String toString() {
return PhotoAlbumModelMapper.ensureInitialized()
.stringifyValue(this as PhotoAlbumModel);
}
}
extension PhotoAlbumModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, PhotoAlbumModel, $Out> {
PhotoAlbumModelCopyWith<$R, PhotoAlbumModel, $Out> get $asPhotoAlbumModel =>
$base.as((v, t, t2) => _PhotoAlbumModelCopyWithImpl(v, t, t2));
}
abstract class PhotoAlbumModelCopyWith<$R, $In extends PhotoAlbumModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get photos;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{List<ItemBaseModel>? photos,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDelete,
bool? canDownload,
dto.BaseItemKind? jellyType});
PhotoAlbumModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t);
}
class _PhotoAlbumModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, PhotoAlbumModel, $Out>
implements PhotoAlbumModelCopyWith<$R, PhotoAlbumModel, $Out> {
_PhotoAlbumModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<PhotoAlbumModel> $mapper =
PhotoAlbumModelMapper.ensureInitialized();
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get photos => ListCopyWith($value.photos, (v, t) => v.copyWith.$chain(t),
(v) => call(photos: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{List<ItemBaseModel>? photos,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDelete = $none,
Object? canDownload = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (photos != null) #photos: photos,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDelete != $none) #canDelete: canDelete,
if (canDownload != $none) #canDownload: canDownload,
if (jellyType != $none) #jellyType: jellyType
}));
@override
PhotoAlbumModel $make(CopyWithData data) => PhotoAlbumModel(
photos: data.get(#photos, or: $value.photos),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDelete: data.get(#canDelete, or: $value.canDelete),
canDownload: data.get(#canDownload, or: $value.canDownload),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
PhotoAlbumModelCopyWith<$R2, PhotoAlbumModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_PhotoAlbumModelCopyWithImpl($value, $cast, t);
}
class PhotoModelMapper extends SubClassMapperBase<PhotoModel> {
PhotoModelMapper._();
static PhotoModelMapper? _instance;
static PhotoModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = PhotoModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'PhotoModel';
static String? _$albumId(PhotoModel v) => v.albumId;
static const Field<PhotoModel, String> _f$albumId =
Field('albumId', _$albumId);
static DateTime? _$dateTaken(PhotoModel v) => v.dateTaken;
static const Field<PhotoModel, DateTime> _f$dateTaken =
Field('dateTaken', _$dateTaken);
static ImagesData? _$thumbnail(PhotoModel v) => v.thumbnail;
static const Field<PhotoModel, ImagesData> _f$thumbnail =
Field('thumbnail', _$thumbnail);
static FladderItemType _$internalType(PhotoModel v) => v.internalType;
static const Field<PhotoModel, FladderItemType> _f$internalType =
Field('internalType', _$internalType);
static String _$name(PhotoModel v) => v.name;
static const Field<PhotoModel, String> _f$name = Field('name', _$name);
static String _$id(PhotoModel v) => v.id;
static const Field<PhotoModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(PhotoModel v) => v.overview;
static const Field<PhotoModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(PhotoModel v) => v.parentId;
static const Field<PhotoModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(PhotoModel v) => v.playlistId;
static const Field<PhotoModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(PhotoModel v) => v.images;
static const Field<PhotoModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(PhotoModel v) => v.childCount;
static const Field<PhotoModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(PhotoModel v) => v.primaryRatio;
static const Field<PhotoModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(PhotoModel v) => v.userData;
static const Field<PhotoModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDownload(PhotoModel v) => v.canDownload;
static const Field<PhotoModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static bool? _$canDelete(PhotoModel v) => v.canDelete;
static const Field<PhotoModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static dto.BaseItemKind? _$jellyType(PhotoModel v) => v.jellyType;
static const Field<PhotoModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<PhotoModel> fields = const {
#albumId: _f$albumId,
#dateTaken: _f$dateTaken,
#thumbnail: _f$thumbnail,
#internalType: _f$internalType,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'PhotoModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static PhotoModel _instantiate(DecodingData data) {
return PhotoModel(
albumId: data.dec(_f$albumId),
dateTaken: data.dec(_f$dateTaken),
thumbnail: data.dec(_f$thumbnail),
internalType: data.dec(_f$internalType),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static PhotoModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<PhotoModel>(map);
}
static PhotoModel fromJson(String json) {
return ensureInitialized().decodeJson<PhotoModel>(json);
}
}
mixin PhotoModelMappable {
String toJson() {
return PhotoModelMapper.ensureInitialized()
.encodeJson<PhotoModel>(this as PhotoModel);
}
Map<String, dynamic> toMap() {
return PhotoModelMapper.ensureInitialized()
.encodeMap<PhotoModel>(this as PhotoModel);
}
PhotoModelCopyWith<PhotoModel, PhotoModel, PhotoModel> get copyWith =>
_PhotoModelCopyWithImpl(this as PhotoModel, $identity, $identity);
@override
String toString() {
return PhotoModelMapper.ensureInitialized()
.stringifyValue(this as PhotoModel);
}
}
extension PhotoModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, PhotoModel, $Out> {
PhotoModelCopyWith<$R, PhotoModel, $Out> get $asPhotoModel =>
$base.as((v, t, t2) => _PhotoModelCopyWithImpl(v, t, t2));
}
abstract class PhotoModelCopyWith<$R, $In extends PhotoModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{String? albumId,
DateTime? dateTaken,
ImagesData? thumbnail,
FladderItemType? internalType,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDownload,
bool? canDelete,
dto.BaseItemKind? jellyType});
PhotoModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _PhotoModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, PhotoModel, $Out>
implements PhotoModelCopyWith<$R, PhotoModel, $Out> {
_PhotoModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<PhotoModel> $mapper =
PhotoModelMapper.ensureInitialized();
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? albumId = $none,
Object? dateTaken = $none,
Object? thumbnail = $none,
FladderItemType? internalType,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (albumId != $none) #albumId: albumId,
if (dateTaken != $none) #dateTaken: dateTaken,
if (thumbnail != $none) #thumbnail: thumbnail,
if (internalType != null) #internalType: internalType,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
PhotoModel $make(CopyWithData data) => PhotoModel(
albumId: data.get(#albumId, or: $value.albumId),
dateTaken: data.get(#dateTaken, or: $value.dateTaken),
thumbnail: data.get(#thumbnail, or: $value.thumbnail),
internalType: data.get(#internalType, or: $value.internalType),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
PhotoModelCopyWith<$R2, PhotoModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_PhotoModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,98 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:collection/collection.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'season_model.mapper.dart';
@MappableClass()
class SeasonModel extends ItemBaseModel with SeasonModelMappable {
final ImagesData? parentImages;
final String seasonName;
final List<EpisodeModel> episodes;
final int episodeCount;
final String seriesId;
final String seriesName;
const SeasonModel({
required this.parentImages,
required this.seasonName,
this.episodes = const [],
required this.episodeCount,
required this.seriesId,
required this.seriesName,
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
required super.canDelete,
required super.canDownload,
super.jellyType,
});
factory SeasonModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return SeasonModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.seasonId ?? item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
seasonName: item.seasonName ?? "",
episodeCount: item.episodeCount ?? 0,
parentImages: ImagesData.fromBaseItemParent(item, ref, primary: const Size(2000, 2000)),
seriesId: item.seriesId ?? item.parentId ?? item.id ?? "",
canDelete: item.canDelete,
canDownload: item.canDownload,
seriesName: item.seriesName ?? "",
);
}
EpisodeModel? get nextUp {
return episodes.lastWhereOrNull((element) => element.userData.progress > 0) ??
episodes.firstWhereOrNull((element) => element.userData.played == false);
}
@override
ImagesData? get getPosters => images ?? parentImages;
String localizedName(BuildContext context) => name.replaceFirst("Season", context.localized.season(1));
@override
SeriesModel get parentBaseModel => SeriesModel(
originalTitle: '',
sortName: '',
status: "",
name: seriesName,
id: parentId ?? "",
playlistId: playlistId,
overview: overview,
parentId: parentId,
images: images,
childCount: childCount,
primaryRatio: primaryRatio,
userData: UserData(),
);
static List<SeasonModel> seasonsFromDto(List<dto.BaseItemDto>? dto, Ref ref) {
return dto?.map((e) => SeasonModel.fromBaseDto(e, ref)).toList() ?? [];
}
}

View file

@ -0,0 +1,287 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'season_model.dart';
class SeasonModelMapper extends SubClassMapperBase<SeasonModel> {
SeasonModelMapper._();
static SeasonModelMapper? _instance;
static SeasonModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = SeasonModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
EpisodeModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'SeasonModel';
static ImagesData? _$parentImages(SeasonModel v) => v.parentImages;
static const Field<SeasonModel, ImagesData> _f$parentImages =
Field('parentImages', _$parentImages);
static String _$seasonName(SeasonModel v) => v.seasonName;
static const Field<SeasonModel, String> _f$seasonName =
Field('seasonName', _$seasonName);
static List<EpisodeModel> _$episodes(SeasonModel v) => v.episodes;
static const Field<SeasonModel, List<EpisodeModel>> _f$episodes =
Field('episodes', _$episodes, opt: true, def: const []);
static int _$episodeCount(SeasonModel v) => v.episodeCount;
static const Field<SeasonModel, int> _f$episodeCount =
Field('episodeCount', _$episodeCount);
static String _$seriesId(SeasonModel v) => v.seriesId;
static const Field<SeasonModel, String> _f$seriesId =
Field('seriesId', _$seriesId);
static String _$seriesName(SeasonModel v) => v.seriesName;
static const Field<SeasonModel, String> _f$seriesName =
Field('seriesName', _$seriesName);
static String _$name(SeasonModel v) => v.name;
static const Field<SeasonModel, String> _f$name = Field('name', _$name);
static String _$id(SeasonModel v) => v.id;
static const Field<SeasonModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(SeasonModel v) => v.overview;
static const Field<SeasonModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(SeasonModel v) => v.parentId;
static const Field<SeasonModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(SeasonModel v) => v.playlistId;
static const Field<SeasonModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(SeasonModel v) => v.images;
static const Field<SeasonModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(SeasonModel v) => v.childCount;
static const Field<SeasonModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(SeasonModel v) => v.primaryRatio;
static const Field<SeasonModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(SeasonModel v) => v.userData;
static const Field<SeasonModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDelete(SeasonModel v) => v.canDelete;
static const Field<SeasonModel, bool> _f$canDelete =
Field('canDelete', _$canDelete);
static bool? _$canDownload(SeasonModel v) => v.canDownload;
static const Field<SeasonModel, bool> _f$canDownload =
Field('canDownload', _$canDownload);
static dto.BaseItemKind? _$jellyType(SeasonModel v) => v.jellyType;
static const Field<SeasonModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<SeasonModel> fields = const {
#parentImages: _f$parentImages,
#seasonName: _f$seasonName,
#episodes: _f$episodes,
#episodeCount: _f$episodeCount,
#seriesId: _f$seriesId,
#seriesName: _f$seriesName,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDelete: _f$canDelete,
#canDownload: _f$canDownload,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'SeasonModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static SeasonModel _instantiate(DecodingData data) {
return SeasonModel(
parentImages: data.dec(_f$parentImages),
seasonName: data.dec(_f$seasonName),
episodes: data.dec(_f$episodes),
episodeCount: data.dec(_f$episodeCount),
seriesId: data.dec(_f$seriesId),
seriesName: data.dec(_f$seriesName),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDelete: data.dec(_f$canDelete),
canDownload: data.dec(_f$canDownload),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static SeasonModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<SeasonModel>(map);
}
static SeasonModel fromJson(String json) {
return ensureInitialized().decodeJson<SeasonModel>(json);
}
}
mixin SeasonModelMappable {
String toJson() {
return SeasonModelMapper.ensureInitialized()
.encodeJson<SeasonModel>(this as SeasonModel);
}
Map<String, dynamic> toMap() {
return SeasonModelMapper.ensureInitialized()
.encodeMap<SeasonModel>(this as SeasonModel);
}
SeasonModelCopyWith<SeasonModel, SeasonModel, SeasonModel> get copyWith =>
_SeasonModelCopyWithImpl(this as SeasonModel, $identity, $identity);
@override
String toString() {
return SeasonModelMapper.ensureInitialized()
.stringifyValue(this as SeasonModel);
}
}
extension SeasonModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, SeasonModel, $Out> {
SeasonModelCopyWith<$R, SeasonModel, $Out> get $asSeasonModel =>
$base.as((v, t, t2) => _SeasonModelCopyWithImpl(v, t, t2));
}
abstract class SeasonModelCopyWith<$R, $In extends SeasonModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, EpisodeModel,
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>> get episodes;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{ImagesData? parentImages,
String? seasonName,
List<EpisodeModel>? episodes,
int? episodeCount,
String? seriesId,
String? seriesName,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDelete,
bool? canDownload,
dto.BaseItemKind? jellyType});
SeasonModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _SeasonModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, SeasonModel, $Out>
implements SeasonModelCopyWith<$R, SeasonModel, $Out> {
_SeasonModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<SeasonModel> $mapper =
SeasonModelMapper.ensureInitialized();
@override
ListCopyWith<$R, EpisodeModel,
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>
get episodes => ListCopyWith($value.episodes,
(v, t) => v.copyWith.$chain(t), (v) => call(episodes: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? parentImages = $none,
String? seasonName,
List<EpisodeModel>? episodes,
int? episodeCount,
String? seriesId,
String? seriesName,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDelete = $none,
Object? canDownload = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (parentImages != $none) #parentImages: parentImages,
if (seasonName != null) #seasonName: seasonName,
if (episodes != null) #episodes: episodes,
if (episodeCount != null) #episodeCount: episodeCount,
if (seriesId != null) #seriesId: seriesId,
if (seriesName != null) #seriesName: seriesName,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDelete != $none) #canDelete: canDelete,
if (canDownload != $none) #canDownload: canDownload,
if (jellyType != $none) #jellyType: jellyType
}));
@override
SeasonModel $make(CopyWithData data) => SeasonModel(
parentImages: data.get(#parentImages, or: $value.parentImages),
seasonName: data.get(#seasonName, or: $value.seasonName),
episodes: data.get(#episodes, or: $value.episodes),
episodeCount: data.get(#episodeCount, or: $value.episodeCount),
seriesId: data.get(#seriesId, or: $value.seriesId),
seriesName: data.get(#seriesName, or: $value.seriesName),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDelete: data.get(#canDelete, or: $value.canDelete),
canDownload: data.get(#canDownload, or: $value.canDownload),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
SeasonModelCopyWith<$R2, SeasonModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_SeasonModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,96 @@
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:fladder/models/items/season_model.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'series_model.mapper.dart';
@MappableClass()
class SeriesModel extends ItemBaseModel with SeriesModelMappable {
final List<EpisodeModel>? availableEpisodes;
final List<SeasonModel>? seasons;
final String originalTitle;
final String sortName;
final String status;
final List<ItemBaseModel> related;
const SeriesModel({
this.availableEpisodes,
this.seasons,
required this.originalTitle,
required this.sortName,
required this.status,
this.related = const [],
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
super.canDownload,
super.canDelete,
super.jellyType,
});
EpisodeModel? get nextUp => availableEpisodes?.nextUp ?? availableEpisodes?.firstOrNull;
@override
String detailedName(BuildContext context) => name;
@override
ItemBaseModel get parentBaseModel => this;
@override
Widget get detailScreenWidget => SeriesDetailScreen(item: this);
@override
bool get emptyShow => childCount == 0;
@override
bool get playAble => userData.unPlayedItemCount != 0;
@override
bool get identifiable => true;
@override
bool get unWatched =>
!userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0 && childCount != 0;
@override
String get subText => overview.yearAired?.toString() ?? "";
List<ItemBaseModel> fetchAllShows() {
return availableEpisodes?.map((e) => e).toList() ?? [];
}
@override
bool get syncAble => true;
factory SeriesModel.fromBaseDto(dto.BaseItemDto item, Ref ref) => SeriesModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
primaryRatio: item.primaryImageAspectRatio,
originalTitle: item.originalTitle ?? "",
sortName: item.sortName ?? "",
canDelete: item.canDelete,
canDownload: item.canDownload,
status: item.status ?? "Continuing",
);
}

View file

@ -0,0 +1,309 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'series_model.dart';
class SeriesModelMapper extends SubClassMapperBase<SeriesModel> {
SeriesModelMapper._();
static SeriesModelMapper? _instance;
static SeriesModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = SeriesModelMapper._());
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
EpisodeModelMapper.ensureInitialized();
SeasonModelMapper.ensureInitialized();
ItemBaseModelMapper.ensureInitialized();
OverviewModelMapper.ensureInitialized();
UserDataMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'SeriesModel';
static List<EpisodeModel>? _$availableEpisodes(SeriesModel v) =>
v.availableEpisodes;
static const Field<SeriesModel, List<EpisodeModel>> _f$availableEpisodes =
Field('availableEpisodes', _$availableEpisodes, opt: true);
static List<SeasonModel>? _$seasons(SeriesModel v) => v.seasons;
static const Field<SeriesModel, List<SeasonModel>> _f$seasons =
Field('seasons', _$seasons, opt: true);
static String _$originalTitle(SeriesModel v) => v.originalTitle;
static const Field<SeriesModel, String> _f$originalTitle =
Field('originalTitle', _$originalTitle);
static String _$sortName(SeriesModel v) => v.sortName;
static const Field<SeriesModel, String> _f$sortName =
Field('sortName', _$sortName);
static String _$status(SeriesModel v) => v.status;
static const Field<SeriesModel, String> _f$status = Field('status', _$status);
static List<ItemBaseModel> _$related(SeriesModel v) => v.related;
static const Field<SeriesModel, List<ItemBaseModel>> _f$related =
Field('related', _$related, opt: true, def: const []);
static String _$name(SeriesModel v) => v.name;
static const Field<SeriesModel, String> _f$name = Field('name', _$name);
static String _$id(SeriesModel v) => v.id;
static const Field<SeriesModel, String> _f$id = Field('id', _$id);
static OverviewModel _$overview(SeriesModel v) => v.overview;
static const Field<SeriesModel, OverviewModel> _f$overview =
Field('overview', _$overview);
static String? _$parentId(SeriesModel v) => v.parentId;
static const Field<SeriesModel, String> _f$parentId =
Field('parentId', _$parentId);
static String? _$playlistId(SeriesModel v) => v.playlistId;
static const Field<SeriesModel, String> _f$playlistId =
Field('playlistId', _$playlistId);
static ImagesData? _$images(SeriesModel v) => v.images;
static const Field<SeriesModel, ImagesData> _f$images =
Field('images', _$images);
static int? _$childCount(SeriesModel v) => v.childCount;
static const Field<SeriesModel, int> _f$childCount =
Field('childCount', _$childCount);
static double? _$primaryRatio(SeriesModel v) => v.primaryRatio;
static const Field<SeriesModel, double> _f$primaryRatio =
Field('primaryRatio', _$primaryRatio);
static UserData _$userData(SeriesModel v) => v.userData;
static const Field<SeriesModel, UserData> _f$userData =
Field('userData', _$userData);
static bool? _$canDownload(SeriesModel v) => v.canDownload;
static const Field<SeriesModel, bool> _f$canDownload =
Field('canDownload', _$canDownload, opt: true);
static bool? _$canDelete(SeriesModel v) => v.canDelete;
static const Field<SeriesModel, bool> _f$canDelete =
Field('canDelete', _$canDelete, opt: true);
static dto.BaseItemKind? _$jellyType(SeriesModel v) => v.jellyType;
static const Field<SeriesModel, dto.BaseItemKind> _f$jellyType =
Field('jellyType', _$jellyType, opt: true);
@override
final MappableFields<SeriesModel> fields = const {
#availableEpisodes: _f$availableEpisodes,
#seasons: _f$seasons,
#originalTitle: _f$originalTitle,
#sortName: _f$sortName,
#status: _f$status,
#related: _f$related,
#name: _f$name,
#id: _f$id,
#overview: _f$overview,
#parentId: _f$parentId,
#playlistId: _f$playlistId,
#images: _f$images,
#childCount: _f$childCount,
#primaryRatio: _f$primaryRatio,
#userData: _f$userData,
#canDownload: _f$canDownload,
#canDelete: _f$canDelete,
#jellyType: _f$jellyType,
};
@override
final bool ignoreNull = true;
@override
final String discriminatorKey = 'type';
@override
final dynamic discriminatorValue = 'SeriesModel';
@override
late final ClassMapperBase superMapper =
ItemBaseModelMapper.ensureInitialized();
static SeriesModel _instantiate(DecodingData data) {
return SeriesModel(
availableEpisodes: data.dec(_f$availableEpisodes),
seasons: data.dec(_f$seasons),
originalTitle: data.dec(_f$originalTitle),
sortName: data.dec(_f$sortName),
status: data.dec(_f$status),
related: data.dec(_f$related),
name: data.dec(_f$name),
id: data.dec(_f$id),
overview: data.dec(_f$overview),
parentId: data.dec(_f$parentId),
playlistId: data.dec(_f$playlistId),
images: data.dec(_f$images),
childCount: data.dec(_f$childCount),
primaryRatio: data.dec(_f$primaryRatio),
userData: data.dec(_f$userData),
canDownload: data.dec(_f$canDownload),
canDelete: data.dec(_f$canDelete),
jellyType: data.dec(_f$jellyType));
}
@override
final Function instantiate = _instantiate;
static SeriesModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<SeriesModel>(map);
}
static SeriesModel fromJson(String json) {
return ensureInitialized().decodeJson<SeriesModel>(json);
}
}
mixin SeriesModelMappable {
String toJson() {
return SeriesModelMapper.ensureInitialized()
.encodeJson<SeriesModel>(this as SeriesModel);
}
Map<String, dynamic> toMap() {
return SeriesModelMapper.ensureInitialized()
.encodeMap<SeriesModel>(this as SeriesModel);
}
SeriesModelCopyWith<SeriesModel, SeriesModel, SeriesModel> get copyWith =>
_SeriesModelCopyWithImpl(this as SeriesModel, $identity, $identity);
@override
String toString() {
return SeriesModelMapper.ensureInitialized()
.stringifyValue(this as SeriesModel);
}
}
extension SeriesModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, SeriesModel, $Out> {
SeriesModelCopyWith<$R, SeriesModel, $Out> get $asSeriesModel =>
$base.as((v, t, t2) => _SeriesModelCopyWithImpl(v, t, t2));
}
abstract class SeriesModelCopyWith<$R, $In extends SeriesModel, $Out>
implements ItemBaseModelCopyWith<$R, $In, $Out> {
ListCopyWith<$R, EpisodeModel,
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>?
get availableEpisodes;
ListCopyWith<$R, SeasonModel,
SeasonModelCopyWith<$R, SeasonModel, SeasonModel>>? get seasons;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get related;
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
@override
UserDataCopyWith<$R, UserData, UserData> get userData;
@override
$R call(
{List<EpisodeModel>? availableEpisodes,
List<SeasonModel>? seasons,
String? originalTitle,
String? sortName,
String? status,
List<ItemBaseModel>? related,
String? name,
String? id,
OverviewModel? overview,
String? parentId,
String? playlistId,
ImagesData? images,
int? childCount,
double? primaryRatio,
UserData? userData,
bool? canDownload,
bool? canDelete,
dto.BaseItemKind? jellyType});
SeriesModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
}
class _SeriesModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, SeriesModel, $Out>
implements SeriesModelCopyWith<$R, SeriesModel, $Out> {
_SeriesModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<SeriesModel> $mapper =
SeriesModelMapper.ensureInitialized();
@override
ListCopyWith<$R, EpisodeModel,
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>?
get availableEpisodes => $value.availableEpisodes != null
? ListCopyWith($value.availableEpisodes!,
(v, t) => v.copyWith.$chain(t), (v) => call(availableEpisodes: v))
: null;
@override
ListCopyWith<$R, SeasonModel,
SeasonModelCopyWith<$R, SeasonModel, SeasonModel>>?
get seasons => $value.seasons != null
? ListCopyWith($value.seasons!, (v, t) => v.copyWith.$chain(t),
(v) => call(seasons: v))
: null;
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get related => ListCopyWith($value.related,
(v, t) => v.copyWith.$chain(t), (v) => call(related: v));
@override
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
$value.overview.copyWith.$chain((v) => call(overview: v));
@override
UserDataCopyWith<$R, UserData, UserData> get userData =>
$value.userData.copyWith.$chain((v) => call(userData: v));
@override
$R call(
{Object? availableEpisodes = $none,
Object? seasons = $none,
String? originalTitle,
String? sortName,
String? status,
List<ItemBaseModel>? related,
String? name,
String? id,
OverviewModel? overview,
Object? parentId = $none,
Object? playlistId = $none,
Object? images = $none,
Object? childCount = $none,
Object? primaryRatio = $none,
UserData? userData,
Object? canDownload = $none,
Object? canDelete = $none,
Object? jellyType = $none}) =>
$apply(FieldCopyWithData({
if (availableEpisodes != $none) #availableEpisodes: availableEpisodes,
if (seasons != $none) #seasons: seasons,
if (originalTitle != null) #originalTitle: originalTitle,
if (sortName != null) #sortName: sortName,
if (status != null) #status: status,
if (related != null) #related: related,
if (name != null) #name: name,
if (id != null) #id: id,
if (overview != null) #overview: overview,
if (parentId != $none) #parentId: parentId,
if (playlistId != $none) #playlistId: playlistId,
if (images != $none) #images: images,
if (childCount != $none) #childCount: childCount,
if (primaryRatio != $none) #primaryRatio: primaryRatio,
if (userData != null) #userData: userData,
if (canDownload != $none) #canDownload: canDownload,
if (canDelete != $none) #canDelete: canDelete,
if (jellyType != $none) #jellyType: jellyType
}));
@override
SeriesModel $make(CopyWithData data) => SeriesModel(
availableEpisodes:
data.get(#availableEpisodes, or: $value.availableEpisodes),
seasons: data.get(#seasons, or: $value.seasons),
originalTitle: data.get(#originalTitle, or: $value.originalTitle),
sortName: data.get(#sortName, or: $value.sortName),
status: data.get(#status, or: $value.status),
related: data.get(#related, or: $value.related),
name: data.get(#name, or: $value.name),
id: data.get(#id, or: $value.id),
overview: data.get(#overview, or: $value.overview),
parentId: data.get(#parentId, or: $value.parentId),
playlistId: data.get(#playlistId, or: $value.playlistId),
images: data.get(#images, or: $value.images),
childCount: data.get(#childCount, or: $value.childCount),
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
userData: data.get(#userData, or: $value.userData),
canDownload: data.get(#canDownload, or: $value.canDownload),
canDelete: data.get(#canDelete, or: $value.canDelete),
jellyType: data.get(#jellyType, or: $value.jellyType));
@override
SeriesModelCopyWith<$R2, SeriesModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_SeriesModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,61 @@
import 'dart:ui';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'trick_play_model.freezed.dart';
part 'trick_play_model.g.dart';
@freezed
class TrickPlayModel with _$TrickPlayModel {
factory TrickPlayModel({
required int width,
required int height,
required int tileWidth,
required int tileHeight,
required int thumbnailCount,
required Duration interval,
@Default([]) List<String> images,
}) = _TrickPlayModel;
const TrickPlayModel._();
int get imagesPerTile => tileWidth * tileHeight;
String? getTile(Duration position) {
final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount);
final int indexOfTile = (currentIndex ~/ imagesPerTile).clamp(0, images.length);
return images.elementAtOrNull(indexOfTile);
}
Offset offset(Duration position) {
final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount - 1);
final int tileIndex = currentIndex % imagesPerTile;
final int column = tileIndex % tileWidth;
final int row = tileIndex ~/ tileWidth;
return Offset((width * column).toDouble(), (height * row).toDouble());
}
static Map<String, TrickPlayModel> toTrickPlayMap(Map<String, dynamic> map) {
Map<String, TrickPlayModel> newMap = {};
final firstMap = (((map.entries.first as MapEntry).value as Map<String, dynamic>));
newMap.addEntries(firstMap.entries.map(
(e) {
final map = e.value as Map<String, dynamic>;
return MapEntry(
e.key,
TrickPlayModel(
width: map['Width'] as int? ?? 0,
height: map['Height'] as int? ?? 0,
tileWidth: map['TileWidth'] as int? ?? 0,
tileHeight: map['TileHeight'] as int? ?? 0,
thumbnailCount: map['ThumbnailCount'] as int? ?? 0,
interval: Duration(milliseconds: map['Interval'] as int? ?? 0),
),
);
},
));
return newMap;
}
factory TrickPlayModel.fromJson(Map<String, dynamic> json) => _$TrickPlayModelFromJson(json);
}

View file

@ -0,0 +1,297 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'trick_play_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
TrickPlayModel _$TrickPlayModelFromJson(Map<String, dynamic> json) {
return _TrickPlayModel.fromJson(json);
}
/// @nodoc
mixin _$TrickPlayModel {
int get width => throw _privateConstructorUsedError;
int get height => throw _privateConstructorUsedError;
int get tileWidth => throw _privateConstructorUsedError;
int get tileHeight => throw _privateConstructorUsedError;
int get thumbnailCount => throw _privateConstructorUsedError;
Duration get interval => throw _privateConstructorUsedError;
List<String> get images => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TrickPlayModelCopyWith<TrickPlayModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TrickPlayModelCopyWith<$Res> {
factory $TrickPlayModelCopyWith(
TrickPlayModel value, $Res Function(TrickPlayModel) then) =
_$TrickPlayModelCopyWithImpl<$Res, TrickPlayModel>;
@useResult
$Res call(
{int width,
int height,
int tileWidth,
int tileHeight,
int thumbnailCount,
Duration interval,
List<String> images});
}
/// @nodoc
class _$TrickPlayModelCopyWithImpl<$Res, $Val extends TrickPlayModel>
implements $TrickPlayModelCopyWith<$Res> {
_$TrickPlayModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? width = null,
Object? height = null,
Object? tileWidth = null,
Object? tileHeight = null,
Object? thumbnailCount = null,
Object? interval = null,
Object? images = null,
}) {
return _then(_value.copyWith(
width: null == width
? _value.width
: width // ignore: cast_nullable_to_non_nullable
as int,
height: null == height
? _value.height
: height // ignore: cast_nullable_to_non_nullable
as int,
tileWidth: null == tileWidth
? _value.tileWidth
: tileWidth // ignore: cast_nullable_to_non_nullable
as int,
tileHeight: null == tileHeight
? _value.tileHeight
: tileHeight // ignore: cast_nullable_to_non_nullable
as int,
thumbnailCount: null == thumbnailCount
? _value.thumbnailCount
: thumbnailCount // ignore: cast_nullable_to_non_nullable
as int,
interval: null == interval
? _value.interval
: interval // ignore: cast_nullable_to_non_nullable
as Duration,
images: null == images
? _value.images
: images // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$TrickPlayModelImplCopyWith<$Res>
implements $TrickPlayModelCopyWith<$Res> {
factory _$$TrickPlayModelImplCopyWith(_$TrickPlayModelImpl value,
$Res Function(_$TrickPlayModelImpl) then) =
__$$TrickPlayModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int width,
int height,
int tileWidth,
int tileHeight,
int thumbnailCount,
Duration interval,
List<String> images});
}
/// @nodoc
class __$$TrickPlayModelImplCopyWithImpl<$Res>
extends _$TrickPlayModelCopyWithImpl<$Res, _$TrickPlayModelImpl>
implements _$$TrickPlayModelImplCopyWith<$Res> {
__$$TrickPlayModelImplCopyWithImpl(
_$TrickPlayModelImpl _value, $Res Function(_$TrickPlayModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? width = null,
Object? height = null,
Object? tileWidth = null,
Object? tileHeight = null,
Object? thumbnailCount = null,
Object? interval = null,
Object? images = null,
}) {
return _then(_$TrickPlayModelImpl(
width: null == width
? _value.width
: width // ignore: cast_nullable_to_non_nullable
as int,
height: null == height
? _value.height
: height // ignore: cast_nullable_to_non_nullable
as int,
tileWidth: null == tileWidth
? _value.tileWidth
: tileWidth // ignore: cast_nullable_to_non_nullable
as int,
tileHeight: null == tileHeight
? _value.tileHeight
: tileHeight // ignore: cast_nullable_to_non_nullable
as int,
thumbnailCount: null == thumbnailCount
? _value.thumbnailCount
: thumbnailCount // ignore: cast_nullable_to_non_nullable
as int,
interval: null == interval
? _value.interval
: interval // ignore: cast_nullable_to_non_nullable
as Duration,
images: null == images
? _value._images
: images // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TrickPlayModelImpl extends _TrickPlayModel {
_$TrickPlayModelImpl(
{required this.width,
required this.height,
required this.tileWidth,
required this.tileHeight,
required this.thumbnailCount,
required this.interval,
final List<String> images = const []})
: _images = images,
super._();
factory _$TrickPlayModelImpl.fromJson(Map<String, dynamic> json) =>
_$$TrickPlayModelImplFromJson(json);
@override
final int width;
@override
final int height;
@override
final int tileWidth;
@override
final int tileHeight;
@override
final int thumbnailCount;
@override
final Duration interval;
final List<String> _images;
@override
@JsonKey()
List<String> get images {
if (_images is EqualUnmodifiableListView) return _images;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_images);
}
@override
String toString() {
return 'TrickPlayModel(width: $width, height: $height, tileWidth: $tileWidth, tileHeight: $tileHeight, thumbnailCount: $thumbnailCount, interval: $interval, images: $images)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TrickPlayModelImpl &&
(identical(other.width, width) || other.width == width) &&
(identical(other.height, height) || other.height == height) &&
(identical(other.tileWidth, tileWidth) ||
other.tileWidth == tileWidth) &&
(identical(other.tileHeight, tileHeight) ||
other.tileHeight == tileHeight) &&
(identical(other.thumbnailCount, thumbnailCount) ||
other.thumbnailCount == thumbnailCount) &&
(identical(other.interval, interval) ||
other.interval == interval) &&
const DeepCollectionEquality().equals(other._images, _images));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
width,
height,
tileWidth,
tileHeight,
thumbnailCount,
interval,
const DeepCollectionEquality().hash(_images));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith =>
__$$TrickPlayModelImplCopyWithImpl<_$TrickPlayModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TrickPlayModelImplToJson(
this,
);
}
}
abstract class _TrickPlayModel extends TrickPlayModel {
factory _TrickPlayModel(
{required final int width,
required final int height,
required final int tileWidth,
required final int tileHeight,
required final int thumbnailCount,
required final Duration interval,
final List<String> images}) = _$TrickPlayModelImpl;
_TrickPlayModel._() : super._();
factory _TrickPlayModel.fromJson(Map<String, dynamic> json) =
_$TrickPlayModelImpl.fromJson;
@override
int get width;
@override
int get height;
@override
int get tileWidth;
@override
int get tileHeight;
@override
int get thumbnailCount;
@override
Duration get interval;
@override
List<String> get images;
@override
@JsonKey(ignore: true)
_$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'trick_play_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TrickPlayModelImpl _$$TrickPlayModelImplFromJson(Map<String, dynamic> json) =>
_$TrickPlayModelImpl(
width: (json['width'] as num).toInt(),
height: (json['height'] as num).toInt(),
tileWidth: (json['tileWidth'] as num).toInt(),
tileHeight: (json['tileHeight'] as num).toInt(),
thumbnailCount: (json['thumbnailCount'] as num).toInt(),
interval: Duration(microseconds: (json['interval'] as num).toInt()),
images: (json['images'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
);
Map<String, dynamic> _$$TrickPlayModelImplToJson(
_$TrickPlayModelImpl instance) =>
<String, dynamic>{
'width': instance.width,
'height': instance.height,
'tileWidth': instance.tileWidth,
'tileHeight': instance.tileHeight,
'thumbnailCount': instance.thumbnailCount,
'interval': instance.interval.inMicroseconds,
'images': instance.images,
};

View file

@ -0,0 +1,61 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart' as enums;
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/models/recommended_model.dart';
class LibraryModel {
final bool loading;
final String id;
final String name;
final List<PhotoModel> timelinePhotos;
final List<ItemBaseModel> posters;
final List<RecommendedModel> recommendations;
final List<String> genres;
final List<ItemBaseModel> latest;
final List<ItemBaseModel> nextUp;
final List<ItemBaseModel> favourites;
final enums.BaseItemKind type;
LibraryModel({
this.loading = false,
required this.id,
required this.name,
this.timelinePhotos = const [],
this.posters = const [],
this.recommendations = const [],
this.genres = const [],
this.latest = const [],
this.nextUp = const [],
this.favourites = const [],
required this.type,
});
LibraryModel copyWith({
bool? loading,
String? id,
String? name,
List<PhotoModel>? timelinePhotos,
List<ItemBaseModel>? posters,
List<RecommendedModel>? recommendations,
List<String>? genres,
List<ItemBaseModel>? latest,
List<ItemBaseModel>? nextUp,
List<ItemBaseModel>? favourites,
enums.BaseItemKind? type,
}) {
return LibraryModel(
loading: loading ?? this.loading,
id: id ?? this.id,
name: name ?? this.name,
timelinePhotos: timelinePhotos ?? this.timelinePhotos,
posters: posters ?? this.posters,
recommendations: recommendations ?? this.recommendations,
genres: genres ?? this.genres,
latest: latest ?? this.latest,
nextUp: nextUp ?? this.nextUp,
favourites: favourites ?? this.favourites,
type: type ?? this.type,
);
}
}

View file

@ -0,0 +1,223 @@
import 'package:collection/collection.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:fladder/util/list_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/view_model.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart';
part 'library_search_model.mapper.dart';
@MappableClass()
class LibrarySearchModel with LibrarySearchModelMappable {
final bool loading;
final bool selecteMode;
final String searchQuery;
final List<ItemBaseModel> folderOverwrite;
final Map<ViewModel, bool> views;
final List<ItemBaseModel> posters;
final List<ItemBaseModel> selectedPosters;
final Map<ItemFilter, bool> filters;
final Map<String, bool> genres;
final Map<Studio, bool> studios;
final Map<String, bool> tags;
final Map<int, bool> years;
final Map<String, bool> officialRatings;
final Map<FladderItemType, bool> types;
final SortingOptions sortingOption;
final SortingOrder sortOrder;
final bool favourites;
final bool hideEmtpyShows;
final bool recursive;
final GroupBy groupBy;
final Map<String, int> lastIndices;
final Map<String, int> libraryItemCounts;
final bool fetchingItems;
const LibrarySearchModel({
this.loading = false,
this.selecteMode = false,
this.folderOverwrite = const [],
this.searchQuery = "",
this.views = const {},
this.posters = const [],
this.selectedPosters = const [],
this.filters = const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false,
},
this.genres = const {},
this.studios = const {},
this.tags = const {},
this.years = const {},
this.officialRatings = const {},
this.types = const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: true,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoalbum: false,
FladderItemType.series: true,
FladderItemType.video: true,
},
this.favourites = false,
this.sortingOption = SortingOptions.name,
this.sortOrder = SortingOrder.ascending,
this.hideEmtpyShows = true,
this.recursive = false,
this.groupBy = GroupBy.none,
this.lastIndices = const {},
this.libraryItemCounts = const {},
this.fetchingItems = false,
});
bool get hasActiveFilters {
return genres.hasEnabled ||
studios.hasEnabled ||
tags.hasEnabled ||
years.hasEnabled ||
officialRatings.hasEnabled ||
hideEmtpyShows ||
filters.hasEnabled ||
favourites ||
searchQuery.isNotEmpty;
}
int get totalItemCount {
if (libraryItemCounts.isEmpty) return posters.length;
int totalCount = 0;
for (var item in libraryItemCounts.values) {
totalCount += item;
}
return totalCount;
}
bool get allDoneFetching {
if (libraryItemCounts.isEmpty) return false;
if (libraryItemCounts.length != lastIndices.length) {
return false;
} else {
for (var item in libraryItemCounts.entries) {
if (lastIndices[item.key] != item.value) {
return false;
}
}
}
return true;
}
String searchBarTitle(BuildContext context) {
if (folderOverwrite.isNotEmpty) {
return "${context.localized.search} ${folderOverwrite.last.name}...";
}
return views.included.length == 1
? "${context.localized.search} ${views.included.first.name}..."
: "${context.localized.search} ${context.localized.library(2)}...";
}
ItemBaseModel? get nestedCurrentItem => folderOverwrite.lastOrNull;
List<ItemBaseModel> get activePosters => selectedPosters.isNotEmpty ? selectedPosters : posters;
bool get showPlayButtons {
if (totalItemCount == 0) return false;
return types.included.isEmpty ||
types.included.containsAny(
{...FladderItemType.playable, FladderItemType.folder},
);
}
bool get showGalleryButtons {
if (totalItemCount == 0) return false;
return types.included.isEmpty ||
types.included.containsAny(
{...FladderItemType.galleryItem, FladderItemType.photoalbum, FladderItemType.folder},
);
}
LibrarySearchModel resetLazyLoad() {
return copyWith(
selectedPosters: [],
lastIndices: const {},
libraryItemCounts: const {},
);
}
LibrarySearchModel fullReset() {
return copyWith(
posters: [],
selectedPosters: [],
lastIndices: const {},
libraryItemCounts: const {},
);
}
LibrarySearchModel setFiltersToDefault() {
return copyWith(
genres: const {},
tags: const {},
officialRatings: const {},
years: const {},
searchQuery: '',
favourites: false,
recursive: false,
studios: const {},
hideEmtpyShows: true,
);
}
@override
bool operator ==(covariant LibrarySearchModel other) {
if (identical(this, other)) return true;
return other.searchQuery == searchQuery &&
listEquals(other.folderOverwrite, folderOverwrite) &&
mapEquals(other.views, views) &&
mapEquals(other.filters, filters) &&
mapEquals(other.genres, genres) &&
mapEquals(other.studios, studios) &&
mapEquals(other.tags, tags) &&
mapEquals(other.years, years) &&
mapEquals(other.officialRatings, officialRatings) &&
mapEquals(other.types, types) &&
other.sortingOption == sortingOption &&
other.sortOrder == sortOrder &&
other.favourites == favourites &&
other.recursive == recursive;
}
@override
int get hashCode {
return searchQuery.hashCode ^
folderOverwrite.hashCode ^
views.hashCode ^
posters.hashCode ^
selectedPosters.hashCode ^
filters.hashCode ^
genres.hashCode ^
studios.hashCode ^
tags.hashCode ^
years.hashCode ^
officialRatings.hashCode ^
types.hashCode ^
sortingOption.hashCode ^
sortOrder.hashCode ^
favourites.hashCode ^
recursive.hashCode;
}
}

View file

@ -0,0 +1,418 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'library_search_model.dart';
class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
LibrarySearchModelMapper._();
static LibrarySearchModelMapper? _instance;
static LibrarySearchModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = LibrarySearchModelMapper._());
ItemBaseModelMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'LibrarySearchModel';
static bool _$loading(LibrarySearchModel v) => v.loading;
static const Field<LibrarySearchModel, bool> _f$loading =
Field('loading', _$loading, opt: true, def: false);
static bool _$selecteMode(LibrarySearchModel v) => v.selecteMode;
static const Field<LibrarySearchModel, bool> _f$selecteMode =
Field('selecteMode', _$selecteMode, opt: true, def: false);
static List<ItemBaseModel> _$folderOverwrite(LibrarySearchModel v) =>
v.folderOverwrite;
static const Field<LibrarySearchModel, List<ItemBaseModel>>
_f$folderOverwrite =
Field('folderOverwrite', _$folderOverwrite, opt: true, def: const []);
static String _$searchQuery(LibrarySearchModel v) => v.searchQuery;
static const Field<LibrarySearchModel, String> _f$searchQuery =
Field('searchQuery', _$searchQuery, opt: true, def: "");
static Map<ViewModel, bool> _$views(LibrarySearchModel v) => v.views;
static const Field<LibrarySearchModel, Map<ViewModel, bool>> _f$views =
Field('views', _$views, opt: true, def: const {});
static List<ItemBaseModel> _$posters(LibrarySearchModel v) => v.posters;
static const Field<LibrarySearchModel, List<ItemBaseModel>> _f$posters =
Field('posters', _$posters, opt: true, def: const []);
static List<ItemBaseModel> _$selectedPosters(LibrarySearchModel v) =>
v.selectedPosters;
static const Field<LibrarySearchModel, List<ItemBaseModel>>
_f$selectedPosters =
Field('selectedPosters', _$selectedPosters, opt: true, def: const []);
static Map<ItemFilter, bool> _$filters(LibrarySearchModel v) => v.filters;
static const Field<LibrarySearchModel, Map<ItemFilter, bool>> _f$filters =
Field('filters', _$filters, opt: true, def: const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false
});
static Map<String, bool> _$genres(LibrarySearchModel v) => v.genres;
static const Field<LibrarySearchModel, Map<String, bool>> _f$genres =
Field('genres', _$genres, opt: true, def: const {});
static Map<Studio, bool> _$studios(LibrarySearchModel v) => v.studios;
static const Field<LibrarySearchModel, Map<Studio, bool>> _f$studios =
Field('studios', _$studios, opt: true, def: const {});
static Map<String, bool> _$tags(LibrarySearchModel v) => v.tags;
static const Field<LibrarySearchModel, Map<String, bool>> _f$tags =
Field('tags', _$tags, opt: true, def: const {});
static Map<int, bool> _$years(LibrarySearchModel v) => v.years;
static const Field<LibrarySearchModel, Map<int, bool>> _f$years =
Field('years', _$years, opt: true, def: const {});
static Map<String, bool> _$officialRatings(LibrarySearchModel v) =>
v.officialRatings;
static const Field<LibrarySearchModel, Map<String, bool>> _f$officialRatings =
Field('officialRatings', _$officialRatings, opt: true, def: const {});
static Map<FladderItemType, bool> _$types(LibrarySearchModel v) => v.types;
static const Field<LibrarySearchModel, Map<FladderItemType, bool>> _f$types =
Field('types', _$types, opt: true, def: const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: true,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoalbum: false,
FladderItemType.series: true,
FladderItemType.video: true
});
static bool _$favourites(LibrarySearchModel v) => v.favourites;
static const Field<LibrarySearchModel, bool> _f$favourites =
Field('favourites', _$favourites, opt: true, def: false);
static SortingOptions _$sortingOption(LibrarySearchModel v) =>
v.sortingOption;
static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption =
Field('sortingOption', _$sortingOption,
opt: true, def: SortingOptions.name);
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
static bool _$hideEmtpyShows(LibrarySearchModel v) => v.hideEmtpyShows;
static const Field<LibrarySearchModel, bool> _f$hideEmtpyShows =
Field('hideEmtpyShows', _$hideEmtpyShows, opt: true, def: true);
static bool _$recursive(LibrarySearchModel v) => v.recursive;
static const Field<LibrarySearchModel, bool> _f$recursive =
Field('recursive', _$recursive, opt: true, def: false);
static GroupBy _$groupBy(LibrarySearchModel v) => v.groupBy;
static const Field<LibrarySearchModel, GroupBy> _f$groupBy =
Field('groupBy', _$groupBy, opt: true, def: GroupBy.none);
static Map<String, int> _$lastIndices(LibrarySearchModel v) => v.lastIndices;
static const Field<LibrarySearchModel, Map<String, int>> _f$lastIndices =
Field('lastIndices', _$lastIndices, opt: true, def: const {});
static Map<String, int> _$libraryItemCounts(LibrarySearchModel v) =>
v.libraryItemCounts;
static const Field<LibrarySearchModel, Map<String, int>>
_f$libraryItemCounts =
Field('libraryItemCounts', _$libraryItemCounts, opt: true, def: const {});
static bool _$fetchingItems(LibrarySearchModel v) => v.fetchingItems;
static const Field<LibrarySearchModel, bool> _f$fetchingItems =
Field('fetchingItems', _$fetchingItems, opt: true, def: false);
@override
final MappableFields<LibrarySearchModel> fields = const {
#loading: _f$loading,
#selecteMode: _f$selecteMode,
#folderOverwrite: _f$folderOverwrite,
#searchQuery: _f$searchQuery,
#views: _f$views,
#posters: _f$posters,
#selectedPosters: _f$selectedPosters,
#filters: _f$filters,
#genres: _f$genres,
#studios: _f$studios,
#tags: _f$tags,
#years: _f$years,
#officialRatings: _f$officialRatings,
#types: _f$types,
#favourites: _f$favourites,
#sortingOption: _f$sortingOption,
#sortOrder: _f$sortOrder,
#hideEmtpyShows: _f$hideEmtpyShows,
#recursive: _f$recursive,
#groupBy: _f$groupBy,
#lastIndices: _f$lastIndices,
#libraryItemCounts: _f$libraryItemCounts,
#fetchingItems: _f$fetchingItems,
};
@override
final bool ignoreNull = true;
static LibrarySearchModel _instantiate(DecodingData data) {
return LibrarySearchModel(
loading: data.dec(_f$loading),
selecteMode: data.dec(_f$selecteMode),
folderOverwrite: data.dec(_f$folderOverwrite),
searchQuery: data.dec(_f$searchQuery),
views: data.dec(_f$views),
posters: data.dec(_f$posters),
selectedPosters: data.dec(_f$selectedPosters),
filters: data.dec(_f$filters),
genres: data.dec(_f$genres),
studios: data.dec(_f$studios),
tags: data.dec(_f$tags),
years: data.dec(_f$years),
officialRatings: data.dec(_f$officialRatings),
types: data.dec(_f$types),
favourites: data.dec(_f$favourites),
sortingOption: data.dec(_f$sortingOption),
sortOrder: data.dec(_f$sortOrder),
hideEmtpyShows: data.dec(_f$hideEmtpyShows),
recursive: data.dec(_f$recursive),
groupBy: data.dec(_f$groupBy),
lastIndices: data.dec(_f$lastIndices),
libraryItemCounts: data.dec(_f$libraryItemCounts),
fetchingItems: data.dec(_f$fetchingItems));
}
@override
final Function instantiate = _instantiate;
static LibrarySearchModel fromMap(Map<String, dynamic> map) {
return ensureInitialized().decodeMap<LibrarySearchModel>(map);
}
static LibrarySearchModel fromJson(String json) {
return ensureInitialized().decodeJson<LibrarySearchModel>(json);
}
}
mixin LibrarySearchModelMappable {
String toJson() {
return LibrarySearchModelMapper.ensureInitialized()
.encodeJson<LibrarySearchModel>(this as LibrarySearchModel);
}
Map<String, dynamic> toMap() {
return LibrarySearchModelMapper.ensureInitialized()
.encodeMap<LibrarySearchModel>(this as LibrarySearchModel);
}
LibrarySearchModelCopyWith<LibrarySearchModel, LibrarySearchModel,
LibrarySearchModel>
get copyWith => _LibrarySearchModelCopyWithImpl(
this as LibrarySearchModel, $identity, $identity);
@override
String toString() {
return LibrarySearchModelMapper.ensureInitialized()
.stringifyValue(this as LibrarySearchModel);
}
}
extension LibrarySearchModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, LibrarySearchModel, $Out> {
LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out>
get $asLibrarySearchModel =>
$base.as((v, t, t2) => _LibrarySearchModelCopyWithImpl(v, t, t2));
}
abstract class LibrarySearchModelCopyWith<$R, $In extends LibrarySearchModel,
$Out> implements ClassCopyWith<$R, $In, $Out> {
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get folderOverwrite;
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get posters;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get selectedPosters;
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>> get filters;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres;
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags;
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
get officialRatings;
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
get types;
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices;
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
get libraryItemCounts;
$R call(
{bool? loading,
bool? selecteMode,
List<ItemBaseModel>? folderOverwrite,
String? searchQuery,
Map<ViewModel, bool>? views,
List<ItemBaseModel>? posters,
List<ItemBaseModel>? selectedPosters,
Map<ItemFilter, bool>? filters,
Map<String, bool>? genres,
Map<Studio, bool>? studios,
Map<String, bool>? tags,
Map<int, bool>? years,
Map<String, bool>? officialRatings,
Map<FladderItemType, bool>? types,
bool? favourites,
SortingOptions? sortingOption,
SortingOrder? sortOrder,
bool? hideEmtpyShows,
bool? recursive,
GroupBy? groupBy,
Map<String, int>? lastIndices,
Map<String, int>? libraryItemCounts,
bool? fetchingItems});
LibrarySearchModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t);
}
class _LibrarySearchModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, LibrarySearchModel, $Out>
implements LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out> {
_LibrarySearchModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<LibrarySearchModel> $mapper =
LibrarySearchModelMapper.ensureInitialized();
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get folderOverwrite => ListCopyWith($value.folderOverwrite,
(v, t) => v.copyWith.$chain(t), (v) => call(folderOverwrite: v));
@override
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views =>
MapCopyWith($value.views, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(views: v));
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get posters => ListCopyWith($value.posters,
(v, t) => v.copyWith.$chain(t), (v) => call(posters: v));
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get selectedPosters => ListCopyWith($value.selectedPosters,
(v, t) => v.copyWith.$chain(t), (v) => call(selectedPosters: v));
@override
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>>
get filters => MapCopyWith($value.filters,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(filters: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres =>
MapCopyWith($value.genres, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(genres: v));
@override
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios =>
MapCopyWith($value.studios, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(studios: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags =>
MapCopyWith($value.tags, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(tags: v));
@override
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years =>
MapCopyWith($value.years, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(years: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
get officialRatings => MapCopyWith(
$value.officialRatings,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(officialRatings: v));
@override
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
get types => MapCopyWith($value.types,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(types: v));
@override
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices =>
MapCopyWith($value.lastIndices, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(lastIndices: v));
@override
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
get libraryItemCounts => MapCopyWith(
$value.libraryItemCounts,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(libraryItemCounts: v));
@override
$R call(
{bool? loading,
bool? selecteMode,
List<ItemBaseModel>? folderOverwrite,
String? searchQuery,
Map<ViewModel, bool>? views,
List<ItemBaseModel>? posters,
List<ItemBaseModel>? selectedPosters,
Map<ItemFilter, bool>? filters,
Map<String, bool>? genres,
Map<Studio, bool>? studios,
Map<String, bool>? tags,
Map<int, bool>? years,
Map<String, bool>? officialRatings,
Map<FladderItemType, bool>? types,
bool? favourites,
SortingOptions? sortingOption,
SortingOrder? sortOrder,
bool? hideEmtpyShows,
bool? recursive,
GroupBy? groupBy,
Map<String, int>? lastIndices,
Map<String, int>? libraryItemCounts,
bool? fetchingItems}) =>
$apply(FieldCopyWithData({
if (loading != null) #loading: loading,
if (selecteMode != null) #selecteMode: selecteMode,
if (folderOverwrite != null) #folderOverwrite: folderOverwrite,
if (searchQuery != null) #searchQuery: searchQuery,
if (views != null) #views: views,
if (posters != null) #posters: posters,
if (selectedPosters != null) #selectedPosters: selectedPosters,
if (filters != null) #filters: filters,
if (genres != null) #genres: genres,
if (studios != null) #studios: studios,
if (tags != null) #tags: tags,
if (years != null) #years: years,
if (officialRatings != null) #officialRatings: officialRatings,
if (types != null) #types: types,
if (favourites != null) #favourites: favourites,
if (sortingOption != null) #sortingOption: sortingOption,
if (sortOrder != null) #sortOrder: sortOrder,
if (hideEmtpyShows != null) #hideEmtpyShows: hideEmtpyShows,
if (recursive != null) #recursive: recursive,
if (groupBy != null) #groupBy: groupBy,
if (lastIndices != null) #lastIndices: lastIndices,
if (libraryItemCounts != null) #libraryItemCounts: libraryItemCounts,
if (fetchingItems != null) #fetchingItems: fetchingItems
}));
@override
LibrarySearchModel $make(CopyWithData data) => LibrarySearchModel(
loading: data.get(#loading, or: $value.loading),
selecteMode: data.get(#selecteMode, or: $value.selecteMode),
folderOverwrite: data.get(#folderOverwrite, or: $value.folderOverwrite),
searchQuery: data.get(#searchQuery, or: $value.searchQuery),
views: data.get(#views, or: $value.views),
posters: data.get(#posters, or: $value.posters),
selectedPosters: data.get(#selectedPosters, or: $value.selectedPosters),
filters: data.get(#filters, or: $value.filters),
genres: data.get(#genres, or: $value.genres),
studios: data.get(#studios, or: $value.studios),
tags: data.get(#tags, or: $value.tags),
years: data.get(#years, or: $value.years),
officialRatings: data.get(#officialRatings, or: $value.officialRatings),
types: data.get(#types, or: $value.types),
favourites: data.get(#favourites, or: $value.favourites),
sortingOption: data.get(#sortingOption, or: $value.sortingOption),
sortOrder: data.get(#sortOrder, or: $value.sortOrder),
hideEmtpyShows: data.get(#hideEmtpyShows, or: $value.hideEmtpyShows),
recursive: data.get(#recursive, or: $value.recursive),
groupBy: data.get(#groupBy, or: $value.groupBy),
lastIndices: data.get(#lastIndices, or: $value.lastIndices),
libraryItemCounts:
data.get(#libraryItemCounts, or: $value.libraryItemCounts),
fetchingItems: data.get(#fetchingItems, or: $value.fetchingItems));
@override
LibrarySearchModelCopyWith<$R2, LibrarySearchModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_LibrarySearchModelCopyWithImpl($value, $cast, t);
}

View file

@ -0,0 +1,127 @@
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:flutter/material.dart';
enum SortingOptions {
name([ItemSortBy.name]),
communityRating([ItemSortBy.communityrating]),
// criticsRating([ItemSortBy.criticrating]),
parentalRating([ItemSortBy.officialrating]),
dateAdded([ItemSortBy.datecreated]),
dateLastContentAdded([ItemSortBy.datelastcontentadded]),
favorite([ItemSortBy.isfavoriteorliked]),
datePlayed([ItemSortBy.dateplayed]),
folders([ItemSortBy.isfolder]),
playCount([ItemSortBy.playcount]),
releaseDate([ItemSortBy.productionyear, ItemSortBy.premieredate]),
runTime([ItemSortBy.runtime]),
random([ItemSortBy.random]);
const SortingOptions(this.value);
final List<ItemSortBy> value;
List<ItemSortBy> get toSortBy => [...value, ItemSortBy.name];
String label(BuildContext context) => switch (this) {
name => context.localized.name,
communityRating => context.localized.communityRating,
parentalRating => context.localized.parentalRating,
dateAdded => context.localized.dateAdded,
dateLastContentAdded => context.localized.dateLastContentAdded,
favorite => context.localized.favorite,
datePlayed => context.localized.datePlayed,
folders => context.localized.folders,
playCount => context.localized.playCount,
releaseDate => context.localized.releaseDate,
runTime => context.localized.runTime,
random => context.localized.random,
};
}
enum GroupBy {
none,
name,
genres,
dateAdded,
tags,
releaseDate,
rating,
type;
String value(BuildContext context) => switch (this) {
GroupBy.none => context.localized.none,
GroupBy.name => context.localized.name,
GroupBy.genres => context.localized.genre(1),
GroupBy.dateAdded => context.localized.dateAdded,
GroupBy.tags => context.localized.tag(1),
GroupBy.releaseDate => context.localized.releaseDate,
GroupBy.rating => context.localized.rating(1),
GroupBy.type => context.localized.type(1),
};
}
enum SortingOrder {
ascending,
descending;
SortOrder get sortOrder => switch (this) {
ascending => SortOrder.ascending,
descending => SortOrder.descending,
};
String label(BuildContext context) => switch (this) {
ascending => context.localized.ascending,
descending => context.localized.descending,
};
}
extension ItemFilterExtension on ItemFilter {
String label(BuildContext context) {
return switch (this) {
ItemFilter.isplayed => context.localized.played,
ItemFilter.isunplayed => context.localized.unPlayed,
ItemFilter.isresumable => context.localized.resumable,
_ => "",
};
}
}
int sortItems(ItemBaseModel a, ItemBaseModel b, SortingOptions sortingOption, SortingOrder sortingOrder) {
for (var sortBy in sortingOption.toSortBy) {
int comparison = 0;
switch (sortBy) {
case ItemSortBy.communityrating:
comparison = (a.overview.communityRating ?? 0).compareTo(b.overview.communityRating ?? 0);
break;
case ItemSortBy.isfavoriteorliked:
comparison = a.userData.isFavourite == b.userData.isFavourite
? 0
: a.userData.isFavourite
? 1
: -1;
break;
case ItemSortBy.dateplayed:
comparison = (a.userData.lastPlayed ?? DateTime(0)).compareTo(b.userData.lastPlayed ?? DateTime(0));
break;
case ItemSortBy.playcount:
comparison = a.userData.playCount.compareTo(b.userData.playCount);
break;
case ItemSortBy.premieredate:
case ItemSortBy.productionyear:
comparison = (a.overview.productionYear ?? 0).compareTo(b.overview.productionYear ?? 0);
break;
case ItemSortBy.runtime:
comparison = (a.overview.runTime ?? Duration.zero).compareTo(b.overview.runTime ?? Duration.zero);
break;
default:
comparison = a.name.compareTo(b.name);
}
if (comparison != 0) {
return sortingOrder == SortingOrder.ascending ? comparison : -comparison;
}
}
return 0;
}

View file

@ -0,0 +1,26 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/models/account_model.dart';
import 'package:fladder/models/credentials_model.dart';
class LoginScreenModel {
final List<AccountModel> accounts;
final CredentialsModel tempCredentials;
final bool loading;
LoginScreenModel({
required this.accounts,
required this.tempCredentials,
required this.loading,
});
LoginScreenModel copyWith({
List<AccountModel>? accounts,
CredentialsModel? tempCredentials,
bool? loading,
}) {
return LoginScreenModel(
accounts: accounts ?? this.accounts,
tempCredentials: tempCredentials ?? this.tempCredentials,
loading: loading ?? this.loading,
);
}
}

View file

@ -0,0 +1,52 @@
enum VideoPlayerState {
minimized,
fullScreen,
disposed,
}
class MediaPlaybackModel {
final VideoPlayerState state;
final bool playing;
final Duration position;
final Duration lastPosition;
final Duration duration;
final Duration buffer;
final bool completed;
final bool errorPlaying;
final bool buffering;
MediaPlaybackModel({
this.state = VideoPlayerState.disposed,
this.playing = false,
this.position = Duration.zero,
this.lastPosition = Duration.zero,
this.duration = Duration.zero,
this.buffer = Duration.zero,
this.completed = false,
this.errorPlaying = false,
this.buffering = false,
});
MediaPlaybackModel copyWith({
VideoPlayerState? state,
bool? playing,
Duration? position,
Duration? lastPosition,
Duration? duration,
Duration? buffer,
bool? completed,
bool? errorPlaying,
bool? buffering,
}) {
return MediaPlaybackModel(
state: state ?? this.state,
playing: playing ?? this.playing,
position: position ?? this.position,
lastPosition: lastPosition ?? this.lastPosition,
duration: duration ?? this.duration,
buffer: buffer ?? this.buffer,
completed: completed ?? this.completed,
errorPlaying: errorPlaying ?? this.errorPlaying,
buffering: buffering ?? this.buffering,
);
}
}

View file

@ -0,0 +1,209 @@
import 'package:collection/collection.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart'
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/util/duration_extensions.dart';
class DirectPlaybackModel implements PlaybackModel {
DirectPlaybackModel({
required this.item,
required this.media,
required this.playbackInfo,
this.mediaStreams,
this.introSkipModel,
this.chapters,
this.trickPlay,
this.queue = const [],
});
@override
final ItemBaseModel item;
@override
final Media? media;
@override
final PlaybackInfoResponse playbackInfo;
@override
final MediaStreamsModel? mediaStreams;
@override
final IntroOutSkipModel? introSkipModel;
@override
final List<Chapter>? chapters;
@override
final TrickPlayModel? trickPlay;
@override
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
@override
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
@override
Future<Duration>? startDuration() async => item.userData.playBackPosition;
@override
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []];
List<QueueItem> get itemsInQueue =>
queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList();
@override
Future<DirectPlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
final wantedSubtitle =
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
if (wantedSubtitle == null) return this;
if (wantedSubtitle.index == SubStreamModel.no().index) {
await player.setSubtitleTrack(SubtitleTrack.no());
} else {
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
final subTrack = subTracks.elementAtOrNull(index);
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
} else if (subTrack != null) {
await player.setSubtitleTrack(subTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
}
@override
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
@override
Future<DirectPlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
final wantedAudioStream =
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
if (wantedAudioStream == null) return this;
if (wantedAudioStream.index == AudioStreamModel.no().index) {
await player.setAudioTrack(AudioTrack.no());
} else {
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
if (audioTrack != null) {
await player.setAudioTrack(audioTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
}
@override
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
await ref.read(jellyApiProvider).sessionsPlayingPost(
body: PlaybackStartInfo(
canSeek: true,
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
volumeLevel: 100,
playbackStartTimeTicks: position.toRuntimeTicks,
playMethod: PlayMethod.directplay,
isMuted: false,
isPaused: false,
repeatMode: RepeatMode.repeatall,
nowPlayingQueue: itemsInQueue,
),
);
return null;
}
@override
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
ref.read(playBackModel.notifier).update((state) => null);
await ref.read(jellyApiProvider).sessionsPlayingStoppedPost(
body: PlaybackStopInfo(
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
positionTicks: position.toRuntimeTicks,
failed: false,
nowPlayingQueue: itemsInQueue,
),
totalDuration: totalDuration,
);
return null;
}
@override
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
final api = ref.read(jellyApiProvider);
//Check for newly generated scrubImages
if (trickPlay == null) {
final trickplay = await api.getTrickPlay(item: item, ref: ref);
ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.body));
}
await api.sessionsPlayingProgressPost(
body: PlaybackProgressInfo(
canSeek: true,
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
volumeLevel: 100,
playMethod: PlayMethod.directplay,
isPaused: !isPlaying,
isMuted: false,
positionTicks: position.toRuntimeTicks,
repeatMode: RepeatMode.repeatall,
nowPlayingQueue: itemsInQueue,
),
);
return null;
}
@override
String toString() => 'DirectPlaybackModel(item: $item, playbackInfo: $playbackInfo)';
@override
final List<ItemBaseModel> queue;
@override
DirectPlaybackModel copyWith({
ItemBaseModel? item,
ValueGetter<Media?>? media,
ValueGetter<Duration>? lastPosition,
PlaybackInfoResponse? playbackInfo,
ValueGetter<MediaStreamsModel?>? mediaStreams,
ValueGetter<IntroOutSkipModel?>? introSkipModel,
ValueGetter<List<Chapter>?>? chapters,
ValueGetter<TrickPlayModel?>? trickPlay,
List<ItemBaseModel>? queue,
}) {
return DirectPlaybackModel(
item: item ?? this.item,
media: media != null ? media() : this.media,
playbackInfo: playbackInfo ?? this.playbackInfo,
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
chapters: chapters != null ? chapters() : this.chapters,
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
queue: queue ?? this.queue,
);
}
}

View file

@ -0,0 +1,175 @@
import 'package:collection/collection.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart'
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/util/duration_extensions.dart';
class OfflinePlaybackModel implements PlaybackModel {
OfflinePlaybackModel({
required this.item,
required this.media,
required this.syncedItem,
this.mediaStreams,
this.playbackInfo,
this.introSkipModel,
this.trickPlay,
this.queue = const [],
this.syncedQueue = const [],
});
@override
final ItemBaseModel item;
@override
final PlaybackInfoResponse? playbackInfo;
@override
final Media? media;
final SyncedItem syncedItem;
@override
final MediaStreamsModel? mediaStreams;
@override
final IntroOutSkipModel? introSkipModel;
@override
List<Chapter>? get chapters => syncedItem.chapters;
@override
final TrickPlayModel? trickPlay;
@override
Future<Duration>? startDuration() async => item.userData.playBackPosition;
@override
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
@override
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
@override
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...syncedItem.subtitles];
@override
Future<OfflinePlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
final wantedSubtitle =
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
if (wantedSubtitle == null) return this;
if (wantedSubtitle.index == SubStreamModel.no().index) {
await player.setSubtitleTrack(SubtitleTrack.no());
} else {
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
final subTrack = subTracks.elementAtOrNull(index);
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
} else if (subTrack != null) {
await player.setSubtitleTrack(subTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
}
@override
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
@override
Future<OfflinePlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
final wantedAudioStream =
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
if (wantedAudioStream == null) return this;
if (wantedAudioStream.index == AudioStreamModel.no().index) {
await player.setAudioTrack(AudioTrack.no());
} else {
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
if (audioTrack != null) {
await player.setAudioTrack(audioTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
}
@override
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
return null;
}
@override
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
return null;
}
@override
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
final progress = position.inMilliseconds / (item.overview.runTime?.inMilliseconds ?? 0) * 100;
final newItem = syncedItem.copyWith(
userData: syncedItem.userData?.copyWith(
playbackPositionTicks: position.toRuntimeTicks,
progress: progress,
played: isPlayed(position, item.overview.runTime ?? Duration.zero),
),
);
await ref.read(syncProvider.notifier).updateItem(newItem);
return this;
}
bool isPlayed(Duration position, Duration totalDuration) {
Duration startBuffer = totalDuration * 0.05;
Duration endBuffer = totalDuration * 0.90;
Duration validStart = startBuffer;
Duration validEnd = endBuffer;
if (position >= validStart && position <= validEnd) {
return true;
}
return false;
}
@override
String toString() => 'OfflinePlaybackModel(item: $item, syncedItem: $syncedItem)';
@override
final List<ItemBaseModel> queue;
final List<SyncedItem> syncedQueue;
@override
OfflinePlaybackModel copyWith({
ItemBaseModel? item,
ValueGetter<Media?>? media,
SyncedItem? syncedItem,
ValueGetter<MediaStreamsModel?>? mediaStreams,
ValueGetter<IntroOutSkipModel?>? introSkipModel,
ValueGetter<TrickPlayModel?>? trickPlay,
List<ItemBaseModel>? queue,
List<SyncedItem>? syncedQueue,
}) {
return OfflinePlaybackModel(
item: item ?? this.item,
media: media != null ? media() : this.media,
syncedItem: syncedItem ?? this.syncedItem,
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
queue: queue ?? this.queue,
syncedQueue: syncedQueue ?? this.syncedQueue,
);
}
}

View file

@ -0,0 +1,370 @@
import 'dart:developer';
import 'package:chopper/chopper.dart';
import 'package:collection/collection.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/season_model.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/playback/direct_playback_model.dart';
import 'package:fladder/models/playback/offline_playback_model.dart';
import 'package:fladder/models/playback/transcode_playback_model.dart';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/models/video_stream_model.dart';
import 'package:fladder/profiles/default_profile.dart';
import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/service_provider.dart';
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/util/duration_extensions.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart'
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
extension PlaybackModelExtension on PlaybackModel? {
String? get label => switch (this) {
DirectPlaybackModel _ => PlaybackType.directStream.name,
TranscodePlaybackModel _ => PlaybackType.transcode.name,
OfflinePlaybackModel _ => PlaybackType.offline.name,
_ => null
};
}
abstract class PlaybackModel {
final ItemBaseModel item = throw UnimplementedError();
final Media? media = throw UnimplementedError();
final List<ItemBaseModel> queue = const [];
final IntroOutSkipModel? introSkipModel = null;
final PlaybackInfoResponse? playbackInfo = throw UnimplementedError();
List<Chapter>? get chapters;
TrickPlayModel? get trickPlay;
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) =>
throw UnimplementedError();
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) => throw UnimplementedError();
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) =>
throw UnimplementedError();
final MediaStreamsModel? mediaStreams = throw UnimplementedError();
List<SubStreamModel>? get subStreams;
List<AudioStreamModel>? get audioStreams;
Future<Duration>? startDuration() async => item.userData.playBackPosition;
Future<PlaybackModel>? setSubtitle(SubStreamModel? model, MediaControlsWrapper player) {
return null;
}
Future<PlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) => null;
ItemBaseModel? get nextVideo => throw UnimplementedError();
ItemBaseModel? get previousVideo => throw UnimplementedError();
PlaybackModel copyWith();
}
final playbackModelHelper = Provider<PlaybackModelHelper>((ref) {
return PlaybackModelHelper(ref: ref);
});
class PlaybackModelHelper {
const PlaybackModelHelper({required this.ref});
final Ref ref;
JellyService get api => ref.read(jellyApiProvider);
Future<PlaybackModel?> loadNewVideo(ItemBaseModel newItem) async {
ref.read(videoPlayerProvider).pause();
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(buffering: true));
final currentModel = ref.read(playBackModel);
final newModel = (await createServerPlaybackModel(
newItem,
null,
oldModel: currentModel,
)) ??
await createOfflinePlaybackModel(
newItem,
ref.read(syncProvider.notifier).getSyncedItem(newItem),
oldModel: currentModel,
);
if (newModel == null) return null;
ref.read(videoPlayerProvider.notifier).loadPlaybackItem(newModel, startPosition: Duration.zero);
return newModel;
}
Future<OfflinePlaybackModel?> createOfflinePlaybackModel(
ItemBaseModel item,
SyncedItem? syncedItem, {
PlaybackModel? oldModel,
}) async {
final ItemBaseModel? syncedItemModel = ref.read(syncProvider.notifier).getItem(syncedItem);
if (syncedItemModel == null || syncedItem == null || !syncedItem.dataFile.existsSync()) return null;
final children = ref.read(syncChildrenProvider(syncedItem));
final syncedItems = children.where((element) => element.videoFile.existsSync()).toList();
final itemQueue = syncedItems.map((e) => e.createItemModel(ref));
return OfflinePlaybackModel(
item: syncedItemModel,
syncedItem: syncedItem,
trickPlay: syncedItem.trickPlayModel,
introSkipModel: syncedItem.introOutSkipModel,
media: Media(syncedItem.videoFile.path),
queue: itemQueue.whereNotNull().toList(),
syncedQueue: children,
mediaStreams: item.streamModel ?? syncedItemModel.streamModel,
);
}
Future<EpisodeModel?> getNextUpEpisode(String itemId) async {
final responnse = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]);
final episode = responnse.body?.items?.firstOrNull;
if (episode == null) {
return null;
} else {
return EpisodeModel.fromBaseDto(episode, ref);
}
}
Future<PlaybackModel?> createServerPlaybackModel(ItemBaseModel? item, PlaybackType? type,
{PlaybackModel? oldModel, List<ItemBaseModel>? libraryQueue, Duration? startPosition}) async {
try {
if (item == null) return null;
final userId = ref.read(userProvider)?.id;
if (userId?.isEmpty == true) return null;
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
final firstItemToPlay = switch (item) {
SeriesModel _ || SeasonModel _ => (await getNextUpEpisode(item.id) ?? queue.first),
_ => item,
};
final fullItem = await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id);
final streamModel = firstItemToPlay.streamModel;
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: firstItemToPlay.id,
enableDirectPlay: type != PlaybackType.transcode,
enableDirectStream: type != PlaybackType.transcode,
enableTranscoding: true,
autoOpenLiveStream: true,
startTimeTicks: startPosition?.toRuntimeTicks,
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
mediaSourceId: firstItemToPlay.id,
body: PlaybackInfoDto(
startTimeTicks: startPosition?.toRuntimeTicks,
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
enableTranscoding: true,
autoOpenLiveStream: true,
deviceProfile: defaultProfile,
userId: userId,
mediaSourceId: firstItemToPlay.id,
),
);
PlaybackInfoResponse? playbackInfo = response.body;
if (playbackInfo == null) return null;
final mediaSource = playbackInfo.mediaSources?.first;
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(
playbackInfo.mediaSources?.firstOrNull, playbackInfo.mediaSources?.firstOrNull?.mediaStreams ?? [], ref)
.copyWith(
defaultAudioStreamIndex: streamModel?.defaultAudioStreamIndex,
defaultSubStreamIndex: streamModel?.defaultSubStreamIndex,
);
final intro = await api.introSkipGet(id: item.id);
final trickPlay = (await api.getTrickPlay(item: fullItem.body, ref: ref))?.body;
final chapters = fullItem.body?.overview.chapters ?? [];
if (mediaSource == null) return null;
if ((mediaSource.supportsDirectStream ?? false) || (mediaSource.supportsDirectPlay ?? false)) {
final Map<String, String?> directOptions = {
'Static': 'true',
'mediaSourceId': mediaSource.id,
'api_key': ref.read(userProvider)?.credentials.token,
};
if (mediaSource.eTag != null) {
directOptions['Tag'] = mediaSource.eTag;
}
if (mediaSource.liveStreamId != null) {
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
}
final params = Uri(queryParameters: directOptions).query;
return DirectPlaybackModel(
item: fullItem.body ?? item,
queue: queue,
introSkipModel: intro?.body,
chapters: chapters,
playbackInfo: playbackInfo,
trickPlay: trickPlay,
media: Media('${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params'),
mediaStreams: mediaStreamsWithUrls,
);
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
return TranscodePlaybackModel(
item: fullItem.body ?? item,
queue: queue,
introSkipModel: intro?.body,
chapters: chapters,
trickPlay: trickPlay,
playbackInfo: playbackInfo,
media: Media("${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}"),
mediaStreams: mediaStreamsWithUrls,
);
}
return null;
} catch (e) {
log(e.toString());
return null;
}
}
Future<List<ItemBaseModel>> collectQueue(ItemBaseModel model) async {
switch (model) {
case EpisodeModel _:
case SeriesModel _:
case SeasonModel _:
List<EpisodeModel> episodeList = ((await fetchEpisodesFromSeries(model.streamId)).body ?? [])
..removeWhere((element) => element.status != EpisodeStatus.available);
return episodeList;
default:
return [];
}
}
Future<Response<List<EpisodeModel>>> fetchEpisodesFromSeries(String seriesId) async {
final response = await api.showsSeriesIdEpisodesGet(
seriesId: seriesId,
fields: [
ItemFields.overview,
ItemFields.originaltitle,
ItemFields.mediastreams,
ItemFields.mediasources,
ItemFields.mediasourcecount,
ItemFields.width,
ItemFields.height,
],
);
return Response(response.base, (response.body?.items?.map((e) => EpisodeModel.fromBaseDto(e, ref)).toList() ?? []));
}
Future<void> shouldReload(PlaybackModel playbackModel) async {
if (playbackModel is OfflinePlaybackModel) {
return;
}
final item = playbackModel.item;
final userId = ref.read(userProvider)?.id;
if (userId?.isEmpty == true) return;
final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position));
final audioIndex = playbackModel.mediaStreams?.defaultAudioStreamIndex;
final subIndex = playbackModel.mediaStreams?.defaultSubStreamIndex;
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
itemId: item.id,
enableDirectPlay: true,
enableDirectStream: true,
enableTranscoding: true,
autoOpenLiveStream: true,
startTimeTicks: currentPosition.toRuntimeTicks,
audioStreamIndex: audioIndex,
subtitleStreamIndex: subIndex,
mediaSourceId: item.id,
body: PlaybackInfoDto(
startTimeTicks: currentPosition.toRuntimeTicks,
audioStreamIndex: audioIndex,
subtitleStreamIndex: subIndex,
enableTranscoding: true,
autoOpenLiveStream: true,
deviceProfile: defaultProfile,
userId: userId,
mediaSourceId: item.id,
),
);
PlaybackInfoResponse playbackInfo = response.bodyOrThrow;
final mediaSource = playbackInfo.mediaSources?.first;
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(
playbackInfo.mediaSources?.firstOrNull, playbackInfo.mediaSources?.firstOrNull?.mediaStreams ?? [], ref)
.copyWith(
defaultAudioStreamIndex: audioIndex,
defaultSubStreamIndex: subIndex,
);
if (mediaSource == null) return;
PlaybackModel? newModel;
if ((mediaSource.supportsDirectStream ?? false) || (mediaSource.supportsDirectPlay ?? false)) {
final Map<String, String?> directOptions = {
'Static': 'true',
'mediaSourceId': mediaSource.id,
'api_key': ref.read(userProvider)?.credentials.token,
};
if (mediaSource.eTag != null) {
directOptions['Tag'] = mediaSource.eTag;
}
if (mediaSource.liveStreamId != null) {
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
}
final params = Uri(queryParameters: directOptions).query;
final directPlay = '${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params';
newModel = DirectPlaybackModel(
item: playbackModel.item,
queue: playbackModel.queue,
introSkipModel: playbackModel.introSkipModel,
chapters: playbackModel.chapters,
playbackInfo: playbackInfo,
trickPlay: playbackModel.trickPlay,
media: Media(directPlay),
mediaStreams: mediaStreamsWithUrls,
);
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
newModel = TranscodePlaybackModel(
item: playbackModel.item,
queue: playbackModel.queue,
introSkipModel: playbackModel.introSkipModel,
chapters: playbackModel.chapters,
playbackInfo: playbackInfo,
trickPlay: playbackModel.trickPlay,
media: Media("${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}"),
mediaStreams: mediaStreamsWithUrls,
);
}
if (newModel == null) return;
if (newModel.runtimeType != playbackModel.runtimeType || newModel is TranscodePlaybackModel) {
ref.read(videoPlayerProvider.notifier).loadPlaybackItem(newModel, startPosition: currentPosition);
}
}
}

View file

@ -0,0 +1,210 @@
import 'package:collection/collection.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/wrappers/media_control_wrapper.dart'
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/playback/playback_model.dart';
import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/util/duration_extensions.dart';
class TranscodePlaybackModel implements PlaybackModel {
TranscodePlaybackModel({
required this.item,
required this.media,
required this.playbackInfo,
this.mediaStreams,
this.introSkipModel,
this.chapters,
this.trickPlay,
this.queue = const [],
});
@override
final ItemBaseModel item;
@override
final Media? media;
@override
final PlaybackInfoResponse playbackInfo;
@override
final MediaStreamsModel? mediaStreams;
@override
final IntroOutSkipModel? introSkipModel;
@override
final List<Chapter>? chapters;
@override
final TrickPlayModel? trickPlay;
@override
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
@override
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
@override
Future<Duration>? startDuration() async => item.userData.playBackPosition;
@override
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []];
List<QueueItem> get itemsInQueue =>
queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList();
@override
Future<TranscodePlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
final wantedSubtitle =
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
if (wantedSubtitle == null) return this;
if (wantedSubtitle.index == SubStreamModel.no().index) {
await player.setSubtitleTrack(SubtitleTrack.no());
} else {
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
final subTrack = subTracks.elementAtOrNull(index);
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
} else if (subTrack != null) {
await player.setSubtitleTrack(subTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
}
@override
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
@override
Future<TranscodePlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
final wantedAudioStream =
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
if (wantedAudioStream == null) return this;
if (wantedAudioStream.index == AudioStreamModel.no().index) {
await player.setAudioTrack(AudioTrack.no());
} else {
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
if (audioTrack != null) {
await player.setAudioTrack(audioTrack);
}
}
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
}
@override
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
await ref.read(jellyApiProvider).sessionsPlayingPost(
body: PlaybackStartInfo(
canSeek: true,
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
sessionId: playbackInfo.playSessionId,
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
volumeLevel: 100,
playbackStartTimeTicks: position.toRuntimeTicks,
playMethod: PlayMethod.transcode,
isMuted: false,
isPaused: false,
repeatMode: RepeatMode.repeatall,
nowPlayingQueue: itemsInQueue,
),
);
return null;
}
@override
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
ref.read(playBackModel.notifier).update((state) => null);
await ref.read(jellyApiProvider).sessionsPlayingStoppedPost(
body: PlaybackStopInfo(
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
positionTicks: position.toRuntimeTicks,
failed: false,
nowPlayingQueue: itemsInQueue,
),
totalDuration: totalDuration,
);
return null;
}
@override
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
final api = ref.read(jellyApiProvider);
//Check for newly generated scrubImages
if (trickPlay == null) {
final trickplay = await api.getTrickPlay(item: item, ref: ref);
ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.bodyOrThrow));
}
await api.sessionsPlayingProgressPost(
body: PlaybackProgressInfo(
canSeek: true,
itemId: item.id,
mediaSourceId: item.id,
playSessionId: playbackInfo.playSessionId,
sessionId: playbackInfo.playSessionId,
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
volumeLevel: 100,
positionTicks: position.toRuntimeTicks,
playMethod: PlayMethod.transcode,
isPaused: !isPlaying,
isMuted: false,
repeatMode: RepeatMode.repeatall,
nowPlayingQueue: itemsInQueue,
),
);
return this;
}
@override
String toString() => 'TranscodePlaybackModel(item: $item, playbackInfo: $playbackInfo)';
@override
final List<ItemBaseModel> queue;
@override
TranscodePlaybackModel copyWith({
ItemBaseModel? item,
ValueGetter<Media?>? media,
ValueGetter<Duration>? lastPosition,
PlaybackInfoResponse? playbackInfo,
ValueGetter<MediaStreamsModel?>? mediaStreams,
ValueGetter<IntroOutSkipModel?>? introSkipModel,
ValueGetter<List<Chapter>?>? chapters,
ValueGetter<TrickPlayModel?>? trickPlay,
List<ItemBaseModel>? queue,
}) {
return TranscodePlaybackModel(
item: item ?? this.item,
media: media != null ? media() : this.media,
playbackInfo: playbackInfo ?? this.playbackInfo,
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
chapters: chapters != null ? chapters() : this.chapters,
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
queue: queue ?? this.queue,
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/overview_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PlaylistModel extends ItemBaseModel {
PlaylistModel({
required super.name,
required super.id,
required super.overview,
required super.parentId,
required super.playlistId,
required super.images,
required super.childCount,
required super.primaryRatio,
required super.userData,
super.canDelete,
super.canDownload,
super.jellyType,
});
factory PlaylistModel.fromBaseDto(BaseItemDto item, Ref ref) {
return PlaylistModel(
name: item.name ?? "",
id: item.id ?? "",
childCount: item.childCount,
overview: OverviewModel.fromBaseItemDto(item, ref),
userData: UserData.fromDto(item.userData),
parentId: item.parentId,
playlistId: item.playlistItemId,
images: ImagesData.fromBaseItem(item, ref),
primaryRatio: item.primaryImageAspectRatio,
canDelete: item.canDelete,
canDownload: item.canDownload,
jellyType: item.type,
);
}
}

View file

@ -0,0 +1,25 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/models/item_base_model.dart';
class RecommendedModel {
final String name;
final List<ItemBaseModel> posters;
final String type;
RecommendedModel({
required this.name,
required this.posters,
required this.type,
});
RecommendedModel copyWith({
String? name,
List<ItemBaseModel>? posters,
String? type,
}) {
return RecommendedModel(
name: name ?? this.name,
posters: posters ?? this.posters,
type: type ?? this.type,
);
}
}

View file

@ -0,0 +1,28 @@
import 'package:fladder/models/item_base_model.dart';
class SearchModel {
final bool loading;
final String searchQuery;
final int resultCount;
final Map<FladderItemType, List<ItemBaseModel>> results;
SearchModel({
this.loading = false,
this.searchQuery = "",
this.resultCount = 0,
this.results = const {},
});
SearchModel copyWith({
bool? loading,
String? searchQuery,
int? resultCount,
Map<FladderItemType, List<ItemBaseModel>>? results,
}) {
return SearchModel(
loading: loading ?? this.loading,
searchQuery: searchQuery ?? this.searchQuery,
resultCount: resultCount ?? this.resultCount,
results: results ?? this.results,
);
}
}

View file

@ -0,0 +1,117 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fladder/util/custom_color_themes.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'client_settings_model.freezed.dart';
part 'client_settings_model.g.dart';
@freezed
class ClientSettingsModel with _$ClientSettingsModel {
factory ClientSettingsModel({
String? syncPath,
@Default(Vector2(x: 0, y: 0)) Vector2 position,
@Default(Vector2(x: 1280, y: 720)) Vector2 size,
@Default(Duration(seconds: 30)) Duration? timeOut,
Duration? nextUpDateCutoff,
@Default(ThemeMode.system) ThemeMode themeMode,
ColorThemes? themeColor,
@Default(false) bool amoledBlack,
@Default(false) bool blurPlaceHolders,
@Default(false) bool blurUpcomingEpisodes,
@LocaleConvert() Locale? selectedLocale,
@Default(true) bool enableMediaKeys,
@Default(1.0) double posterSize,
@Default(false) bool pinchPosterZoom,
@Default(false) bool mouseDragSupport,
int? libraryPageSize,
}) = _ClientSettingsModel;
factory ClientSettingsModel.fromJson(Map<String, dynamic> json) => _$ClientSettingsModelFromJson(json);
}
class LocaleConvert implements JsonConverter<Locale?, String?> {
const LocaleConvert();
@override
Locale? fromJson(String? json) {
if (json == null) return null;
final parts = json.split('_');
if (parts.length == 1) {
return Locale(parts[0]);
} else if (parts.length == 2) {
return Locale(parts[0], parts[1]);
} else {
log("Invalid Locale format");
return null;
}
}
@override
String? toJson(Locale? object) {
if (object == null) return null;
if (object.countryCode == null || object.countryCode?.isEmpty == true) {
return object.languageCode;
}
return '${object.languageCode}_${object.countryCode}';
}
}
class Vector2 {
final double x;
final double y;
const Vector2({
required this.x,
required this.y,
});
Vector2 copyWith({
double? x,
double? y,
}) {
return Vector2(
x: x ?? this.x,
y: y ?? this.y,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'x': x,
'y': y,
};
}
factory Vector2.fromMap(Map<String, dynamic> map) {
return Vector2(
x: map['x'] as double,
y: map['y'] as double,
);
}
String toJson() => json.encode(toMap());
factory Vector2.fromJson(String source) => Vector2.fromMap(json.decode(source) as Map<String, dynamic>);
factory Vector2.fromSize(Size size) => Vector2(x: size.width, y: size.height);
@override
String toString() => 'Vector2(x: $x, y: $y)';
@override
bool operator ==(covariant Vector2 other) {
if (identical(this, other)) return true;
return other.x == x && other.y == y;
}
@override
int get hashCode => x.hashCode ^ y.hashCode;
static fromPosition(Offset windowPosition) => Vector2(x: windowPosition.dx, y: windowPosition.dy);
}

View file

@ -0,0 +1,526 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'client_settings_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
ClientSettingsModel _$ClientSettingsModelFromJson(Map<String, dynamic> json) {
return _ClientSettingsModel.fromJson(json);
}
/// @nodoc
mixin _$ClientSettingsModel {
String? get syncPath => throw _privateConstructorUsedError;
Vector2 get position => throw _privateConstructorUsedError;
Vector2 get size => throw _privateConstructorUsedError;
Duration? get timeOut => throw _privateConstructorUsedError;
Duration? get nextUpDateCutoff => throw _privateConstructorUsedError;
ThemeMode get themeMode => throw _privateConstructorUsedError;
ColorThemes? get themeColor => throw _privateConstructorUsedError;
bool get amoledBlack => throw _privateConstructorUsedError;
bool get blurPlaceHolders => throw _privateConstructorUsedError;
bool get blurUpcomingEpisodes => throw _privateConstructorUsedError;
@LocaleConvert()
Locale? get selectedLocale => throw _privateConstructorUsedError;
bool get enableMediaKeys => throw _privateConstructorUsedError;
double get posterSize => throw _privateConstructorUsedError;
bool get pinchPosterZoom => throw _privateConstructorUsedError;
bool get mouseDragSupport => throw _privateConstructorUsedError;
int? get libraryPageSize => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ClientSettingsModelCopyWith<ClientSettingsModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ClientSettingsModelCopyWith<$Res> {
factory $ClientSettingsModelCopyWith(
ClientSettingsModel value, $Res Function(ClientSettingsModel) then) =
_$ClientSettingsModelCopyWithImpl<$Res, ClientSettingsModel>;
@useResult
$Res call(
{String? syncPath,
Vector2 position,
Vector2 size,
Duration? timeOut,
Duration? nextUpDateCutoff,
ThemeMode themeMode,
ColorThemes? themeColor,
bool amoledBlack,
bool blurPlaceHolders,
bool blurUpcomingEpisodes,
@LocaleConvert() Locale? selectedLocale,
bool enableMediaKeys,
double posterSize,
bool pinchPosterZoom,
bool mouseDragSupport,
int? libraryPageSize});
}
/// @nodoc
class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
implements $ClientSettingsModelCopyWith<$Res> {
_$ClientSettingsModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? syncPath = freezed,
Object? position = null,
Object? size = null,
Object? timeOut = freezed,
Object? nextUpDateCutoff = freezed,
Object? themeMode = null,
Object? themeColor = freezed,
Object? amoledBlack = null,
Object? blurPlaceHolders = null,
Object? blurUpcomingEpisodes = null,
Object? selectedLocale = freezed,
Object? enableMediaKeys = null,
Object? posterSize = null,
Object? pinchPosterZoom = null,
Object? mouseDragSupport = null,
Object? libraryPageSize = freezed,
}) {
return _then(_value.copyWith(
syncPath: freezed == syncPath
? _value.syncPath
: syncPath // ignore: cast_nullable_to_non_nullable
as String?,
position: null == position
? _value.position
: position // ignore: cast_nullable_to_non_nullable
as Vector2,
size: null == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as Vector2,
timeOut: freezed == timeOut
? _value.timeOut
: timeOut // ignore: cast_nullable_to_non_nullable
as Duration?,
nextUpDateCutoff: freezed == nextUpDateCutoff
? _value.nextUpDateCutoff
: nextUpDateCutoff // ignore: cast_nullable_to_non_nullable
as Duration?,
themeMode: null == themeMode
? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode,
themeColor: freezed == themeColor
? _value.themeColor
: themeColor // ignore: cast_nullable_to_non_nullable
as ColorThemes?,
amoledBlack: null == amoledBlack
? _value.amoledBlack
: amoledBlack // ignore: cast_nullable_to_non_nullable
as bool,
blurPlaceHolders: null == blurPlaceHolders
? _value.blurPlaceHolders
: blurPlaceHolders // ignore: cast_nullable_to_non_nullable
as bool,
blurUpcomingEpisodes: null == blurUpcomingEpisodes
? _value.blurUpcomingEpisodes
: blurUpcomingEpisodes // ignore: cast_nullable_to_non_nullable
as bool,
selectedLocale: freezed == selectedLocale
? _value.selectedLocale
: selectedLocale // ignore: cast_nullable_to_non_nullable
as Locale?,
enableMediaKeys: null == enableMediaKeys
? _value.enableMediaKeys
: enableMediaKeys // ignore: cast_nullable_to_non_nullable
as bool,
posterSize: null == posterSize
? _value.posterSize
: posterSize // ignore: cast_nullable_to_non_nullable
as double,
pinchPosterZoom: null == pinchPosterZoom
? _value.pinchPosterZoom
: pinchPosterZoom // ignore: cast_nullable_to_non_nullable
as bool,
mouseDragSupport: null == mouseDragSupport
? _value.mouseDragSupport
: mouseDragSupport // ignore: cast_nullable_to_non_nullable
as bool,
libraryPageSize: freezed == libraryPageSize
? _value.libraryPageSize
: libraryPageSize // ignore: cast_nullable_to_non_nullable
as int?,
) as $Val);
}
}
/// @nodoc
abstract class _$$ClientSettingsModelImplCopyWith<$Res>
implements $ClientSettingsModelCopyWith<$Res> {
factory _$$ClientSettingsModelImplCopyWith(_$ClientSettingsModelImpl value,
$Res Function(_$ClientSettingsModelImpl) then) =
__$$ClientSettingsModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? syncPath,
Vector2 position,
Vector2 size,
Duration? timeOut,
Duration? nextUpDateCutoff,
ThemeMode themeMode,
ColorThemes? themeColor,
bool amoledBlack,
bool blurPlaceHolders,
bool blurUpcomingEpisodes,
@LocaleConvert() Locale? selectedLocale,
bool enableMediaKeys,
double posterSize,
bool pinchPosterZoom,
bool mouseDragSupport,
int? libraryPageSize});
}
/// @nodoc
class __$$ClientSettingsModelImplCopyWithImpl<$Res>
extends _$ClientSettingsModelCopyWithImpl<$Res, _$ClientSettingsModelImpl>
implements _$$ClientSettingsModelImplCopyWith<$Res> {
__$$ClientSettingsModelImplCopyWithImpl(_$ClientSettingsModelImpl _value,
$Res Function(_$ClientSettingsModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? syncPath = freezed,
Object? position = null,
Object? size = null,
Object? timeOut = freezed,
Object? nextUpDateCutoff = freezed,
Object? themeMode = null,
Object? themeColor = freezed,
Object? amoledBlack = null,
Object? blurPlaceHolders = null,
Object? blurUpcomingEpisodes = null,
Object? selectedLocale = freezed,
Object? enableMediaKeys = null,
Object? posterSize = null,
Object? pinchPosterZoom = null,
Object? mouseDragSupport = null,
Object? libraryPageSize = freezed,
}) {
return _then(_$ClientSettingsModelImpl(
syncPath: freezed == syncPath
? _value.syncPath
: syncPath // ignore: cast_nullable_to_non_nullable
as String?,
position: null == position
? _value.position
: position // ignore: cast_nullable_to_non_nullable
as Vector2,
size: null == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as Vector2,
timeOut: freezed == timeOut
? _value.timeOut
: timeOut // ignore: cast_nullable_to_non_nullable
as Duration?,
nextUpDateCutoff: freezed == nextUpDateCutoff
? _value.nextUpDateCutoff
: nextUpDateCutoff // ignore: cast_nullable_to_non_nullable
as Duration?,
themeMode: null == themeMode
? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode,
themeColor: freezed == themeColor
? _value.themeColor
: themeColor // ignore: cast_nullable_to_non_nullable
as ColorThemes?,
amoledBlack: null == amoledBlack
? _value.amoledBlack
: amoledBlack // ignore: cast_nullable_to_non_nullable
as bool,
blurPlaceHolders: null == blurPlaceHolders
? _value.blurPlaceHolders
: blurPlaceHolders // ignore: cast_nullable_to_non_nullable
as bool,
blurUpcomingEpisodes: null == blurUpcomingEpisodes
? _value.blurUpcomingEpisodes
: blurUpcomingEpisodes // ignore: cast_nullable_to_non_nullable
as bool,
selectedLocale: freezed == selectedLocale
? _value.selectedLocale
: selectedLocale // ignore: cast_nullable_to_non_nullable
as Locale?,
enableMediaKeys: null == enableMediaKeys
? _value.enableMediaKeys
: enableMediaKeys // ignore: cast_nullable_to_non_nullable
as bool,
posterSize: null == posterSize
? _value.posterSize
: posterSize // ignore: cast_nullable_to_non_nullable
as double,
pinchPosterZoom: null == pinchPosterZoom
? _value.pinchPosterZoom
: pinchPosterZoom // ignore: cast_nullable_to_non_nullable
as bool,
mouseDragSupport: null == mouseDragSupport
? _value.mouseDragSupport
: mouseDragSupport // ignore: cast_nullable_to_non_nullable
as bool,
libraryPageSize: freezed == libraryPageSize
? _value.libraryPageSize
: libraryPageSize // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ClientSettingsModelImpl
with DiagnosticableTreeMixin
implements _ClientSettingsModel {
_$ClientSettingsModelImpl(
{this.syncPath,
this.position = const Vector2(x: 0, y: 0),
this.size = const Vector2(x: 1280, y: 720),
this.timeOut = const Duration(seconds: 30),
this.nextUpDateCutoff,
this.themeMode = ThemeMode.system,
this.themeColor,
this.amoledBlack = false,
this.blurPlaceHolders = false,
this.blurUpcomingEpisodes = false,
@LocaleConvert() this.selectedLocale,
this.enableMediaKeys = true,
this.posterSize = 1.0,
this.pinchPosterZoom = false,
this.mouseDragSupport = false,
this.libraryPageSize});
factory _$ClientSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
_$$ClientSettingsModelImplFromJson(json);
@override
final String? syncPath;
@override
@JsonKey()
final Vector2 position;
@override
@JsonKey()
final Vector2 size;
@override
@JsonKey()
final Duration? timeOut;
@override
final Duration? nextUpDateCutoff;
@override
@JsonKey()
final ThemeMode themeMode;
@override
final ColorThemes? themeColor;
@override
@JsonKey()
final bool amoledBlack;
@override
@JsonKey()
final bool blurPlaceHolders;
@override
@JsonKey()
final bool blurUpcomingEpisodes;
@override
@LocaleConvert()
final Locale? selectedLocale;
@override
@JsonKey()
final bool enableMediaKeys;
@override
@JsonKey()
final double posterSize;
@override
@JsonKey()
final bool pinchPosterZoom;
@override
@JsonKey()
final bool mouseDragSupport;
@override
final int? libraryPageSize;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, libraryPageSize: $libraryPageSize)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'ClientSettingsModel'))
..add(DiagnosticsProperty('syncPath', syncPath))
..add(DiagnosticsProperty('position', position))
..add(DiagnosticsProperty('size', size))
..add(DiagnosticsProperty('timeOut', timeOut))
..add(DiagnosticsProperty('nextUpDateCutoff', nextUpDateCutoff))
..add(DiagnosticsProperty('themeMode', themeMode))
..add(DiagnosticsProperty('themeColor', themeColor))
..add(DiagnosticsProperty('amoledBlack', amoledBlack))
..add(DiagnosticsProperty('blurPlaceHolders', blurPlaceHolders))
..add(DiagnosticsProperty('blurUpcomingEpisodes', blurUpcomingEpisodes))
..add(DiagnosticsProperty('selectedLocale', selectedLocale))
..add(DiagnosticsProperty('enableMediaKeys', enableMediaKeys))
..add(DiagnosticsProperty('posterSize', posterSize))
..add(DiagnosticsProperty('pinchPosterZoom', pinchPosterZoom))
..add(DiagnosticsProperty('mouseDragSupport', mouseDragSupport))
..add(DiagnosticsProperty('libraryPageSize', libraryPageSize));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ClientSettingsModelImpl &&
(identical(other.syncPath, syncPath) ||
other.syncPath == syncPath) &&
(identical(other.position, position) ||
other.position == position) &&
(identical(other.size, size) || other.size == size) &&
(identical(other.timeOut, timeOut) || other.timeOut == timeOut) &&
(identical(other.nextUpDateCutoff, nextUpDateCutoff) ||
other.nextUpDateCutoff == nextUpDateCutoff) &&
(identical(other.themeMode, themeMode) ||
other.themeMode == themeMode) &&
(identical(other.themeColor, themeColor) ||
other.themeColor == themeColor) &&
(identical(other.amoledBlack, amoledBlack) ||
other.amoledBlack == amoledBlack) &&
(identical(other.blurPlaceHolders, blurPlaceHolders) ||
other.blurPlaceHolders == blurPlaceHolders) &&
(identical(other.blurUpcomingEpisodes, blurUpcomingEpisodes) ||
other.blurUpcomingEpisodes == blurUpcomingEpisodes) &&
(identical(other.selectedLocale, selectedLocale) ||
other.selectedLocale == selectedLocale) &&
(identical(other.enableMediaKeys, enableMediaKeys) ||
other.enableMediaKeys == enableMediaKeys) &&
(identical(other.posterSize, posterSize) ||
other.posterSize == posterSize) &&
(identical(other.pinchPosterZoom, pinchPosterZoom) ||
other.pinchPosterZoom == pinchPosterZoom) &&
(identical(other.mouseDragSupport, mouseDragSupport) ||
other.mouseDragSupport == mouseDragSupport) &&
(identical(other.libraryPageSize, libraryPageSize) ||
other.libraryPageSize == libraryPageSize));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
syncPath,
position,
size,
timeOut,
nextUpDateCutoff,
themeMode,
themeColor,
amoledBlack,
blurPlaceHolders,
blurUpcomingEpisodes,
selectedLocale,
enableMediaKeys,
posterSize,
pinchPosterZoom,
mouseDragSupport,
libraryPageSize);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ClientSettingsModelImplCopyWith<_$ClientSettingsModelImpl> get copyWith =>
__$$ClientSettingsModelImplCopyWithImpl<_$ClientSettingsModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ClientSettingsModelImplToJson(
this,
);
}
}
abstract class _ClientSettingsModel implements ClientSettingsModel {
factory _ClientSettingsModel(
{final String? syncPath,
final Vector2 position,
final Vector2 size,
final Duration? timeOut,
final Duration? nextUpDateCutoff,
final ThemeMode themeMode,
final ColorThemes? themeColor,
final bool amoledBlack,
final bool blurPlaceHolders,
final bool blurUpcomingEpisodes,
@LocaleConvert() final Locale? selectedLocale,
final bool enableMediaKeys,
final double posterSize,
final bool pinchPosterZoom,
final bool mouseDragSupport,
final int? libraryPageSize}) = _$ClientSettingsModelImpl;
factory _ClientSettingsModel.fromJson(Map<String, dynamic> json) =
_$ClientSettingsModelImpl.fromJson;
@override
String? get syncPath;
@override
Vector2 get position;
@override
Vector2 get size;
@override
Duration? get timeOut;
@override
Duration? get nextUpDateCutoff;
@override
ThemeMode get themeMode;
@override
ColorThemes? get themeColor;
@override
bool get amoledBlack;
@override
bool get blurPlaceHolders;
@override
bool get blurUpcomingEpisodes;
@override
@LocaleConvert()
Locale? get selectedLocale;
@override
bool get enableMediaKeys;
@override
double get posterSize;
@override
bool get pinchPosterZoom;
@override
bool get mouseDragSupport;
@override
int? get libraryPageSize;
@override
@JsonKey(ignore: true)
_$$ClientSettingsModelImplCopyWith<_$ClientSettingsModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,83 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'client_settings_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson(
Map<String, dynamic> json) =>
_$ClientSettingsModelImpl(
syncPath: json['syncPath'] as String?,
position: json['position'] == null
? const Vector2(x: 0, y: 0)
: Vector2.fromJson(json['position'] as String),
size: json['size'] == null
? const Vector2(x: 1280, y: 720)
: Vector2.fromJson(json['size'] as String),
timeOut: json['timeOut'] == null
? const Duration(seconds: 30)
: Duration(microseconds: (json['timeOut'] as num).toInt()),
nextUpDateCutoff: json['nextUpDateCutoff'] == null
? null
: Duration(microseconds: (json['nextUpDateCutoff'] as num).toInt()),
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
ThemeMode.system,
themeColor: $enumDecodeNullable(_$ColorThemesEnumMap, json['themeColor']),
amoledBlack: json['amoledBlack'] as bool? ?? false,
blurPlaceHolders: json['blurPlaceHolders'] as bool? ?? false,
blurUpcomingEpisodes: json['blurUpcomingEpisodes'] as bool? ?? false,
selectedLocale:
const LocaleConvert().fromJson(json['selectedLocale'] as String?),
enableMediaKeys: json['enableMediaKeys'] as bool? ?? true,
posterSize: (json['posterSize'] as num?)?.toDouble() ?? 1.0,
pinchPosterZoom: json['pinchPosterZoom'] as bool? ?? false,
mouseDragSupport: json['mouseDragSupport'] as bool? ?? false,
libraryPageSize: (json['libraryPageSize'] as num?)?.toInt(),
);
Map<String, dynamic> _$$ClientSettingsModelImplToJson(
_$ClientSettingsModelImpl instance) =>
<String, dynamic>{
'syncPath': instance.syncPath,
'position': instance.position,
'size': instance.size,
'timeOut': instance.timeOut?.inMicroseconds,
'nextUpDateCutoff': instance.nextUpDateCutoff?.inMicroseconds,
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'themeColor': _$ColorThemesEnumMap[instance.themeColor],
'amoledBlack': instance.amoledBlack,
'blurPlaceHolders': instance.blurPlaceHolders,
'blurUpcomingEpisodes': instance.blurUpcomingEpisodes,
'selectedLocale': const LocaleConvert().toJson(instance.selectedLocale),
'enableMediaKeys': instance.enableMediaKeys,
'posterSize': instance.posterSize,
'pinchPosterZoom': instance.pinchPosterZoom,
'mouseDragSupport': instance.mouseDragSupport,
'libraryPageSize': instance.libraryPageSize,
};
const _$ThemeModeEnumMap = {
ThemeMode.system: 'system',
ThemeMode.light: 'light',
ThemeMode.dark: 'dark',
};
const _$ColorThemesEnumMap = {
ColorThemes.fladder: 'fladder',
ColorThemes.deepOrange: 'deepOrange',
ColorThemes.amber: 'amber',
ColorThemes.green: 'green',
ColorThemes.lightGreen: 'lightGreen',
ColorThemes.lime: 'lime',
ColorThemes.cyan: 'cyan',
ColorThemes.blue: 'blue',
ColorThemes.lightBlue: 'lightBlue',
ColorThemes.indigo: 'indigo',
ColorThemes.deepBlue: 'deepBlue',
ColorThemes.brown: 'brown',
ColorThemes.purple: 'purple',
ColorThemes.deepPurple: 'deepPurple',
ColorThemes.blueGrey: 'blueGrey',
};

View file

@ -0,0 +1,108 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
enum HomeCarouselSettings {
off,
nextUp,
cont,
combined,
;
const HomeCarouselSettings();
String label(BuildContext context) => switch (this) {
HomeCarouselSettings.off => context.localized.hide,
HomeCarouselSettings.nextUp => context.localized.nextUp,
HomeCarouselSettings.cont => context.localized.settingsContinue,
HomeCarouselSettings.combined => context.localized.combined,
};
String toMap() {
return toString();
}
static HomeCarouselSettings fromMap(String value) {
return HomeCarouselSettings.values.firstWhereOrNull((element) => element.name == value) ??
HomeCarouselSettings.combined;
}
}
enum HomeNextUp {
off,
nextUp,
cont,
combined,
separate,
;
const HomeNextUp();
String label(BuildContext context) => switch (this) {
HomeNextUp.off => context.localized.hide,
HomeNextUp.nextUp => context.localized.nextUp,
HomeNextUp.cont => context.localized.settingsContinue,
HomeNextUp.combined => context.localized.combined,
HomeNextUp.separate => context.localized.separate,
};
String toMap() {
return toString();
}
static HomeNextUp fromMap(String value) {
return HomeNextUp.values.firstWhereOrNull((element) => element.name == value) ?? HomeNextUp.separate;
}
}
class HomeSettingsModel {
final HomeCarouselSettings carouselSettings;
final HomeNextUp nextUp;
HomeSettingsModel({
this.carouselSettings = HomeCarouselSettings.combined,
this.nextUp = HomeNextUp.separate,
});
HomeSettingsModel copyWith({
HomeCarouselSettings? carouselSettings,
HomeNextUp? nextUp,
}) {
return HomeSettingsModel(
carouselSettings: carouselSettings ?? this.carouselSettings,
nextUp: nextUp ?? this.nextUp,
);
}
Map<String, dynamic> toMap() {
return {
'carouselSettings': carouselSettings.toMap(),
'nextUp': nextUp.toMap(),
};
}
factory HomeSettingsModel.fromMap(Map<String, dynamic> map) {
return HomeSettingsModel(
carouselSettings: HomeCarouselSettings.fromMap(map['carouselSettings']),
nextUp: HomeNextUp.fromMap(map['nextUp']),
);
}
String toJson() => json.encode(toMap());
factory HomeSettingsModel.fromJson(String source) => HomeSettingsModel.fromMap(json.decode(source));
@override
String toString() => 'HomeSettingsModel(carouselSettings: $carouselSettings, nextUp: $nextUp)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is HomeSettingsModel && other.carouselSettings == carouselSettings && other.nextUp == nextUp;
}
@override
int get hashCode => carouselSettings.hashCode ^ nextUp.hashCode;
}

View file

@ -0,0 +1,254 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
class SubtitleSettingsModel {
final double fontSize;
final FontWeight fontWeight;
final double verticalOffset;
final Color color;
final Color outlineColor;
final double outlineSize;
final Color backGroundColor;
final double shadow;
const SubtitleSettingsModel({
this.fontSize = 60,
this.fontWeight = FontWeight.normal,
this.verticalOffset = 0.10,
this.color = Colors.white,
this.outlineColor = const Color.fromRGBO(0, 0, 0, 0.85),
this.outlineSize = 4,
this.backGroundColor = const Color.fromARGB(0, 0, 0, 0),
this.shadow = 0.5,
});
SubtitleSettingsModel copyWith({
double? fontSize,
FontWeight? fontWeight,
double? verticalOffset,
Color? color,
Color? outlineColor,
double? outlineSize,
Color? backGroundColor,
double? shadow,
}) {
return SubtitleSettingsModel(
fontSize: fontSize ?? this.fontSize,
fontWeight: fontWeight ?? this.fontWeight,
verticalOffset: verticalOffset ?? this.verticalOffset,
color: color ?? this.color,
outlineColor: outlineColor ?? this.outlineColor,
outlineSize: outlineSize ?? this.outlineSize,
backGroundColor: backGroundColor ?? this.backGroundColor,
shadow: shadow ?? this.shadow,
);
}
TextStyle get backGroundStyle {
return style.copyWith(
shadows: (shadow > 0.01)
? [
Shadow(
blurRadius: 16,
color: Colors.black.withOpacity(shadow),
),
Shadow(
blurRadius: 8,
color: Colors.black.withOpacity(shadow),
),
]
: null,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = outlineSize * (fontSize / 30)
..color = outlineColor
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round,
);
}
TextStyle get style {
return TextStyle(
height: 1.4,
fontSize: fontSize,
fontWeight: fontWeight,
fontFamily: GoogleFonts.openSans().fontFamily,
letterSpacing: 0.0,
wordSpacing: 0.0,
color: color,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'fontSize': fontSize,
'fontWeight': fontWeight.value,
'verticalOffset': verticalOffset,
'color': color.value,
'outlineColor': outlineColor.value,
'outlineSize': outlineSize,
'backGroundColor': backGroundColor.value,
'shadow': shadow,
};
}
String toJson() => json.encode(toMap());
factory SubtitleSettingsModel.fromJson(String source) => SubtitleSettingsModel.fromMap(json.decode(source));
factory SubtitleSettingsModel.fromMap(Map<String, dynamic> map) {
return const SubtitleSettingsModel().copyWith(
fontSize: map['fontSize'] as double?,
fontWeight: FontWeight.values.firstWhereOrNull((element) => element.index == map['fontWeight'] as int?),
verticalOffset: map['verticalOffset'] as double?,
color: map['color'] != null ? Color(map['color'] as int) : null,
outlineColor: map['outlineColor'] != null ? Color(map['outlineColor'] as int) : null,
outlineSize: map['outlineSize'] as double?,
backGroundColor: map['backGroundColor'] != null ? Color(map['backGroundColor'] as int) : null,
shadow: map['shadow'] as double?,
);
}
@override
String toString() {
return 'SubtitleSettingsModel(fontSize: $fontSize, fontWeight: $fontWeight, verticalOffset: $verticalOffset, color: $color, outlineColor: $outlineColor, outlineSize: $outlineSize, backGroundColor: $backGroundColor, shadow: $shadow)';
}
@override
bool operator ==(covariant SubtitleSettingsModel other) {
if (identical(this, other)) return true;
return other.fontSize == fontSize &&
other.fontWeight == fontWeight &&
other.verticalOffset == verticalOffset &&
other.color == color &&
other.outlineColor == outlineColor &&
other.outlineSize == outlineSize &&
other.backGroundColor == backGroundColor &&
other.shadow == shadow;
}
@override
int get hashCode {
return fontSize.hashCode ^
fontWeight.hashCode ^
verticalOffset.hashCode ^
color.hashCode ^
outlineColor.hashCode ^
outlineSize.hashCode ^
backGroundColor.hashCode ^
shadow.hashCode;
}
}
class SubtitleText extends ConsumerWidget {
final SubtitleSettingsModel subModel;
final EdgeInsets padding;
final String text;
final double offset;
const SubtitleText({
required this.subModel,
required this.padding,
required this.offset,
required this.text,
super.key,
});
// The reference width for calculating the visible text scale factor.
static const kTextScaleFactorReferenceWidth = 1920.0;
// The reference height for calculating the visible text scale factor.
static const kTextScaleFactorReferenceHeight = 1080.0;
@override
Widget build(BuildContext context, WidgetRef ref) {
final fillScreen = ref.watch(videoPlayerSettingsProvider.select((value) => value.fillScreen));
return Padding(
padding: (fillScreen ? EdgeInsets.zero : EdgeInsets.only(left: padding.left, right: padding.right))
.add(const EdgeInsets.all(16)),
child: LayoutBuilder(
builder: (context, constraints) {
final textScale = MediaQuery.textScalerOf(context)
.scale((ref.read(subtitleSettingsProvider.select((value) => value.fontSize)) *
math.sqrt(
((constraints.maxWidth * constraints.maxHeight) /
(kTextScaleFactorReferenceWidth * kTextScaleFactorReferenceHeight))
.clamp(0.0, 1.0),
)));
// Function to calculate the height of the text
double getTextHeight(BuildContext context, String text, TextStyle style) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaler: MediaQuery.textScalerOf(context),
)..layout(minWidth: 0, maxWidth: double.infinity);
return textPainter.height;
}
// Calculate the available height for the text alignment
double availableHeight = constraints.maxHeight;
// Calculate the desired position based on the percentage
double desiredPosition = availableHeight * offset;
// Get the height of the Text widget with the current font style
double textHeight = getTextHeight(context, text, subModel.style);
// Calculate the position to keep the text within visible bounds
double position = desiredPosition - textHeight / 2;
// Ensure the text doesn't go off-screen
position = position.clamp(0, availableHeight - textHeight);
return IgnorePointer(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned(
bottom: position,
child: Container(
constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight),
decoration: BoxDecoration(
color: subModel.backGroundColor,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
text,
style: subModel.backGroundStyle.copyWith(fontSize: textScale),
textAlign: TextAlign.center,
),
),
),
),
Positioned(
bottom: position,
child: Container(
constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
text,
style: subModel.style.copyWith(fontSize: textScale),
textAlign: TextAlign.center,
),
),
),
)
],
),
);
},
),
);
}
}

View file

@ -0,0 +1,113 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class VideoPlayerSettingsModel {
final double? screenBrightness;
final BoxFit videoFit;
final bool fillScreen;
final bool hardwareAccel;
final bool useLibass;
final double internalVolume;
final String? audioDevice;
const VideoPlayerSettingsModel({
this.screenBrightness,
this.videoFit = BoxFit.contain,
this.fillScreen = false,
this.hardwareAccel = true,
this.useLibass = false,
this.internalVolume = 100,
this.audioDevice,
});
double get volume => switch (defaultTargetPlatform) {
TargetPlatform.android || TargetPlatform.iOS => 100,
_ => internalVolume,
};
VideoPlayerSettingsModel copyWith({
ValueGetter<double?>? screenBrightness,
BoxFit? videoFit,
bool? fillScreen,
bool? hardwareAccel,
bool? useLibass,
double? internalVolume,
ValueGetter<String?>? audioDevice,
}) {
return VideoPlayerSettingsModel(
screenBrightness: screenBrightness != null ? screenBrightness() : this.screenBrightness,
videoFit: videoFit ?? this.videoFit,
fillScreen: fillScreen ?? this.fillScreen,
hardwareAccel: hardwareAccel ?? this.hardwareAccel,
useLibass: useLibass ?? this.useLibass,
internalVolume: internalVolume ?? this.internalVolume,
audioDevice: audioDevice != null ? audioDevice() : this.audioDevice,
);
}
Map<String, dynamic> toMap() {
return {
'screenBrightness': screenBrightness,
'videoFit': videoFit.name,
'fillScreen': fillScreen,
'hardwareAccel': hardwareAccel,
'useLibass': useLibass,
'internalVolume': internalVolume,
'audioDevice': audioDevice,
};
}
factory VideoPlayerSettingsModel.fromMap(Map<String, dynamic> map) {
return VideoPlayerSettingsModel(
screenBrightness: map['screenBrightness']?.toDouble(),
videoFit: BoxFit.values.firstWhereOrNull((element) => element.name == map['videoFit']) ?? BoxFit.contain,
fillScreen: map['fillScreen'] ?? false,
hardwareAccel: map['hardwareAccel'] ?? false,
useLibass: map['useLibass'] ?? false,
internalVolume: map['internalVolume']?.toDouble() ?? 0.0,
audioDevice: map['audioDevice'],
);
}
String toJson() => json.encode(toMap());
factory VideoPlayerSettingsModel.fromJson(String source) => VideoPlayerSettingsModel.fromMap(json.decode(source));
@override
String toString() {
return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, internalVolume: $internalVolume, audioDevice: $audioDevice)';
}
bool playerSame(VideoPlayerSettingsModel other) {
return other.hardwareAccel == hardwareAccel && other.useLibass == useLibass;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is VideoPlayerSettingsModel &&
other.screenBrightness == screenBrightness &&
other.videoFit == videoFit &&
other.fillScreen == fillScreen &&
other.hardwareAccel == hardwareAccel &&
other.useLibass == useLibass &&
other.internalVolume == internalVolume &&
other.audioDevice == audioDevice;
}
@override
int get hashCode {
return screenBrightness.hashCode ^
videoFit.hashCode ^
fillScreen.hashCode ^
hardwareAccel.hashCode ^
useLibass.hashCode ^
internalVolume.hashCode ^
audioDevice.hashCode;
}
}

View file

@ -0,0 +1,41 @@
import 'package:background_downloader/background_downloader.dart' as dl;
class DownloadStream {
final String id;
final dl.DownloadTask? task;
final double progress;
final dl.TaskStatus status;
DownloadStream({
required this.id,
this.task,
required this.progress,
required this.status,
});
DownloadStream.empty()
: id = '',
task = null,
progress = -1,
status = dl.TaskStatus.notFound;
bool get hasDownload => progress != -1.0 && status != dl.TaskStatus.notFound && status != dl.TaskStatus.complete;
DownloadStream copyWith({
String? id,
dl.DownloadTask? task,
double? progress,
dl.TaskStatus? status,
}) {
return DownloadStream(
id: id ?? this.id,
task: task ?? this.task,
progress: progress ?? this.progress,
status: status ?? this.status,
);
}
@override
String toString() {
return 'DownloadStream(id: $id, task: $task, progress: $progress, status: $status)';
}
}

View file

@ -0,0 +1,75 @@
import 'dart:convert';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:isar/isar.dart';
part 'i_synced_item.g.dart';
// extension IsarExtensions on String? {
// int get fastHash {
// if (this == null) return 0;
// var hash = 0xcbf29ce484222325;
// var i = 0;
// while (i < this!.length) {
// final codeUnit = this!.codeUnitAt(i++);
// hash ^= codeUnit >> 8;
// hash *= 0x100000001b3;
// hash ^= codeUnit & 0xFF;
// hash *= 0x100000001b3;
// }
// return hash;
// }
// }
@collection
class ISyncedItem {
String? userId;
String id;
int? sortKey;
String? parentId;
String? path;
int? fileSize;
String? videoFileName;
String? trickPlayModel;
String? introOutroSkipModel;
String? images;
List<String>? chapters;
List<String>? subtitles;
String? userData;
ISyncedItem({
this.userId,
required this.id,
this.sortKey,
this.parentId,
this.path,
this.fileSize,
this.videoFileName,
this.trickPlayModel,
this.introOutroSkipModel,
this.images,
this.chapters,
this.subtitles,
this.userData,
});
factory ISyncedItem.fromSynced(SyncedItem syncedItem, String? path) {
return ISyncedItem(
id: syncedItem.id,
parentId: syncedItem.parentId,
userId: syncedItem.userId,
path: syncedItem.path?.replaceAll(path ?? "", '').substring(1),
fileSize: syncedItem.fileSize,
sortKey: syncedItem.sortKey,
videoFileName: syncedItem.videoFileName,
trickPlayModel: syncedItem.fTrickPlayModel != null ? jsonEncode(syncedItem.fTrickPlayModel?.toJson()) : null,
introOutroSkipModel:
syncedItem.introOutSkipModel != null ? jsonEncode(syncedItem.introOutSkipModel?.toJson()) : null,
images: syncedItem.fImages != null ? jsonEncode(syncedItem.fImages?.toJson()) : null,
chapters: syncedItem.fChapters.map((e) => jsonEncode(e.toJson())).toList(),
subtitles: syncedItem.subtitles.map((e) => jsonEncode(e.toJson())).toList(),
userData: syncedItem.userData != null ? jsonEncode(syncedItem.userData?.toJson()) : null,
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
import 'dart:convert';
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/syncing/i_synced_item.dart';
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:path/path.dart';
part 'sync_item.freezed.dart';
part 'sync_item.g.dart';
@freezed
class SyncedItem with _$SyncedItem {
const SyncedItem._();
factory SyncedItem({
required String id,
String? parentId,
required String userId,
String? path,
@Default(false) bool markedForDelete,
int? sortKey,
int? fileSize,
String? videoFileName,
IntroOutSkipModel? introOutSkipModel,
TrickPlayModel? fTrickPlayModel,
ImagesData? fImages,
@Default([]) List<Chapter> fChapters,
@Default([]) List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData,
}) = _SyncItem;
static String trickPlayPath = "TrickPlay";
List<Chapter> get chapters => fChapters.map((e) => e.copyWith(imageUrl: joinAll({"$path", e.imageUrl}))).toList();
ImagesData? get images => fImages?.copyWith(
primary: () => fImages?.primary?.copyWith(path: joinAll(["$path", "${fImages?.primary?.path}"])),
logo: () => fImages?.logo?.copyWith(path: joinAll(["$path", "${fImages?.logo?.path}"])),
backDrop: () => fImages?.backDrop?.map((e) => e.copyWith(path: joinAll(["$path", (e.path)]))).toList(),
);
TrickPlayModel? get trickPlayModel => fTrickPlayModel?.copyWith(
images: fTrickPlayModel?.images
.map(
(trickPlayPath) => joinAll(["$path", trickPlayPath]),
)
.toList() ??
[]);
File get dataFile => File(joinAll(["$path", "data.json"]));
Directory get trickPlayDirectory => Directory(joinAll(["$path", trickPlayPath]));
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
Directory get directory => Directory(path ?? "");
SyncStatus get status => switch (videoFile.existsSync()) {
true => SyncStatus.complete,
_ => SyncStatus.partially,
};
String? get taskId => null;
bool get childHasTask => false;
double get totalProgress => 0.0;
bool get hasVideoFile => videoFileName?.isNotEmpty == true && (fileSize ?? 0) > 0;
TaskStatus get anyStatus {
return TaskStatus.notFound;
}
double get downloadProgress => 0.0;
TaskStatus get downloadStatus => TaskStatus.notFound;
DownloadTask? get task => null;
Future<bool> deleteDatFiles(Ref ref) async {
try {
await videoFile.delete();
await Directory(joinAll([directory.path, trickPlayPath])).delete(recursive: true);
} catch (e) {
return false;
}
return true;
}
List<SyncedItem> nestedChildren(WidgetRef ref) => ref.watch(syncChildrenProvider(this));
List<SyncedItem> getChildren(Ref ref) => ref.read(syncProvider.notifier).getChildren(this);
List<SyncedItem> getNestedChildren(Ref ref) => ref.read(syncProvider.notifier).getNestedChildren(this);
Future<int> get getDirSize async {
var files = await directory.list(recursive: true).toList();
var dirSize = files.fold(0, (int sum, file) => sum + file.statSync().size);
return dirSize;
}
ItemBaseModel? createItemModel(Ref ref) {
if (!dataFile.existsSync()) return null;
final BaseItemDto itemDto = BaseItemDto.fromJson(jsonDecode(dataFile.readAsStringSync()));
final itemModel = ItemBaseModel.fromBaseDto(itemDto, ref);
return itemModel.copyWith(
images: images,
userData: userData,
);
}
factory SyncedItem.fromJson(Map<String, dynamic> json) => _$SyncedItemFromJson(json);
factory SyncedItem.fromIsar(ISyncedItem isarSyncedItem, String savePath) {
return SyncedItem(
id: isarSyncedItem.id,
parentId: isarSyncedItem.parentId,
userId: isarSyncedItem.userId ?? "",
sortKey: isarSyncedItem.sortKey,
path: joinAll([savePath, isarSyncedItem.path ?? ""]),
fileSize: isarSyncedItem.fileSize,
videoFileName: isarSyncedItem.videoFileName,
introOutSkipModel: isarSyncedItem.introOutroSkipModel != null
? IntroOutSkipModel.fromJson(jsonDecode(isarSyncedItem.introOutroSkipModel!))
: null,
fTrickPlayModel: isarSyncedItem.trickPlayModel != null
? TrickPlayModel.fromJson(jsonDecode(isarSyncedItem.trickPlayModel!))
: null,
fImages: isarSyncedItem.images != null ? ImagesData.fromJson(jsonDecode(isarSyncedItem.images!)) : null,
fChapters: isarSyncedItem.chapters
?.map(
(e) => Chapter.fromJson(jsonDecode(e)),
)
.toList() ??
[],
subtitles: isarSyncedItem.subtitles
?.map(
(e) => SubStreamModel.fromJson(jsonDecode(e)),
)
.toList() ??
[],
userData: isarSyncedItem.userData != null ? UserData.fromJson(jsonDecode(isarSyncedItem.userData!)) : null,
);
}
}
enum SyncStatus {
complete(
"Synced",
Color.fromARGB(255, 141, 214, 58),
IconsaxOutline.tick_circle,
),
partially(
"Partially",
Color.fromARGB(255, 221, 135, 23),
IconsaxOutline.more_circle,
),
;
const SyncStatus(this.label, this.color, this.icon);
final Color color;
final String label;
final IconData icon;
}
extension StatusExtension on TaskStatus {
Color color(BuildContext context) => switch (this) {
TaskStatus.enqueued => Colors.blueAccent,
TaskStatus.running => Colors.limeAccent,
TaskStatus.complete => Colors.limeAccent,
TaskStatus.canceled || TaskStatus.notFound || TaskStatus.failed => Theme.of(context).colorScheme.error,
TaskStatus.waitingToRetry => Colors.yellowAccent,
TaskStatus.paused => Colors.orangeAccent,
};
String get name => switch (this) {
TaskStatus.enqueued => 'Enqueued',
TaskStatus.running => 'Running',
TaskStatus.complete => 'Complete',
TaskStatus.notFound => 'Not Found',
TaskStatus.failed => 'Failed',
TaskStatus.canceled => 'Canceled',
TaskStatus.waitingToRetry => 'Waiting To Retry',
TaskStatus.paused => 'Paused',
};
}

View file

@ -0,0 +1,494 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'sync_item.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SyncedItem _$SyncedItemFromJson(Map<String, dynamic> json) {
return _SyncItem.fromJson(json);
}
/// @nodoc
mixin _$SyncedItem {
String get id => throw _privateConstructorUsedError;
String? get parentId => throw _privateConstructorUsedError;
String get userId => throw _privateConstructorUsedError;
String? get path => throw _privateConstructorUsedError;
bool get markedForDelete => throw _privateConstructorUsedError;
int? get sortKey => throw _privateConstructorUsedError;
int? get fileSize => throw _privateConstructorUsedError;
String? get videoFileName => throw _privateConstructorUsedError;
IntroOutSkipModel? get introOutSkipModel =>
throw _privateConstructorUsedError;
TrickPlayModel? get fTrickPlayModel => throw _privateConstructorUsedError;
ImagesData? get fImages => throw _privateConstructorUsedError;
List<Chapter> get fChapters => throw _privateConstructorUsedError;
List<SubStreamModel> get subtitles => throw _privateConstructorUsedError;
@UserDataJsonSerializer()
UserData? get userData => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$SyncedItemCopyWith<SyncedItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SyncedItemCopyWith<$Res> {
factory $SyncedItemCopyWith(
SyncedItem value, $Res Function(SyncedItem) then) =
_$SyncedItemCopyWithImpl<$Res, SyncedItem>;
@useResult
$Res call(
{String id,
String? parentId,
String userId,
String? path,
bool markedForDelete,
int? sortKey,
int? fileSize,
String? videoFileName,
IntroOutSkipModel? introOutSkipModel,
TrickPlayModel? fTrickPlayModel,
ImagesData? fImages,
List<Chapter> fChapters,
List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData});
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel;
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
}
/// @nodoc
class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
implements $SyncedItemCopyWith<$Res> {
_$SyncedItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? parentId = freezed,
Object? userId = null,
Object? path = freezed,
Object? markedForDelete = null,
Object? sortKey = freezed,
Object? fileSize = freezed,
Object? videoFileName = freezed,
Object? introOutSkipModel = freezed,
Object? fTrickPlayModel = freezed,
Object? fImages = freezed,
Object? fChapters = null,
Object? subtitles = null,
Object? userData = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
parentId: freezed == parentId
? _value.parentId
: parentId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
path: freezed == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String?,
markedForDelete: null == markedForDelete
? _value.markedForDelete
: markedForDelete // ignore: cast_nullable_to_non_nullable
as bool,
sortKey: freezed == sortKey
? _value.sortKey
: sortKey // ignore: cast_nullable_to_non_nullable
as int?,
fileSize: freezed == fileSize
? _value.fileSize
: fileSize // ignore: cast_nullable_to_non_nullable
as int?,
videoFileName: freezed == videoFileName
? _value.videoFileName
: videoFileName // ignore: cast_nullable_to_non_nullable
as String?,
introOutSkipModel: freezed == introOutSkipModel
? _value.introOutSkipModel
: introOutSkipModel // ignore: cast_nullable_to_non_nullable
as IntroOutSkipModel?,
fTrickPlayModel: freezed == fTrickPlayModel
? _value.fTrickPlayModel
: fTrickPlayModel // ignore: cast_nullable_to_non_nullable
as TrickPlayModel?,
fImages: freezed == fImages
? _value.fImages
: fImages // ignore: cast_nullable_to_non_nullable
as ImagesData?,
fChapters: null == fChapters
? _value.fChapters
: fChapters // ignore: cast_nullable_to_non_nullable
as List<Chapter>,
subtitles: null == subtitles
? _value.subtitles
: subtitles // ignore: cast_nullable_to_non_nullable
as List<SubStreamModel>,
userData: freezed == userData
? _value.userData
: userData // ignore: cast_nullable_to_non_nullable
as UserData?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel {
if (_value.introOutSkipModel == null) {
return null;
}
return $IntroOutSkipModelCopyWith<$Res>(_value.introOutSkipModel!, (value) {
return _then(_value.copyWith(introOutSkipModel: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel {
if (_value.fTrickPlayModel == null) {
return null;
}
return $TrickPlayModelCopyWith<$Res>(_value.fTrickPlayModel!, (value) {
return _then(_value.copyWith(fTrickPlayModel: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$SyncItemImplCopyWith<$Res>
implements $SyncedItemCopyWith<$Res> {
factory _$$SyncItemImplCopyWith(
_$SyncItemImpl value, $Res Function(_$SyncItemImpl) then) =
__$$SyncItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
String? parentId,
String userId,
String? path,
bool markedForDelete,
int? sortKey,
int? fileSize,
String? videoFileName,
IntroOutSkipModel? introOutSkipModel,
TrickPlayModel? fTrickPlayModel,
ImagesData? fImages,
List<Chapter> fChapters,
List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData});
@override
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel;
@override
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
}
/// @nodoc
class __$$SyncItemImplCopyWithImpl<$Res>
extends _$SyncedItemCopyWithImpl<$Res, _$SyncItemImpl>
implements _$$SyncItemImplCopyWith<$Res> {
__$$SyncItemImplCopyWithImpl(
_$SyncItemImpl _value, $Res Function(_$SyncItemImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? parentId = freezed,
Object? userId = null,
Object? path = freezed,
Object? markedForDelete = null,
Object? sortKey = freezed,
Object? fileSize = freezed,
Object? videoFileName = freezed,
Object? introOutSkipModel = freezed,
Object? fTrickPlayModel = freezed,
Object? fImages = freezed,
Object? fChapters = null,
Object? subtitles = null,
Object? userData = freezed,
}) {
return _then(_$SyncItemImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
parentId: freezed == parentId
? _value.parentId
: parentId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
path: freezed == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String?,
markedForDelete: null == markedForDelete
? _value.markedForDelete
: markedForDelete // ignore: cast_nullable_to_non_nullable
as bool,
sortKey: freezed == sortKey
? _value.sortKey
: sortKey // ignore: cast_nullable_to_non_nullable
as int?,
fileSize: freezed == fileSize
? _value.fileSize
: fileSize // ignore: cast_nullable_to_non_nullable
as int?,
videoFileName: freezed == videoFileName
? _value.videoFileName
: videoFileName // ignore: cast_nullable_to_non_nullable
as String?,
introOutSkipModel: freezed == introOutSkipModel
? _value.introOutSkipModel
: introOutSkipModel // ignore: cast_nullable_to_non_nullable
as IntroOutSkipModel?,
fTrickPlayModel: freezed == fTrickPlayModel
? _value.fTrickPlayModel
: fTrickPlayModel // ignore: cast_nullable_to_non_nullable
as TrickPlayModel?,
fImages: freezed == fImages
? _value.fImages
: fImages // ignore: cast_nullable_to_non_nullable
as ImagesData?,
fChapters: null == fChapters
? _value._fChapters
: fChapters // ignore: cast_nullable_to_non_nullable
as List<Chapter>,
subtitles: null == subtitles
? _value._subtitles
: subtitles // ignore: cast_nullable_to_non_nullable
as List<SubStreamModel>,
userData: freezed == userData
? _value.userData
: userData // ignore: cast_nullable_to_non_nullable
as UserData?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SyncItemImpl extends _SyncItem {
_$SyncItemImpl(
{required this.id,
this.parentId,
required this.userId,
this.path,
this.markedForDelete = false,
this.sortKey,
this.fileSize,
this.videoFileName,
this.introOutSkipModel,
this.fTrickPlayModel,
this.fImages,
final List<Chapter> fChapters = const [],
final List<SubStreamModel> subtitles = const [],
@UserDataJsonSerializer() this.userData})
: _fChapters = fChapters,
_subtitles = subtitles,
super._();
factory _$SyncItemImpl.fromJson(Map<String, dynamic> json) =>
_$$SyncItemImplFromJson(json);
@override
final String id;
@override
final String? parentId;
@override
final String userId;
@override
final String? path;
@override
@JsonKey()
final bool markedForDelete;
@override
final int? sortKey;
@override
final int? fileSize;
@override
final String? videoFileName;
@override
final IntroOutSkipModel? introOutSkipModel;
@override
final TrickPlayModel? fTrickPlayModel;
@override
final ImagesData? fImages;
final List<Chapter> _fChapters;
@override
@JsonKey()
List<Chapter> get fChapters {
if (_fChapters is EqualUnmodifiableListView) return _fChapters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_fChapters);
}
final List<SubStreamModel> _subtitles;
@override
@JsonKey()
List<SubStreamModel> get subtitles {
if (_subtitles is EqualUnmodifiableListView) return _subtitles;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_subtitles);
}
@override
@UserDataJsonSerializer()
final UserData? userData;
@override
String toString() {
return 'SyncedItem(id: $id, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortKey: $sortKey, fileSize: $fileSize, videoFileName: $videoFileName, introOutSkipModel: $introOutSkipModel, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SyncItemImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.parentId, parentId) ||
other.parentId == parentId) &&
(identical(other.userId, userId) || other.userId == userId) &&
(identical(other.path, path) || other.path == path) &&
(identical(other.markedForDelete, markedForDelete) ||
other.markedForDelete == markedForDelete) &&
(identical(other.sortKey, sortKey) || other.sortKey == sortKey) &&
(identical(other.fileSize, fileSize) ||
other.fileSize == fileSize) &&
(identical(other.videoFileName, videoFileName) ||
other.videoFileName == videoFileName) &&
(identical(other.introOutSkipModel, introOutSkipModel) ||
other.introOutSkipModel == introOutSkipModel) &&
(identical(other.fTrickPlayModel, fTrickPlayModel) ||
other.fTrickPlayModel == fTrickPlayModel) &&
(identical(other.fImages, fImages) || other.fImages == fImages) &&
const DeepCollectionEquality()
.equals(other._fChapters, _fChapters) &&
const DeepCollectionEquality()
.equals(other._subtitles, _subtitles) &&
(identical(other.userData, userData) ||
other.userData == userData));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
id,
parentId,
userId,
path,
markedForDelete,
sortKey,
fileSize,
videoFileName,
introOutSkipModel,
fTrickPlayModel,
fImages,
const DeepCollectionEquality().hash(_fChapters),
const DeepCollectionEquality().hash(_subtitles),
userData);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith =>
__$$SyncItemImplCopyWithImpl<_$SyncItemImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SyncItemImplToJson(
this,
);
}
}
abstract class _SyncItem extends SyncedItem {
factory _SyncItem(
{required final String id,
final String? parentId,
required final String userId,
final String? path,
final bool markedForDelete,
final int? sortKey,
final int? fileSize,
final String? videoFileName,
final IntroOutSkipModel? introOutSkipModel,
final TrickPlayModel? fTrickPlayModel,
final ImagesData? fImages,
final List<Chapter> fChapters,
final List<SubStreamModel> subtitles,
@UserDataJsonSerializer() final UserData? userData}) = _$SyncItemImpl;
_SyncItem._() : super._();
factory _SyncItem.fromJson(Map<String, dynamic> json) =
_$SyncItemImpl.fromJson;
@override
String get id;
@override
String? get parentId;
@override
String get userId;
@override
String? get path;
@override
bool get markedForDelete;
@override
int? get sortKey;
@override
int? get fileSize;
@override
String? get videoFileName;
@override
IntroOutSkipModel? get introOutSkipModel;
@override
TrickPlayModel? get fTrickPlayModel;
@override
ImagesData? get fImages;
@override
List<Chapter> get fChapters;
@override
List<SubStreamModel> get subtitles;
@override
@UserDataJsonSerializer()
UserData? get userData;
@override
@JsonKey(ignore: true)
_$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,71 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sync_item.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SyncItemImpl _$$SyncItemImplFromJson(Map<String, dynamic> json) =>
_$SyncItemImpl(
id: json['id'] as String,
parentId: json['parentId'] as String?,
userId: json['userId'] as String,
path: json['path'] as String?,
markedForDelete: json['markedForDelete'] as bool? ?? false,
sortKey: (json['sortKey'] as num?)?.toInt(),
fileSize: (json['fileSize'] as num?)?.toInt(),
videoFileName: json['videoFileName'] as String?,
introOutSkipModel: json['introOutSkipModel'] == null
? null
: IntroOutSkipModel.fromJson(
json['introOutSkipModel'] as Map<String, dynamic>),
fTrickPlayModel: json['fTrickPlayModel'] == null
? null
: TrickPlayModel.fromJson(
json['fTrickPlayModel'] as Map<String, dynamic>),
fImages: json['fImages'] == null
? null
: ImagesData.fromJson(json['fImages'] as String),
fChapters: (json['fChapters'] as List<dynamic>?)
?.map((e) => Chapter.fromJson(e as String))
.toList() ??
const [],
subtitles: (json['subtitles'] as List<dynamic>?)
?.map((e) => SubStreamModel.fromJson(e as String))
.toList() ??
const [],
userData: _$JsonConverterFromJson<String, UserData>(
json['userData'], const UserDataJsonSerializer().fromJson),
);
Map<String, dynamic> _$$SyncItemImplToJson(_$SyncItemImpl instance) =>
<String, dynamic>{
'id': instance.id,
'parentId': instance.parentId,
'userId': instance.userId,
'path': instance.path,
'markedForDelete': instance.markedForDelete,
'sortKey': instance.sortKey,
'fileSize': instance.fileSize,
'videoFileName': instance.videoFileName,
'introOutSkipModel': instance.introOutSkipModel,
'fTrickPlayModel': instance.fTrickPlayModel,
'fImages': instance.fImages,
'fChapters': instance.fChapters,
'subtitles': instance.subtitles,
'userData': _$JsonConverterToJson<String, UserData>(
instance.userData, const UserDataJsonSerializer().toJson),
};
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);

View file

@ -0,0 +1,16 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:fladder/models/syncing/sync_item.dart';
part 'sync_settings_model.freezed.dart';
@Freezed(toJson: false, fromJson: false)
class SyncSettingsModel with _$SyncSettingsModel {
const SyncSettingsModel._();
factory SyncSettingsModel({
@Default([]) List<SyncedItem> items,
}) = _SyncSettignsModel;
}

View file

@ -0,0 +1,144 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'sync_settings_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$SyncSettingsModel {
List<SyncedItem> get items => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$SyncSettingsModelCopyWith<SyncSettingsModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SyncSettingsModelCopyWith<$Res> {
factory $SyncSettingsModelCopyWith(
SyncSettingsModel value, $Res Function(SyncSettingsModel) then) =
_$SyncSettingsModelCopyWithImpl<$Res, SyncSettingsModel>;
@useResult
$Res call({List<SyncedItem> items});
}
/// @nodoc
class _$SyncSettingsModelCopyWithImpl<$Res, $Val extends SyncSettingsModel>
implements $SyncSettingsModelCopyWith<$Res> {
_$SyncSettingsModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? items = null,
}) {
return _then(_value.copyWith(
items: null == items
? _value.items
: items // ignore: cast_nullable_to_non_nullable
as List<SyncedItem>,
) as $Val);
}
}
/// @nodoc
abstract class _$$SyncSettignsModelImplCopyWith<$Res>
implements $SyncSettingsModelCopyWith<$Res> {
factory _$$SyncSettignsModelImplCopyWith(_$SyncSettignsModelImpl value,
$Res Function(_$SyncSettignsModelImpl) then) =
__$$SyncSettignsModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<SyncedItem> items});
}
/// @nodoc
class __$$SyncSettignsModelImplCopyWithImpl<$Res>
extends _$SyncSettingsModelCopyWithImpl<$Res, _$SyncSettignsModelImpl>
implements _$$SyncSettignsModelImplCopyWith<$Res> {
__$$SyncSettignsModelImplCopyWithImpl(_$SyncSettignsModelImpl _value,
$Res Function(_$SyncSettignsModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? items = null,
}) {
return _then(_$SyncSettignsModelImpl(
items: null == items
? _value._items
: items // ignore: cast_nullable_to_non_nullable
as List<SyncedItem>,
));
}
}
/// @nodoc
class _$SyncSettignsModelImpl extends _SyncSettignsModel {
_$SyncSettignsModelImpl({final List<SyncedItem> items = const []})
: _items = items,
super._();
final List<SyncedItem> _items;
@override
@JsonKey()
List<SyncedItem> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@override
String toString() {
return 'SyncSettingsModel(items: $items)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SyncSettignsModelImpl &&
const DeepCollectionEquality().equals(other._items, _items));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_items));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith =>
__$$SyncSettignsModelImplCopyWithImpl<_$SyncSettignsModelImpl>(
this, _$identity);
}
abstract class _SyncSettignsModel extends SyncSettingsModel {
factory _SyncSettignsModel({final List<SyncedItem> items}) =
_$SyncSettignsModelImpl;
_SyncSettignsModel._() : super._();
@override
List<SyncedItem> get items;
@override
@JsonKey(ignore: true)
_$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,177 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:fladder/models/items/chapters_model.dart';
class Bif {
final String version;
final List<int> widthResolutions;
Bif({
required this.version,
required this.widthResolutions,
});
Bif copyWith({
String? version,
List<int>? widthResolutions,
}) {
return Bif(
version: version ?? this.version,
widthResolutions: widthResolutions ?? this.widthResolutions,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'Version': version,
'WidthResolutions': widthResolutions,
};
}
factory Bif.fromMap(Map<String, dynamic> map) {
return Bif(
version: (map['Version'] ?? '') as String,
widthResolutions: List<int>.from((map['WidthResolutions'] ?? const <int>[]) as List<dynamic>),
);
}
String toJson() => json.encode(toMap());
factory Bif.fromJson(String source) => Bif.fromMap(json.decode(source) as Map<String, dynamic>);
@override
String toString() => 'Bif(version: $version, widthResolutions: $widthResolutions)';
@override
bool operator ==(covariant Bif other) {
if (identical(this, other)) return true;
return other.version == version && listEquals(other.widthResolutions, widthResolutions);
}
@override
int get hashCode => version.hashCode ^ widthResolutions.hashCode;
}
class BifUtil {
static List<int> bifMagicNumbers = [0x89, 0x42, 0x49, 0x46, 0x0D, 0x0A, 0x1A, 0x0A];
static const int supportedBifVersion = 0;
static Future<BifData?> trickPlayDecode(Uint8List array, int width) async {
Indexed8Array data = Indexed8Array(array);
for (int b in bifMagicNumbers) {
if (data.read() != b) {
print('Attempted to read invalid bif file.');
return null;
}
}
int bifVersion = data.readInt32();
if (bifVersion != supportedBifVersion) {
print('Client only supports BIF v$supportedBifVersion but file is v$bifVersion');
return null;
}
print('BIF version: $bifVersion');
int bifImgCount = data.readInt32();
if (bifImgCount <= 0) {
print('BIF file contains no images.');
return null;
}
print('BIF image count: $bifImgCount');
int timestampMultiplier = data.readInt32();
if (timestampMultiplier == 0) timestampMultiplier = 1000;
data.addPosition(44); // Reserved
List<BifIndexEntry> bifIndex = [];
for (int i = 0; i < bifImgCount; i++) {
bifIndex.add(BifIndexEntry(data.readInt32(), data.readInt32()));
}
List<Chapter> bifImages = [];
for (int i = 0; i < bifIndex.length; i++) {
BifIndexEntry indexEntry = bifIndex[i];
int timestamp = indexEntry.timestamp;
int offset = indexEntry.offset;
int nextOffset = i + 1 < bifIndex.length ? bifIndex[i + 1].offset : data.limit();
Uint8List imageBytes = Uint8List.sublistView(Uint8List.view(data.array.buffer, offset, nextOffset - offset));
bifImages.add(
Chapter(
name: "",
imageUrl: "",
imageData: imageBytes,
startPosition: Duration(milliseconds: timestamp * timestampMultiplier),
),
);
}
return BifData(bifVersion, bifImgCount, bifImages, width);
}
}
class Indexed8Array {
Uint8List array;
int readIndex;
Indexed8Array(Uint8List byteArray)
: array = byteArray,
readIndex = 0;
int read() {
return array[readIndex++];
}
void addPosition(int amount) {
readIndex += amount;
}
int readInt32() {
int b1 = read() & 0xFF;
int b2 = read() & 0xFF;
int b3 = read() & 0xFF;
int b4 = read() & 0xFF;
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
}
Uint8List getByteArray() {
return array;
}
int limit() {
return array.length;
}
Endian order() {
return Endian.big;
}
}
class BifIndexEntry {
int timestamp;
int offset;
BifIndexEntry(this.timestamp, this.offset);
}
class BifData {
int bifVersion;
int bifImgCount;
List<Chapter> chapters;
int width;
BifData(
this.bifVersion,
this.bifImgCount,
this.chapters,
this.width,
);
}

View file

@ -0,0 +1,208 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:collection/collection.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/items/item_stream_model.dart';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/intro_skip_model.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/providers/user_provider.dart';
enum PlaybackType {
directStream,
offline,
transcode;
IconData get icon => switch (this) {
PlaybackType.offline => IconsaxOutline.cloud,
PlaybackType.directStream => IconsaxOutline.arrow_right_1,
PlaybackType.transcode => IconsaxOutline.convert,
};
String get name {
switch (this) {
case PlaybackType.directStream:
return "Direct";
case PlaybackType.offline:
return "Offline";
case PlaybackType.transcode:
return "Transcoding";
}
}
}
class VideoPlayback {
final List<ItemStreamModel> queue;
final ItemStreamModel? currentItem;
final SyncedItem? currentSyncedItem;
final VideoStream? currentStream;
VideoPlayback({
required this.queue,
this.currentItem,
this.currentSyncedItem,
this.currentStream,
});
List<SubStreamModel> get subStreams => currentStream?.mediaStreamsModel?.subStreams ?? [];
List<AudioStreamModel> get audioStreams => currentStream?.mediaStreamsModel?.audioStreams ?? [];
ItemStreamModel? get previousVideo {
final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id);
if (currentIndex > 0) {
return queue[currentIndex - 1];
}
return null;
}
ItemStreamModel? get nextVideo {
final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id);
if ((currentIndex + 1) < queue.length) {
return queue[currentIndex + 1];
}
return null;
}
VideoPlayback copyWith({
List<ItemStreamModel>? queue,
ItemStreamModel? currentItem,
SyncedItem? currentSyncedItem,
VideoStream? currentStream,
Map<AudioStreamModel, AudioTrack>? audioMappings,
Map<SubStreamModel, SubtitleTrack>? subMappings,
}) {
return VideoPlayback(
queue: queue ?? this.queue,
currentItem: currentItem ?? this.currentItem,
currentSyncedItem: currentSyncedItem ?? this.currentSyncedItem,
currentStream: currentStream ?? this.currentStream,
);
}
VideoPlayback clear() {
return VideoPlayback(
queue: queue,
currentItem: null,
currentStream: null,
);
}
}
class VideoStream {
final String id;
final Duration? currentPosition;
final PlaybackType playbackType;
final String playbackUrl;
final String playSessionId;
final List<Chapter>? chapters;
final List<Chapter>? trickPlay;
final IntroSkipModel? introSkipModel;
final int? audioStreamIndex;
final int? subtitleStreamIndex;
final MediaStreamsModel? mediaStreamsModel;
AudioStreamModel? get currentAudioStream {
if (audioStreamIndex == -1) {
return null;
}
return mediaStreamsModel?.audioStreams.firstWhereOrNull(
(element) => element.index == (audioStreamIndex ?? mediaStreamsModel?.currentAudioStream ?? 0));
}
SubStreamModel? get currentSubStream {
if (subtitleStreamIndex == -1) {
return null;
}
return mediaStreamsModel?.subStreams.firstWhereOrNull(
(element) => element.index == (subtitleStreamIndex ?? mediaStreamsModel?.currentSubStream ?? 0));
}
VideoStream({
required this.id,
this.currentPosition,
required this.playbackType,
required this.playbackUrl,
required this.playSessionId,
this.chapters,
this.trickPlay,
this.introSkipModel,
this.audioStreamIndex,
this.subtitleStreamIndex,
this.mediaStreamsModel,
});
VideoStream copyWith({
String? id,
Duration? currentPosition,
PlaybackType? playbackType,
String? playbackUrl,
String? playSessionId,
List<Chapter>? chapters,
List<Chapter>? trickPlay,
IntroSkipModel? introSkipModel,
int? audioStreamIndex,
int? subtitleStreamIndex,
MediaStreamsModel? mediaStreamsModel,
}) {
return VideoStream(
id: id ?? this.id,
currentPosition: currentPosition ?? this.currentPosition,
playbackType: playbackType ?? this.playbackType,
playbackUrl: playbackUrl ?? this.playbackUrl,
playSessionId: playSessionId ?? this.playSessionId,
chapters: chapters ?? this.chapters,
trickPlay: trickPlay ?? this.trickPlay,
introSkipModel: introSkipModel ?? this.introSkipModel,
audioStreamIndex: audioStreamIndex ?? this.audioStreamIndex,
subtitleStreamIndex: subtitleStreamIndex ?? this.subtitleStreamIndex,
mediaStreamsModel: mediaStreamsModel ?? this.mediaStreamsModel,
);
}
static VideoStream? fromPlayBackInfo(PlaybackInfoResponse info, Ref ref) {
final mediaSource = info.mediaSources?.first;
var playType = PlaybackType.directStream;
String? playbackUrl;
if (mediaSource == null) return null;
if (mediaSource.supportsDirectStream ?? false) {
final Map<String, String?> directOptions = {
'Static': 'true',
'mediaSourceId': mediaSource.id,
'api_key': ref.read(userProvider)?.credentials.token,
};
if (mediaSource.eTag != null) {
directOptions['Tag'] = mediaSource.eTag;
}
if (mediaSource.liveStreamId != null) {
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
}
final params = Uri(queryParameters: directOptions).query;
playbackUrl = '${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params';
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
playbackUrl = "${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}";
playType = PlaybackType.transcode;
}
if (playbackUrl == null) return null;
return VideoStream(
id: info.mediaSources?.first.id ?? "",
playbackUrl: playbackUrl,
playbackType: playType,
playSessionId: info.playSessionId ?? "",
mediaStreamsModel: MediaStreamsModel.fromMediaStreamsList(
info.mediaSources?.firstOrNull, info.mediaSources?.firstOrNull?.mediaStreams ?? [], ref),
);
}
}

View file

@ -0,0 +1,97 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/models/item_base_model.dart';
class ViewModel {
final String name;
final String id;
final String serverId;
final DateTime dateCreated;
final bool canDelete;
final bool canDownload;
final String parentId;
final CollectionType collectionType;
final dto.PlayAccess playAccess;
final List<ItemBaseModel> recentlyAdded;
final int childCount;
ViewModel({
required this.name,
required this.id,
required this.serverId,
required this.dateCreated,
required this.canDelete,
required this.canDownload,
required this.parentId,
required this.collectionType,
required this.playAccess,
required this.recentlyAdded,
required this.childCount,
});
ViewModel copyWith({
String? name,
String? id,
String? serverId,
DateTime? dateCreated,
bool? canDelete,
bool? canDownload,
String? parentId,
CollectionType? collectionType,
dto.PlayAccess? playAccess,
List<ItemBaseModel>? recentlyAdded,
int? childCount,
}) {
return ViewModel(
name: name ?? this.name,
id: id ?? this.id,
serverId: serverId ?? this.serverId,
dateCreated: dateCreated ?? this.dateCreated,
canDelete: canDelete ?? this.canDelete,
canDownload: canDownload ?? this.canDownload,
parentId: parentId ?? this.parentId,
collectionType: collectionType ?? this.collectionType,
playAccess: playAccess ?? this.playAccess,
recentlyAdded: recentlyAdded ?? this.recentlyAdded,
childCount: childCount ?? this.childCount,
);
}
factory ViewModel.fromBodyDto(dto.BaseItemDto item, Ref ref) {
return ViewModel(
name: item.name ?? "",
id: item.id ?? "",
serverId: item.serverId ?? "",
dateCreated: item.dateCreated ?? DateTime.now(),
canDelete: item.canDelete ?? false,
canDownload: item.canDownload ?? false,
parentId: item.parentId ?? "",
recentlyAdded: [],
collectionType: CollectionType.values
.firstWhereOrNull((element) => element.name.toLowerCase() == item.collectionType?.value?.toLowerCase()) ??
CollectionType.movies,
playAccess: item.playAccess ?? PlayAccess.none,
childCount: item.childCount ?? 0,
);
}
@override
bool operator ==(covariant ViewModel other) {
if (identical(this, other)) return true;
return other.id == id && other.serverId == serverId;
}
@override
int get hashCode {
return id.hashCode ^ serverId.hashCode;
}
@override
String toString() {
return 'ViewModel(name: $name, id: $id, serverId: $serverId, dateCreated: $dateCreated, canDelete: $canDelete, canDownload: $canDownload, parentId: $parentId, collectionType: $collectionType, playAccess: $playAccess, recentlyAdded: $recentlyAdded, childCount: $childCount)';
}
}

View file

@ -0,0 +1,24 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:fladder/models/view_model.dart';
class ViewsModel {
final bool loading;
final List<ViewModel> views;
final List<ViewModel> dashboardViews;
ViewsModel({
this.loading = false,
this.views = const [],
this.dashboardViews = const [],
});
ViewsModel copyWith({
bool? loading,
List<ViewModel>? views,
List<ViewModel>? dashboardViews,
}) {
return ViewsModel(
loading: loading ?? this.loading,
views: views ?? this.views,
dashboardViews: dashboardViews ?? this.dashboardViews);
}
}