first commit
This commit is contained in:
@@ -0,0 +1,618 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:path/path.dart' as path_helper;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../services/finamp_settings_helper.dart';
|
||||
import '../services/get_internal_song_dir.dart';
|
||||
import 'jellyfin_models.dart';
|
||||
|
||||
part 'finamp_models.g.dart';
|
||||
|
||||
@HiveType(typeId: 8)
|
||||
class FinampUser {
|
||||
FinampUser({
|
||||
required this.id,
|
||||
required this.baseUrl,
|
||||
required this.accessToken,
|
||||
required this.serverId,
|
||||
this.currentViewId,
|
||||
required this.views,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
String id;
|
||||
@HiveField(1)
|
||||
String baseUrl;
|
||||
@HiveField(2)
|
||||
String accessToken;
|
||||
@HiveField(3)
|
||||
String serverId;
|
||||
@HiveField(4)
|
||||
String? currentViewId;
|
||||
@HiveField(5)
|
||||
Map<String, BaseItemDto> views;
|
||||
|
||||
BaseItemDto? get currentView => views[currentViewId];
|
||||
}
|
||||
|
||||
// These consts are so that we can easily keep the same default for
|
||||
// FinampSettings's constructor and Hive's defaultValue.
|
||||
const _songShuffleItemCountDefault = 250;
|
||||
const _contentViewType = ContentViewType.list;
|
||||
const _contentGridViewCrossAxisCountPortrait = 2;
|
||||
const _contentGridViewCrossAxisCountLandscape = 3;
|
||||
const _showTextOnGridView = true;
|
||||
const _sleepTimerSeconds = 1800; // 30 Minutes
|
||||
const _showCoverAsPlayerBackground = true;
|
||||
const _hideSongArtistsIfSameAsAlbumArtists = true;
|
||||
const _disableGesture = false;
|
||||
const _showFastScroller = true;
|
||||
const _bufferDurationSeconds = 50;
|
||||
const _tabOrder = TabContentType.values;
|
||||
const _swipeInsertQueueNext = false;
|
||||
|
||||
@HiveType(typeId: 28)
|
||||
class FinampSettings {
|
||||
FinampSettings({
|
||||
this.isOffline = false,
|
||||
this.shouldTranscode = false,
|
||||
this.transcodeBitrate = 320000,
|
||||
// downloadLocations is required since the other values can be created with
|
||||
// default values. create() is used to return a FinampSettings with
|
||||
// downloadLocations.
|
||||
required this.downloadLocations,
|
||||
this.androidStopForegroundOnPause = true,
|
||||
required this.showTabs,
|
||||
this.isFavourite = false,
|
||||
this.sortBy = SortBy.sortName,
|
||||
this.sortOrder = SortOrder.ascending,
|
||||
this.songShuffleItemCount = _songShuffleItemCountDefault,
|
||||
this.contentViewType = _contentViewType,
|
||||
this.contentGridViewCrossAxisCountPortrait =
|
||||
_contentGridViewCrossAxisCountPortrait,
|
||||
this.contentGridViewCrossAxisCountLandscape =
|
||||
_contentGridViewCrossAxisCountLandscape,
|
||||
this.showTextOnGridView = _showTextOnGridView,
|
||||
this.sleepTimerSeconds = _sleepTimerSeconds,
|
||||
required this.downloadLocationsMap,
|
||||
this.showCoverAsPlayerBackground = _showCoverAsPlayerBackground,
|
||||
this.hideSongArtistsIfSameAsAlbumArtists =
|
||||
_hideSongArtistsIfSameAsAlbumArtists,
|
||||
this.bufferDurationSeconds = _bufferDurationSeconds,
|
||||
required this.tabSortBy,
|
||||
required this.tabSortOrder,
|
||||
this.tabOrder = _tabOrder,
|
||||
this.hasCompletedBlurhashImageMigration = true,
|
||||
this.hasCompletedBlurhashImageMigrationIdFix = true,
|
||||
this.swipeInsertQueueNext = _swipeInsertQueueNext,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
bool isOffline;
|
||||
@HiveField(1)
|
||||
bool shouldTranscode;
|
||||
@HiveField(2)
|
||||
int transcodeBitrate;
|
||||
|
||||
@Deprecated("Use downloadedLocationsMap instead")
|
||||
@HiveField(3)
|
||||
List<DownloadLocation> downloadLocations;
|
||||
|
||||
@HiveField(4)
|
||||
bool androidStopForegroundOnPause;
|
||||
@HiveField(5)
|
||||
Map<TabContentType, bool> showTabs;
|
||||
|
||||
/// Used to remember if the user has set their music screen to favourites
|
||||
/// mode.
|
||||
@HiveField(6)
|
||||
bool isFavourite;
|
||||
|
||||
/// Current sort by setting.
|
||||
@Deprecated("Use per-tab sort by instead")
|
||||
@HiveField(7)
|
||||
SortBy sortBy;
|
||||
|
||||
/// Current sort order setting.
|
||||
@Deprecated("Use per-tab sort order instead")
|
||||
@HiveField(8)
|
||||
SortOrder sortOrder;
|
||||
|
||||
/// Amount of songs to get when shuffling songs.
|
||||
@HiveField(9, defaultValue: _songShuffleItemCountDefault)
|
||||
int songShuffleItemCount;
|
||||
|
||||
/// The content view type used by the music screen.
|
||||
@HiveField(10, defaultValue: _contentViewType)
|
||||
ContentViewType contentViewType;
|
||||
|
||||
/// Amount of grid tiles to use per-row when portrait.
|
||||
@HiveField(11, defaultValue: _contentGridViewCrossAxisCountPortrait)
|
||||
int contentGridViewCrossAxisCountPortrait;
|
||||
|
||||
/// Amount of grid tiles to use per-row when landscape.
|
||||
@HiveField(12, defaultValue: _contentGridViewCrossAxisCountLandscape)
|
||||
int contentGridViewCrossAxisCountLandscape;
|
||||
|
||||
/// Whether or not to show the text (title, artist etc) on the grid music
|
||||
/// screen.
|
||||
@HiveField(13, defaultValue: _showTextOnGridView)
|
||||
bool showTextOnGridView = _showTextOnGridView;
|
||||
|
||||
/// The number of seconds to wait in a sleep timer. This is so that the app
|
||||
/// can remember the last duration. I'd use a Duration type here but Hive
|
||||
/// doesn't come with an adapter for it by default.
|
||||
@HiveField(14, defaultValue: _sleepTimerSeconds)
|
||||
int sleepTimerSeconds;
|
||||
|
||||
@HiveField(15, defaultValue: {})
|
||||
Map<String, DownloadLocation> downloadLocationsMap;
|
||||
|
||||
/// Whether or not to use blurred cover art as background on player screen.
|
||||
@HiveField(16, defaultValue: _showCoverAsPlayerBackground)
|
||||
bool showCoverAsPlayerBackground = _showCoverAsPlayerBackground;
|
||||
|
||||
@HiveField(17, defaultValue: _hideSongArtistsIfSameAsAlbumArtists)
|
||||
bool hideSongArtistsIfSameAsAlbumArtists =
|
||||
_hideSongArtistsIfSameAsAlbumArtists;
|
||||
|
||||
@HiveField(18, defaultValue: _bufferDurationSeconds)
|
||||
int bufferDurationSeconds;
|
||||
|
||||
@HiveField(19, defaultValue: _disableGesture)
|
||||
bool disableGesture = _disableGesture;
|
||||
|
||||
@HiveField(20, defaultValue: {})
|
||||
Map<TabContentType, SortBy> tabSortBy;
|
||||
|
||||
@HiveField(21, defaultValue: {})
|
||||
Map<TabContentType, SortOrder> tabSortOrder;
|
||||
|
||||
@HiveField(22, defaultValue: _tabOrder)
|
||||
List<TabContentType> tabOrder;
|
||||
|
||||
@HiveField(23, defaultValue: false)
|
||||
bool hasCompletedBlurhashImageMigration;
|
||||
|
||||
@HiveField(24, defaultValue: false)
|
||||
bool hasCompletedBlurhashImageMigrationIdFix;
|
||||
|
||||
@HiveField(25, defaultValue: _showFastScroller)
|
||||
bool showFastScroller = _showFastScroller;
|
||||
|
||||
@HiveField(26, defaultValue: _swipeInsertQueueNext)
|
||||
bool swipeInsertQueueNext;
|
||||
|
||||
static Future<FinampSettings> create() async {
|
||||
final internalSongDir = await getInternalSongDir();
|
||||
final downloadLocation = DownloadLocation.create(
|
||||
name: "Internal Storage",
|
||||
path: internalSongDir.path,
|
||||
useHumanReadableNames: false,
|
||||
deletable: false,
|
||||
);
|
||||
return FinampSettings(
|
||||
downloadLocations: [],
|
||||
// Create a map of TabContentType from TabContentType's values.
|
||||
showTabs: Map.fromEntries(
|
||||
TabContentType.values.map(
|
||||
(e) => MapEntry(e, true),
|
||||
),
|
||||
),
|
||||
downloadLocationsMap: {downloadLocation.id: downloadLocation},
|
||||
tabSortBy: {},
|
||||
tabSortOrder: {},
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the DownloadLocation that is the internal song dir. See the
|
||||
/// description of the "deletable" property to see how this works. This can
|
||||
/// technically throw a StateError, but that should never happen™.
|
||||
DownloadLocation get internalSongDir =>
|
||||
downloadLocationsMap.values.firstWhere((element) => !element.deletable);
|
||||
|
||||
Duration get bufferDuration => Duration(seconds: bufferDurationSeconds);
|
||||
|
||||
set bufferDuration(Duration duration) =>
|
||||
bufferDurationSeconds = duration.inSeconds;
|
||||
|
||||
SortBy getTabSortBy(TabContentType tabType) {
|
||||
return tabSortBy[tabType] ?? SortBy.sortName;
|
||||
}
|
||||
|
||||
SortOrder getSortOrder(TabContentType tabType) {
|
||||
return tabSortOrder[tabType] ?? SortOrder.ascending;
|
||||
}
|
||||
|
||||
bool get shouldRunBlurhashImageMigrationIdFix =>
|
||||
hasCompletedBlurhashImageMigration &&
|
||||
!hasCompletedBlurhashImageMigrationIdFix;
|
||||
}
|
||||
|
||||
/// Custom storage locations for storing music.
|
||||
@HiveType(typeId: 31)
|
||||
class DownloadLocation {
|
||||
DownloadLocation(
|
||||
{required this.name,
|
||||
required this.path,
|
||||
required this.useHumanReadableNames,
|
||||
required this.deletable,
|
||||
required this.id});
|
||||
|
||||
/// Human-readable name for the path (shown in settings)
|
||||
@HiveField(0)
|
||||
String name;
|
||||
|
||||
/// The path. We store this as a string since it's easier to put into Hive.
|
||||
@HiveField(1)
|
||||
String path;
|
||||
|
||||
/// If true, store songs using their actual names instead of Jellyfin item IDs.
|
||||
@HiveField(2)
|
||||
bool useHumanReadableNames;
|
||||
|
||||
/// If true, the user can delete this storage location. It's a bit of a hack,
|
||||
/// but the only undeletable location is the internal storage dir, so we can
|
||||
/// use this value to get the internal song dir.
|
||||
@HiveField(3)
|
||||
bool deletable;
|
||||
|
||||
/// Unique ID for the DownloadLocation. If this DownloadLocation was created
|
||||
/// before 0.6, it will be "0", very temporarily until it is changed on
|
||||
/// startup.
|
||||
@HiveField(4, defaultValue: "0")
|
||||
String id;
|
||||
|
||||
/// Initialises a new DownloadLocation. id will be a UUID.
|
||||
static DownloadLocation create({
|
||||
required String name,
|
||||
required String path,
|
||||
required bool useHumanReadableNames,
|
||||
required bool deletable,
|
||||
}) {
|
||||
return DownloadLocation(
|
||||
name: name,
|
||||
path: path,
|
||||
useHumanReadableNames: useHumanReadableNames,
|
||||
deletable: deletable,
|
||||
id: const Uuid().v4(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Class used in AddDownloadLocationScreen. Basically just a DownloadLocation
|
||||
/// with nullable values. Shouldn't be used for actually storing download
|
||||
/// locations.
|
||||
class NewDownloadLocation {
|
||||
NewDownloadLocation({
|
||||
this.name,
|
||||
this.path,
|
||||
this.useHumanReadableNames,
|
||||
required this.deletable,
|
||||
});
|
||||
|
||||
String? name;
|
||||
String? path;
|
||||
bool? useHumanReadableNames;
|
||||
bool deletable;
|
||||
}
|
||||
|
||||
/// Supported tab types in MusicScreenTabView.
|
||||
@HiveType(typeId: 36)
|
||||
enum TabContentType {
|
||||
@HiveField(0)
|
||||
albums,
|
||||
@HiveField(1)
|
||||
artists,
|
||||
@HiveField(2)
|
||||
playlists,
|
||||
@HiveField(3)
|
||||
genres,
|
||||
@HiveField(4)
|
||||
songs;
|
||||
|
||||
/// Human-readable version of the [TabContentType]. For example, toString() on
|
||||
/// [TabContentType.songs], toString() would return "TabContentType.songs".
|
||||
/// With this function, the same input would return "Songs".
|
||||
@override
|
||||
@Deprecated("Use toLocalisedString when possible")
|
||||
String toString() => _humanReadableName(this);
|
||||
|
||||
String toLocalisedString(BuildContext context) =>
|
||||
_humanReadableLocalisedName(this, context);
|
||||
|
||||
String _humanReadableName(TabContentType tabContentType) {
|
||||
switch (tabContentType) {
|
||||
case TabContentType.songs:
|
||||
return "Songs";
|
||||
case TabContentType.albums:
|
||||
return "Albums";
|
||||
case TabContentType.artists:
|
||||
return "Artists";
|
||||
case TabContentType.genres:
|
||||
return "Genres";
|
||||
case TabContentType.playlists:
|
||||
return "Playlists";
|
||||
}
|
||||
}
|
||||
|
||||
String _humanReadableLocalisedName(
|
||||
TabContentType tabContentType, BuildContext context) {
|
||||
switch (tabContentType) {
|
||||
case TabContentType.songs:
|
||||
return AppLocalizations.of(context)!.songs;
|
||||
case TabContentType.albums:
|
||||
return AppLocalizations.of(context)!.albums;
|
||||
case TabContentType.artists:
|
||||
return AppLocalizations.of(context)!.artists;
|
||||
case TabContentType.genres:
|
||||
return AppLocalizations.of(context)!.genres;
|
||||
case TabContentType.playlists:
|
||||
return AppLocalizations.of(context)!.playlists;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 39)
|
||||
enum ContentViewType {
|
||||
@HiveField(0)
|
||||
list,
|
||||
@HiveField(1)
|
||||
grid;
|
||||
|
||||
/// Human-readable version of this enum. I've written longer descriptions on
|
||||
/// enums like [TabContentType], and I can't be bothered to copy and paste it
|
||||
/// again.
|
||||
@override
|
||||
@Deprecated("Use toLocalisedString when possible")
|
||||
String toString() => _humanReadableName(this);
|
||||
|
||||
String toLocalisedString(BuildContext context) =>
|
||||
_humanReadableLocalisedName(this, context);
|
||||
|
||||
String _humanReadableName(ContentViewType contentViewType) {
|
||||
switch (contentViewType) {
|
||||
case ContentViewType.list:
|
||||
return "List";
|
||||
case ContentViewType.grid:
|
||||
return "Grid";
|
||||
}
|
||||
}
|
||||
|
||||
String _humanReadableLocalisedName(
|
||||
ContentViewType contentViewType, BuildContext context) {
|
||||
switch (contentViewType) {
|
||||
case ContentViewType.list:
|
||||
return AppLocalizations.of(context)!.list;
|
||||
case ContentViewType.grid:
|
||||
return AppLocalizations.of(context)!.grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
@JsonSerializable(
|
||||
explicitToJson: true,
|
||||
anyMap: true,
|
||||
)
|
||||
class DownloadedSong {
|
||||
DownloadedSong({
|
||||
required this.song,
|
||||
required this.mediaSourceInfo,
|
||||
required this.downloadId,
|
||||
required this.requiredBy,
|
||||
required this.path,
|
||||
required this.useHumanReadableNames,
|
||||
required this.viewId,
|
||||
this.isPathRelative = true,
|
||||
required this.downloadLocationId,
|
||||
});
|
||||
|
||||
/// The Jellyfin item for the song
|
||||
@HiveField(0)
|
||||
BaseItemDto song;
|
||||
|
||||
/// The media source info for the song (used to get file format)
|
||||
@HiveField(1)
|
||||
MediaSourceInfo mediaSourceInfo;
|
||||
|
||||
/// The download ID of the song (for FlutterDownloader)
|
||||
@HiveField(2)
|
||||
String downloadId;
|
||||
|
||||
/// The list of parent item IDs the item is downloaded for. If this is 0, the
|
||||
/// song should be deleted.
|
||||
@HiveField(3)
|
||||
List<String> requiredBy;
|
||||
|
||||
/// The path of the song file. if [isPathRelative] is true, this will be a
|
||||
/// relative path from the song's DownloadLocation.
|
||||
@HiveField(4)
|
||||
String path;
|
||||
|
||||
/// Whether or not the file is stored with a human readable name. We need this
|
||||
/// when deleting downloads, as we need to check for empty folders when
|
||||
/// deleting files with human readable names.
|
||||
@HiveField(5)
|
||||
bool useHumanReadableNames;
|
||||
|
||||
/// The view that this download is in. Used for sorting in offline mode.
|
||||
@HiveField(6)
|
||||
String viewId;
|
||||
|
||||
/// Whether or not [path] is relative.
|
||||
@HiveField(7, defaultValue: false)
|
||||
bool isPathRelative;
|
||||
|
||||
/// The ID of the DownloadLocation that holds this file. Will be null if made
|
||||
/// before 0.6.
|
||||
@HiveField(8)
|
||||
String? downloadLocationId;
|
||||
|
||||
File get file {
|
||||
if (isPathRelative) {
|
||||
final downloadLocation = FinampSettingsHelper
|
||||
.finampSettings.downloadLocationsMap[downloadLocationId];
|
||||
|
||||
if (downloadLocation == null) {
|
||||
throw "DownloadLocation was null in file getter for DownloadsSong!";
|
||||
}
|
||||
|
||||
return File(path_helper.join(downloadLocation.path, path));
|
||||
}
|
||||
|
||||
return File(path);
|
||||
}
|
||||
|
||||
DownloadLocation? get downloadLocation => FinampSettingsHelper
|
||||
.finampSettings.downloadLocationsMap[downloadLocationId];
|
||||
|
||||
Future<DownloadTask?> get downloadTask async {
|
||||
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '$downloadId'");
|
||||
|
||||
if (tasks?.isEmpty == false) {
|
||||
return tasks!.first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
factory DownloadedSong.fromJson(Map<String, dynamic> json) =>
|
||||
_$DownloadedSongFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$DownloadedSongToJson(this);
|
||||
}
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
class DownloadedParent {
|
||||
DownloadedParent({
|
||||
required this.item,
|
||||
required this.downloadedChildren,
|
||||
required this.viewId,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
BaseItemDto item;
|
||||
@HiveField(1)
|
||||
Map<String, BaseItemDto> downloadedChildren;
|
||||
|
||||
/// The view that this download is in. Used for sorting in offline mode.
|
||||
@HiveField(2)
|
||||
String viewId;
|
||||
}
|
||||
|
||||
@HiveType(typeId: 40)
|
||||
class DownloadedImage {
|
||||
DownloadedImage({
|
||||
required this.id,
|
||||
required this.downloadId,
|
||||
required this.path,
|
||||
required this.requiredBy,
|
||||
required this.downloadLocationId,
|
||||
});
|
||||
|
||||
/// The image ID
|
||||
@HiveField(0)
|
||||
String id;
|
||||
|
||||
/// The download ID of the song (for FlutterDownloader)
|
||||
@HiveField(1)
|
||||
String downloadId;
|
||||
|
||||
/// The relative path to the image file. To get the absolute path, use the
|
||||
/// file getter.
|
||||
@HiveField(2)
|
||||
String path;
|
||||
|
||||
/// The list of item IDs that use this image. If this is empty, the image
|
||||
/// should be deleted.
|
||||
/// TODO: Investigate adding set support to Hive
|
||||
@HiveField(3)
|
||||
List<String> requiredBy;
|
||||
|
||||
/// The ID of the DownloadLocation that holds this file.
|
||||
@HiveField(4)
|
||||
String downloadLocationId;
|
||||
|
||||
DownloadLocation? get downloadLocation => FinampSettingsHelper
|
||||
.finampSettings.downloadLocationsMap[downloadLocationId];
|
||||
|
||||
File get file {
|
||||
if (downloadLocation == null) {
|
||||
throw "Download location is null for image $id, this shouldn't happen...";
|
||||
}
|
||||
|
||||
return File(path_helper.join(downloadLocation!.path, path));
|
||||
}
|
||||
|
||||
Future<DownloadTask?> get downloadTask async {
|
||||
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '$downloadId'");
|
||||
|
||||
if (tasks?.isEmpty == false) {
|
||||
return tasks!.first;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Creates a new DownloadedImage. Does not actually handle downloading or
|
||||
/// anything. This is only really a thing since having to manually specify
|
||||
/// empty lists is a bit jank.
|
||||
static DownloadedImage create({
|
||||
required String id,
|
||||
required String downloadId,
|
||||
required String path,
|
||||
List<String>? requiredBy,
|
||||
required String downloadLocationId,
|
||||
}) =>
|
||||
DownloadedImage(
|
||||
id: id,
|
||||
downloadId: downloadId,
|
||||
path: path,
|
||||
requiredBy: requiredBy ?? [],
|
||||
downloadLocationId: downloadLocationId,
|
||||
);
|
||||
}
|
||||
|
||||
@HiveType(typeId: 43)
|
||||
class OfflineListen {
|
||||
OfflineListen({
|
||||
required this.timestamp,
|
||||
required this.userId,
|
||||
required this.itemId,
|
||||
required this.name,
|
||||
this.artist,
|
||||
this.album,
|
||||
this.trackMbid,
|
||||
});
|
||||
|
||||
/// The stop timestamp of the listen, measured in seconds since the epoch.
|
||||
@HiveField(0)
|
||||
int timestamp;
|
||||
|
||||
@HiveField(1)
|
||||
String userId;
|
||||
|
||||
@HiveField(2)
|
||||
String itemId;
|
||||
|
||||
@HiveField(3)
|
||||
String name;
|
||||
|
||||
@HiveField(4)
|
||||
String? artist;
|
||||
|
||||
@HiveField(5)
|
||||
String? album;
|
||||
|
||||
// The MusicBrainz ID of the track, if available.
|
||||
@HiveField(6)
|
||||
String? trackMbid;
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'finamp_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class FinampUserAdapter extends TypeAdapter<FinampUser> {
|
||||
@override
|
||||
final int typeId = 8;
|
||||
|
||||
@override
|
||||
FinampUser read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return FinampUser(
|
||||
id: fields[0] as String,
|
||||
baseUrl: fields[1] as String,
|
||||
accessToken: fields[2] as String,
|
||||
serverId: fields[3] as String,
|
||||
currentViewId: fields[4] as String?,
|
||||
views: (fields[5] as Map).cast<String, BaseItemDto>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, FinampUser obj) {
|
||||
writer
|
||||
..writeByte(6)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.baseUrl)
|
||||
..writeByte(2)
|
||||
..write(obj.accessToken)
|
||||
..writeByte(3)
|
||||
..write(obj.serverId)
|
||||
..writeByte(4)
|
||||
..write(obj.currentViewId)
|
||||
..writeByte(5)
|
||||
..write(obj.views);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FinampUserAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class FinampSettingsAdapter extends TypeAdapter<FinampSettings> {
|
||||
@override
|
||||
final int typeId = 28;
|
||||
|
||||
@override
|
||||
FinampSettings read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return FinampSettings(
|
||||
isOffline: fields[0] as bool,
|
||||
shouldTranscode: fields[1] as bool,
|
||||
transcodeBitrate: fields[2] as int,
|
||||
downloadLocations: (fields[3] as List).cast<DownloadLocation>(),
|
||||
androidStopForegroundOnPause: fields[4] as bool,
|
||||
showTabs: (fields[5] as Map).cast<TabContentType, bool>(),
|
||||
isFavourite: fields[6] as bool,
|
||||
sortBy: fields[7] as SortBy,
|
||||
sortOrder: fields[8] as SortOrder,
|
||||
songShuffleItemCount: fields[9] == null ? 250 : fields[9] as int,
|
||||
contentViewType: fields[10] == null
|
||||
? ContentViewType.list
|
||||
: fields[10] as ContentViewType,
|
||||
contentGridViewCrossAxisCountPortrait:
|
||||
fields[11] == null ? 2 : fields[11] as int,
|
||||
contentGridViewCrossAxisCountLandscape:
|
||||
fields[12] == null ? 3 : fields[12] as int,
|
||||
showTextOnGridView: fields[13] == null ? true : fields[13] as bool,
|
||||
sleepTimerSeconds: fields[14] == null ? 1800 : fields[14] as int,
|
||||
downloadLocationsMap: fields[15] == null
|
||||
? {}
|
||||
: (fields[15] as Map).cast<String, DownloadLocation>(),
|
||||
showCoverAsPlayerBackground:
|
||||
fields[16] == null ? true : fields[16] as bool,
|
||||
hideSongArtistsIfSameAsAlbumArtists:
|
||||
fields[17] == null ? true : fields[17] as bool,
|
||||
bufferDurationSeconds: fields[18] == null ? 50 : fields[18] as int,
|
||||
tabSortBy: fields[20] == null
|
||||
? {}
|
||||
: (fields[20] as Map).cast<TabContentType, SortBy>(),
|
||||
tabSortOrder: fields[21] == null
|
||||
? {}
|
||||
: (fields[21] as Map).cast<TabContentType, SortOrder>(),
|
||||
tabOrder: fields[22] == null
|
||||
? [
|
||||
TabContentType.albums,
|
||||
TabContentType.artists,
|
||||
TabContentType.playlists,
|
||||
TabContentType.genres,
|
||||
TabContentType.songs
|
||||
]
|
||||
: (fields[22] as List).cast<TabContentType>(),
|
||||
hasCompletedBlurhashImageMigration:
|
||||
fields[23] == null ? false : fields[23] as bool,
|
||||
hasCompletedBlurhashImageMigrationIdFix:
|
||||
fields[24] == null ? false : fields[24] as bool,
|
||||
swipeInsertQueueNext: fields[26] == null ? false : fields[26] as bool,
|
||||
)
|
||||
..disableGesture = fields[19] == null ? false : fields[19] as bool
|
||||
..showFastScroller = fields[25] == null ? true : fields[25] as bool;
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, FinampSettings obj) {
|
||||
writer
|
||||
..writeByte(27)
|
||||
..writeByte(0)
|
||||
..write(obj.isOffline)
|
||||
..writeByte(1)
|
||||
..write(obj.shouldTranscode)
|
||||
..writeByte(2)
|
||||
..write(obj.transcodeBitrate)
|
||||
..writeByte(3)
|
||||
..write(obj.downloadLocations)
|
||||
..writeByte(4)
|
||||
..write(obj.androidStopForegroundOnPause)
|
||||
..writeByte(5)
|
||||
..write(obj.showTabs)
|
||||
..writeByte(6)
|
||||
..write(obj.isFavourite)
|
||||
..writeByte(7)
|
||||
..write(obj.sortBy)
|
||||
..writeByte(8)
|
||||
..write(obj.sortOrder)
|
||||
..writeByte(9)
|
||||
..write(obj.songShuffleItemCount)
|
||||
..writeByte(10)
|
||||
..write(obj.contentViewType)
|
||||
..writeByte(11)
|
||||
..write(obj.contentGridViewCrossAxisCountPortrait)
|
||||
..writeByte(12)
|
||||
..write(obj.contentGridViewCrossAxisCountLandscape)
|
||||
..writeByte(13)
|
||||
..write(obj.showTextOnGridView)
|
||||
..writeByte(14)
|
||||
..write(obj.sleepTimerSeconds)
|
||||
..writeByte(15)
|
||||
..write(obj.downloadLocationsMap)
|
||||
..writeByte(16)
|
||||
..write(obj.showCoverAsPlayerBackground)
|
||||
..writeByte(17)
|
||||
..write(obj.hideSongArtistsIfSameAsAlbumArtists)
|
||||
..writeByte(18)
|
||||
..write(obj.bufferDurationSeconds)
|
||||
..writeByte(19)
|
||||
..write(obj.disableGesture)
|
||||
..writeByte(20)
|
||||
..write(obj.tabSortBy)
|
||||
..writeByte(21)
|
||||
..write(obj.tabSortOrder)
|
||||
..writeByte(22)
|
||||
..write(obj.tabOrder)
|
||||
..writeByte(23)
|
||||
..write(obj.hasCompletedBlurhashImageMigration)
|
||||
..writeByte(24)
|
||||
..write(obj.hasCompletedBlurhashImageMigrationIdFix)
|
||||
..writeByte(25)
|
||||
..write(obj.showFastScroller)
|
||||
..writeByte(26)
|
||||
..write(obj.swipeInsertQueueNext);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FinampSettingsAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class DownloadLocationAdapter extends TypeAdapter<DownloadLocation> {
|
||||
@override
|
||||
final int typeId = 31;
|
||||
|
||||
@override
|
||||
DownloadLocation read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return DownloadLocation(
|
||||
name: fields[0] as String,
|
||||
path: fields[1] as String,
|
||||
useHumanReadableNames: fields[2] as bool,
|
||||
deletable: fields[3] as bool,
|
||||
id: fields[4] == null ? '0' : fields[4] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DownloadLocation obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
..write(obj.path)
|
||||
..writeByte(2)
|
||||
..write(obj.useHumanReadableNames)
|
||||
..writeByte(3)
|
||||
..write(obj.deletable)
|
||||
..writeByte(4)
|
||||
..write(obj.id);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DownloadLocationAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class DownloadedSongAdapter extends TypeAdapter<DownloadedSong> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
DownloadedSong read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return DownloadedSong(
|
||||
song: fields[0] as BaseItemDto,
|
||||
mediaSourceInfo: fields[1] as MediaSourceInfo,
|
||||
downloadId: fields[2] as String,
|
||||
requiredBy: (fields[3] as List).cast<String>(),
|
||||
path: fields[4] as String,
|
||||
useHumanReadableNames: fields[5] as bool,
|
||||
viewId: fields[6] as String,
|
||||
isPathRelative: fields[7] == null ? false : fields[7] as bool,
|
||||
downloadLocationId: fields[8] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DownloadedSong obj) {
|
||||
writer
|
||||
..writeByte(9)
|
||||
..writeByte(0)
|
||||
..write(obj.song)
|
||||
..writeByte(1)
|
||||
..write(obj.mediaSourceInfo)
|
||||
..writeByte(2)
|
||||
..write(obj.downloadId)
|
||||
..writeByte(3)
|
||||
..write(obj.requiredBy)
|
||||
..writeByte(4)
|
||||
..write(obj.path)
|
||||
..writeByte(5)
|
||||
..write(obj.useHumanReadableNames)
|
||||
..writeByte(6)
|
||||
..write(obj.viewId)
|
||||
..writeByte(7)
|
||||
..write(obj.isPathRelative)
|
||||
..writeByte(8)
|
||||
..write(obj.downloadLocationId);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DownloadedSongAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class DownloadedParentAdapter extends TypeAdapter<DownloadedParent> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
DownloadedParent read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return DownloadedParent(
|
||||
item: fields[0] as BaseItemDto,
|
||||
downloadedChildren: (fields[1] as Map).cast<String, BaseItemDto>(),
|
||||
viewId: fields[2] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DownloadedParent obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.item)
|
||||
..writeByte(1)
|
||||
..write(obj.downloadedChildren)
|
||||
..writeByte(2)
|
||||
..write(obj.viewId);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DownloadedParentAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class DownloadedImageAdapter extends TypeAdapter<DownloadedImage> {
|
||||
@override
|
||||
final int typeId = 40;
|
||||
|
||||
@override
|
||||
DownloadedImage read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return DownloadedImage(
|
||||
id: fields[0] as String,
|
||||
downloadId: fields[1] as String,
|
||||
path: fields[2] as String,
|
||||
requiredBy: (fields[3] as List).cast<String>(),
|
||||
downloadLocationId: fields[4] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DownloadedImage obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.downloadId)
|
||||
..writeByte(2)
|
||||
..write(obj.path)
|
||||
..writeByte(3)
|
||||
..write(obj.requiredBy)
|
||||
..writeByte(4)
|
||||
..write(obj.downloadLocationId);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DownloadedImageAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class OfflineListenAdapter extends TypeAdapter<OfflineListen> {
|
||||
@override
|
||||
final int typeId = 43;
|
||||
|
||||
@override
|
||||
OfflineListen read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return OfflineListen(
|
||||
timestamp: fields[0] as int,
|
||||
userId: fields[1] as String,
|
||||
itemId: fields[2] as String,
|
||||
name: fields[3] as String,
|
||||
artist: fields[4] as String?,
|
||||
album: fields[5] as String?,
|
||||
trackMbid: fields[6] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, OfflineListen obj) {
|
||||
writer
|
||||
..writeByte(7)
|
||||
..writeByte(0)
|
||||
..write(obj.timestamp)
|
||||
..writeByte(1)
|
||||
..write(obj.userId)
|
||||
..writeByte(2)
|
||||
..write(obj.itemId)
|
||||
..writeByte(3)
|
||||
..write(obj.name)
|
||||
..writeByte(4)
|
||||
..write(obj.artist)
|
||||
..writeByte(5)
|
||||
..write(obj.album)
|
||||
..writeByte(6)
|
||||
..write(obj.trackMbid);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is OfflineListenAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class TabContentTypeAdapter extends TypeAdapter<TabContentType> {
|
||||
@override
|
||||
final int typeId = 36;
|
||||
|
||||
@override
|
||||
TabContentType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return TabContentType.albums;
|
||||
case 1:
|
||||
return TabContentType.artists;
|
||||
case 2:
|
||||
return TabContentType.playlists;
|
||||
case 3:
|
||||
return TabContentType.genres;
|
||||
case 4:
|
||||
return TabContentType.songs;
|
||||
default:
|
||||
return TabContentType.albums;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, TabContentType obj) {
|
||||
switch (obj) {
|
||||
case TabContentType.albums:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case TabContentType.artists:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case TabContentType.playlists:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case TabContentType.genres:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case TabContentType.songs:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TabContentTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ContentViewTypeAdapter extends TypeAdapter<ContentViewType> {
|
||||
@override
|
||||
final int typeId = 39;
|
||||
|
||||
@override
|
||||
ContentViewType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ContentViewType.list;
|
||||
case 1:
|
||||
return ContentViewType.grid;
|
||||
default:
|
||||
return ContentViewType.list;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ContentViewType obj) {
|
||||
switch (obj) {
|
||||
case ContentViewType.list:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case ContentViewType.grid:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ContentViewTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
DownloadedSong _$DownloadedSongFromJson(Map json) => DownloadedSong(
|
||||
song:
|
||||
BaseItemDto.fromJson(Map<String, dynamic>.from(json['song'] as Map)),
|
||||
mediaSourceInfo: MediaSourceInfo.fromJson(
|
||||
Map<String, dynamic>.from(json['mediaSourceInfo'] as Map)),
|
||||
downloadId: json['downloadId'] as String,
|
||||
requiredBy: (json['requiredBy'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
path: json['path'] as String,
|
||||
useHumanReadableNames: json['useHumanReadableNames'] as bool,
|
||||
viewId: json['viewId'] as String,
|
||||
isPathRelative: json['isPathRelative'] as bool? ?? true,
|
||||
downloadLocationId: json['downloadLocationId'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DownloadedSongToJson(DownloadedSong instance) =>
|
||||
<String, dynamic>{
|
||||
'song': instance.song.toJson(),
|
||||
'mediaSourceInfo': instance.mediaSourceInfo.toJson(),
|
||||
'downloadId': instance.downloadId,
|
||||
'requiredBy': instance.requiredBy,
|
||||
'path': instance.path,
|
||||
'useHumanReadableNames': instance.useHumanReadableNames,
|
||||
'viewId': instance.viewId,
|
||||
'isPathRelative': instance.isPathRelative,
|
||||
'downloadLocationId': instance.downloadLocationId,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class LocaleAdapter extends TypeAdapter<Locale> {
|
||||
@override
|
||||
int get typeId => 42;
|
||||
|
||||
@override
|
||||
Locale read(BinaryReader reader) {
|
||||
final localeList = reader.readStringList();
|
||||
|
||||
final languageCode = localeList[0];
|
||||
|
||||
// scriptCode and countryCode are empty strings when null (see write)
|
||||
final scriptCode = localeList[1] == "" ? null : localeList[1];
|
||||
final countryCode = localeList[2] == "" ? null : localeList[2];
|
||||
|
||||
return Locale.fromSubtags(
|
||||
languageCode: languageCode,
|
||||
scriptCode: scriptCode,
|
||||
countryCode: countryCode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Locale obj) {
|
||||
writer.writeStringList([
|
||||
obj.languageCode,
|
||||
obj.scriptCode ?? "",
|
||||
obj.countryCode ?? "",
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class ThemeModeAdapter extends TypeAdapter<ThemeMode> {
|
||||
@override
|
||||
final int typeId = 41;
|
||||
|
||||
@override
|
||||
ThemeMode read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ThemeMode.system;
|
||||
case 1:
|
||||
return ThemeMode.light;
|
||||
case 2:
|
||||
return ThemeMode.dark;
|
||||
default:
|
||||
return ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ThemeMode obj) {
|
||||
switch (obj) {
|
||||
case ThemeMode.system:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case ThemeMode.light:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case ThemeMode.dark:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ThemeModeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
Reference in New Issue
Block a user