mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-11 00:10:29 -07:00
feat: UI 2.0 and other Improvements (#357)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
9ca06eaa37
commit
e7b5bb40ff
169 changed files with 4584 additions and 3626 deletions
|
|
@ -10,7 +10,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/credentials_model.dart';
|
||||
import 'package:fladder/models/library_filters_model.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
part 'account_model.freezed.dart';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ extension CollectionTypeExtension on CollectionType {
|
|||
|
||||
Set<FladderItemType> get itemKinds {
|
||||
switch (this) {
|
||||
case CollectionType.music:
|
||||
return {FladderItemType.musicAlbum};
|
||||
case CollectionType.movies:
|
||||
return {FladderItemType.movie};
|
||||
case CollectionType.tvshows:
|
||||
|
|
@ -30,6 +32,8 @@ extension CollectionTypeExtension on CollectionType {
|
|||
|
||||
IconData getIconType(bool outlined) {
|
||||
switch (this) {
|
||||
case CollectionType.music:
|
||||
return outlined ? IconsaxPlusLinear.music_square : IconsaxPlusBold.music_square;
|
||||
case CollectionType.movies:
|
||||
return outlined ? IconsaxPlusLinear.video_horizontal : IconsaxPlusBold.video_horizontal;
|
||||
case CollectionType.tvshows:
|
||||
|
|
@ -48,4 +52,16 @@ extension CollectionTypeExtension on CollectionType {
|
|||
return IconsaxPlusLinear.information;
|
||||
}
|
||||
}
|
||||
|
||||
double? get aspectRatio => switch (this) {
|
||||
CollectionType.music ||
|
||||
CollectionType.homevideos ||
|
||||
CollectionType.boxsets ||
|
||||
CollectionType.photos ||
|
||||
CollectionType.livetv ||
|
||||
CollectionType.playlists =>
|
||||
0.8,
|
||||
CollectionType.folders => 1.3,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
|
|
@ -304,6 +304,15 @@ enum FladderItemType {
|
|||
|
||||
const FladderItemType({required this.icon, required this.selectedicon});
|
||||
|
||||
double get aspectRatio => switch (this) {
|
||||
FladderItemType.video => 0.8,
|
||||
FladderItemType.photo => 0.8,
|
||||
FladderItemType.photoAlbum => 0.8,
|
||||
FladderItemType.musicAlbum => 0.8,
|
||||
FladderItemType.baseType => 0.8,
|
||||
_ => 0.55,
|
||||
};
|
||||
|
||||
static Set<FladderItemType> get playable => {
|
||||
FladderItemType.series,
|
||||
FladderItemType.episode,
|
||||
|
|
@ -317,27 +326,25 @@ enum FladderItemType {
|
|||
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,
|
||||
};
|
||||
}
|
||||
String label(BuildContext context) => 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,
|
||||
|
|
|
|||
|
|
@ -199,20 +199,20 @@ extension EpisodeListExtensions on List<EpisodeModel> {
|
|||
}
|
||||
|
||||
EpisodeModel? get nextUp {
|
||||
final episodes = whereNot((element) => element.season <= 0).toList();
|
||||
final episodes = where((e) => e.season > 0 && e.status == EpisodeStatus.available).toList();
|
||||
if (episodes.isEmpty) return null;
|
||||
|
||||
final lastProgress = episodes
|
||||
.lastIndexWhere((element) => element.userData.progress != 0 && element.status == EpisodeStatus.available);
|
||||
final lastPlayed =
|
||||
episodes.lastIndexWhere((element) => element.userData.played && element.status == EpisodeStatus.available);
|
||||
final lastWatchedIndex = [
|
||||
episodes.lastIndexWhere((e) => e.userData.progress != 0),
|
||||
episodes.lastIndexWhere((e) => e.userData.played),
|
||||
].reduce((a, b) => a > b ? a : b);
|
||||
|
||||
if (lastProgress == -1 && lastPlayed == -1) {
|
||||
return episodes.firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
} else {
|
||||
return episodes
|
||||
.getRange(lastProgress > lastPlayed ? lastProgress : lastPlayed + 1, episodes.length)
|
||||
.firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
if (lastWatchedIndex >= 0 && lastWatchedIndex + 1 < episodes.length) {
|
||||
final next = episodes.sublist(lastWatchedIndex + 1).firstWhereOrNull((e) => e.status == EpisodeStatus.available);
|
||||
if (next != null) return next;
|
||||
}
|
||||
|
||||
return episodes.firstOrNull;
|
||||
}
|
||||
|
||||
bool get allPlayed {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
|
|
@ -9,8 +10,7 @@ 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';
|
||||
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
|
||||
|
||||
part 'series_model.mapper.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/util/localization_helper.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:flutter/material.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
enum SortingOptions {
|
||||
name([ItemSortBy.name]),
|
||||
|
|
|
|||
|
|
@ -198,17 +198,15 @@ class PlaybackModelHelper {
|
|||
|
||||
final streamModel = firstItemToPlay.streamModel;
|
||||
final audioStreamIndex = selectAudioStream(
|
||||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
|
||||
oldModel?.mediaStreams?.currentAudioStream,
|
||||
streamModel?.audioStreams,
|
||||
streamModel?.defaultAudioStreamIndex
|
||||
);
|
||||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
|
||||
oldModel?.mediaStreams?.currentAudioStream,
|
||||
streamModel?.audioStreams,
|
||||
streamModel?.defaultAudioStreamIndex);
|
||||
final subStreamIndex = selectSubStream(
|
||||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
|
||||
oldModel?.mediaStreams?.currentSubStream,
|
||||
streamModel?.subStreams,
|
||||
streamModel?.defaultSubStreamIndex
|
||||
);
|
||||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
|
||||
oldModel?.mediaStreams?.currentSubStream,
|
||||
streamModel?.subStreams,
|
||||
streamModel?.defaultSubStreamIndex);
|
||||
|
||||
final Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
|
||||
itemId: firstItemToPlay.id,
|
||||
|
|
@ -345,14 +343,12 @@ class PlaybackModelHelper {
|
|||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberAudioSelections ?? true)),
|
||||
playbackModel.mediaStreams?.currentAudioStream,
|
||||
playbackModel.audioStreams,
|
||||
playbackModel.mediaStreams?.defaultAudioStreamIndex
|
||||
);
|
||||
playbackModel.mediaStreams?.defaultAudioStreamIndex);
|
||||
final subIndex = selectSubStream(
|
||||
ref.read(userProvider.select((value) => value?.userConfiguration?.rememberSubtitleSelections ?? true)),
|
||||
playbackModel.mediaStreams?.currentSubStream,
|
||||
playbackModel.subStreams,
|
||||
playbackModel.mediaStreams?.defaultSubStreamIndex
|
||||
);
|
||||
playbackModel.mediaStreams?.defaultSubStreamIndex);
|
||||
|
||||
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
|
||||
itemId: item.id,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,66 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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/util/localization_helper.dart';
|
||||
|
||||
sealed class NameSwitch {
|
||||
const NameSwitch();
|
||||
|
||||
String label(BuildContext context);
|
||||
}
|
||||
|
||||
class NextUp extends NameSwitch {
|
||||
const NextUp();
|
||||
|
||||
@override
|
||||
String label(BuildContext context) => context.localized.nextUp;
|
||||
}
|
||||
|
||||
class Latest extends NameSwitch {
|
||||
const Latest();
|
||||
|
||||
@override
|
||||
String label(BuildContext context) => context.localized.latest;
|
||||
}
|
||||
|
||||
class Other extends NameSwitch {
|
||||
final String customLabel;
|
||||
|
||||
const Other(this.customLabel);
|
||||
|
||||
@override
|
||||
String label(BuildContext context) => customLabel;
|
||||
}
|
||||
|
||||
extension RecommendationTypeExtenstion on RecommendationType {
|
||||
String label(BuildContext context) => switch (this) {
|
||||
RecommendationType.similartorecentlyplayed => context.localized.similarToRecentlyPlayed,
|
||||
RecommendationType.similartolikeditem => context.localized.similarToLikedItem,
|
||||
RecommendationType.hasdirectorfromrecentlyplayed => context.localized.hasDirectorFromRecentlyPlayed,
|
||||
RecommendationType.hasactorfromrecentlyplayed => context.localized.hasActorFromRecentlyPlayed,
|
||||
RecommendationType.haslikeddirector => context.localized.hasLikedDirector,
|
||||
RecommendationType.haslikedactor => context.localized.hasLikedActor,
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
|
||||
class RecommendedModel {
|
||||
final String name;
|
||||
final NameSwitch name;
|
||||
final List<ItemBaseModel> posters;
|
||||
final String type;
|
||||
final RecommendationType? type;
|
||||
RecommendedModel({
|
||||
required this.name,
|
||||
required this.posters,
|
||||
required this.type,
|
||||
this.type,
|
||||
});
|
||||
|
||||
RecommendedModel copyWith({
|
||||
String? name,
|
||||
NameSwitch? name,
|
||||
List<ItemBaseModel>? posters,
|
||||
String? type,
|
||||
RecommendationType? type,
|
||||
}) {
|
||||
return RecommendedModel(
|
||||
name: name ?? this.name,
|
||||
|
|
@ -22,4 +68,12 @@ class RecommendedModel {
|
|||
type: type ?? this.type,
|
||||
);
|
||||
}
|
||||
|
||||
factory RecommendedModel.fromBaseDto(RecommendationDto e, Ref ref) {
|
||||
return RecommendedModel(
|
||||
name: Other(e.baselineItemName ?? ""),
|
||||
posters: e.items?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList() ?? [],
|
||||
type: e.recommendationType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class ClientSettingsModel with _$ClientSettingsModel {
|
|||
@Default(true) bool requireWifi,
|
||||
@Default(false) bool showAllCollectionTypes,
|
||||
@Default(2) int maxConcurrentDownloads,
|
||||
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
|
||||
@Default(DynamicSchemeVariant.rainbow) DynamicSchemeVariant schemeVariant,
|
||||
int? libraryPageSize,
|
||||
}) = _ClientSettingsModel;
|
||||
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ class _$ClientSettingsModelImpl extends _ClientSettingsModel
|
|||
this.requireWifi = true,
|
||||
this.showAllCollectionTypes = false,
|
||||
this.maxConcurrentDownloads = 2,
|
||||
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
|
||||
this.schemeVariant = DynamicSchemeVariant.rainbow,
|
||||
this.libraryPageSize})
|
||||
: super._();
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ _$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson(
|
|||
(json['maxConcurrentDownloads'] as num?)?.toInt() ?? 2,
|
||||
schemeVariant: $enumDecodeNullable(
|
||||
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
|
||||
DynamicSchemeVariant.tonalSpot,
|
||||
DynamicSchemeVariant.rainbow,
|
||||
libraryPageSize: (json['libraryPageSize'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
part 'home_settings_model.freezed.dart';
|
||||
|
|
@ -36,42 +37,6 @@ T selectAvailableOrSmaller<T>(T value, Set<T> availableOptions, List<T> allOptio
|
|||
return availableOptions.first;
|
||||
}
|
||||
|
||||
enum ViewSize {
|
||||
phone,
|
||||
tablet,
|
||||
desktop;
|
||||
|
||||
const ViewSize();
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
ViewSize.phone => context.localized.phone,
|
||||
ViewSize.tablet => context.localized.tablet,
|
||||
ViewSize.desktop => context.localized.desktop,
|
||||
};
|
||||
|
||||
bool operator >(ViewSize other) => index > other.index;
|
||||
bool operator >=(ViewSize other) => index >= other.index;
|
||||
bool operator <(ViewSize other) => index < other.index;
|
||||
bool operator <=(ViewSize other) => index <= other.index;
|
||||
}
|
||||
|
||||
enum LayoutMode {
|
||||
single,
|
||||
dual;
|
||||
|
||||
const LayoutMode();
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
LayoutMode.single => context.localized.layoutModeSingle,
|
||||
LayoutMode.dual => context.localized.layoutModeDual,
|
||||
};
|
||||
|
||||
bool operator >(ViewSize other) => index > other.index;
|
||||
bool operator >=(ViewSize other) => index >= other.index;
|
||||
bool operator <(ViewSize other) => index < other.index;
|
||||
bool operator <=(ViewSize other) => index <= other.index;
|
||||
}
|
||||
|
||||
enum HomeBanner {
|
||||
hide,
|
||||
carousel,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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/collection_types.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/widgets/navigation_scaffold/components/navigation_button.dart';
|
||||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
|
||||
class ViewModel {
|
||||
final String name;
|
||||
|
|
@ -16,7 +24,9 @@ class ViewModel {
|
|||
final CollectionType collectionType;
|
||||
final dto.PlayAccess playAccess;
|
||||
final List<ItemBaseModel> recentlyAdded;
|
||||
final ImagesData? imageData;
|
||||
final int childCount;
|
||||
final String? path;
|
||||
ViewModel({
|
||||
required this.name,
|
||||
required this.id,
|
||||
|
|
@ -28,7 +38,9 @@ class ViewModel {
|
|||
required this.collectionType,
|
||||
required this.playAccess,
|
||||
required this.recentlyAdded,
|
||||
required this.imageData,
|
||||
required this.childCount,
|
||||
required this.path,
|
||||
});
|
||||
|
||||
ViewModel copyWith({
|
||||
|
|
@ -42,7 +54,9 @@ class ViewModel {
|
|||
CollectionType? collectionType,
|
||||
dto.PlayAccess? playAccess,
|
||||
List<ItemBaseModel>? recentlyAdded,
|
||||
ImagesData? imageData,
|
||||
int? childCount,
|
||||
String? path,
|
||||
}) {
|
||||
return ViewModel(
|
||||
name: name ?? this.name,
|
||||
|
|
@ -55,7 +69,9 @@ class ViewModel {
|
|||
collectionType: collectionType ?? this.collectionType,
|
||||
playAccess: playAccess ?? this.playAccess,
|
||||
recentlyAdded: recentlyAdded ?? this.recentlyAdded,
|
||||
imageData: imageData ?? this.imageData,
|
||||
childCount: childCount ?? this.childCount,
|
||||
path: path ?? this.path,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -69,11 +85,13 @@ class ViewModel {
|
|||
canDownload: item.canDownload ?? false,
|
||||
parentId: item.parentId ?? "",
|
||||
recentlyAdded: [],
|
||||
imageData: ImagesData.fromBaseItem(item, ref),
|
||||
collectionType: CollectionType.values
|
||||
.firstWhereOrNull((element) => element.name.toLowerCase() == item.collectionType?.value?.toLowerCase()) ??
|
||||
CollectionType.folders,
|
||||
playAccess: item.playAccess ?? PlayAccess.none,
|
||||
childCount: item.childCount ?? 0,
|
||||
path: "",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +106,27 @@ class ViewModel {
|
|||
return id.hashCode ^ serverId.hashCode;
|
||||
}
|
||||
|
||||
NavigationButton toNavigationButton(
|
||||
bool selected,
|
||||
bool horizontal,
|
||||
bool expanded,
|
||||
FutureOr Function() action, {
|
||||
FutureOr Function()? onLongPress,
|
||||
List<ItemAction>? trailing,
|
||||
}) {
|
||||
return NavigationButton(
|
||||
label: name,
|
||||
selected: selected,
|
||||
onPressed: action,
|
||||
onLongPress: onLongPress,
|
||||
horizontal: horizontal,
|
||||
expanded: expanded,
|
||||
trailing: trailing ?? [],
|
||||
selectedIcon: Icon(collectionType.icon),
|
||||
icon: Icon(collectionType.iconOutlined),
|
||||
);
|
||||
}
|
||||
|
||||
@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)';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue