mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: update FriendItem and HorizontalPlaybuttonCardView styles, remove unused imports, and clean up dependencies
This commit is contained in:
parent
2fefd65f51
commit
05d544fe5a
@ -9,6 +9,17 @@
|
|||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class $AssetsBackgroundsGen {
|
||||||
|
const $AssetsBackgroundsGen();
|
||||||
|
|
||||||
|
/// File path: assets/backgrounds/xmas-effect.png
|
||||||
|
AssetGenImage get xmasEffect =>
|
||||||
|
const AssetGenImage('assets/backgrounds/xmas-effect.png');
|
||||||
|
|
||||||
|
/// List of all assets
|
||||||
|
List<AssetGenImage> get values => [xmasEffect];
|
||||||
|
}
|
||||||
|
|
||||||
class $AssetsLogosGen {
|
class $AssetsLogosGen {
|
||||||
const $AssetsLogosGen();
|
const $AssetsLogosGen();
|
||||||
|
|
||||||
@ -46,6 +57,7 @@ class Assets {
|
|||||||
static const String license = 'LICENSE';
|
static const String license = 'LICENSE';
|
||||||
static const AssetGenImage albumPlaceholder =
|
static const AssetGenImage albumPlaceholder =
|
||||||
AssetGenImage('assets/album-placeholder.png');
|
AssetGenImage('assets/album-placeholder.png');
|
||||||
|
static const $AssetsBackgroundsGen backgrounds = $AssetsBackgroundsGen();
|
||||||
static const AssetGenImage bengaliPatternsBg =
|
static const AssetGenImage bengaliPatternsBg =
|
||||||
AssetGenImage('assets/bengali-patterns-bg.jpg');
|
AssetGenImage('assets/bengali-patterns-bg.jpg');
|
||||||
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
|
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
|
||||||
@ -61,6 +73,8 @@ class Assets {
|
|||||||
AssetGenImage('assets/spotube-hero-banner.png');
|
AssetGenImage('assets/spotube-hero-banner.png');
|
||||||
static const AssetGenImage spotubeLogoForeground =
|
static const AssetGenImage spotubeLogoForeground =
|
||||||
AssetGenImage('assets/spotube-logo-foreground.jpg');
|
AssetGenImage('assets/spotube-logo-foreground.jpg');
|
||||||
|
static const AssetGenImage spotubeLogoMacos =
|
||||||
|
AssetGenImage('assets/spotube-logo-macos.png');
|
||||||
static const AssetGenImage spotubeLogoBmp =
|
static const AssetGenImage spotubeLogoBmp =
|
||||||
AssetGenImage('assets/spotube-logo.bmp');
|
AssetGenImage('assets/spotube-logo.bmp');
|
||||||
static const String spotubeLogoIco = 'assets/spotube-logo.ico';
|
static const String spotubeLogoIco = 'assets/spotube-logo.ico';
|
||||||
@ -104,6 +118,7 @@ class Assets {
|
|||||||
placeholder,
|
placeholder,
|
||||||
spotubeHeroBanner,
|
spotubeHeroBanner,
|
||||||
spotubeLogoForeground,
|
spotubeLogoForeground,
|
||||||
|
spotubeLogoMacos,
|
||||||
spotubeLogoBmp,
|
spotubeLogoBmp,
|
||||||
spotubeLogoIco,
|
spotubeLogoIco,
|
||||||
spotubeLogoPng,
|
spotubeLogoPng,
|
||||||
|
24
lib/collections/fonts.gen.dart
Normal file
24
lib/collections/fonts.gen.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
/// *****************************************************
|
||||||
|
/// FlutterGen
|
||||||
|
/// *****************************************************
|
||||||
|
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
|
||||||
|
|
||||||
|
class FontFamily {
|
||||||
|
FontFamily._();
|
||||||
|
|
||||||
|
/// Font family: BootstrapIcons
|
||||||
|
static const String bootstrapIcons = 'BootstrapIcons';
|
||||||
|
|
||||||
|
/// Font family: GeistMono
|
||||||
|
static const String geistMono = 'GeistMono';
|
||||||
|
|
||||||
|
/// Font family: GeistSans
|
||||||
|
static const String geistSans = 'GeistSans';
|
||||||
|
|
||||||
|
/// Font family: RadixIcons
|
||||||
|
static const String radixIcons = 'RadixIcons';
|
||||||
|
}
|
@ -1,106 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
|
|
||||||
import 'package:popover/popover.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
|
||||||
|
|
||||||
class Action extends StatelessWidget {
|
|
||||||
final Widget text;
|
|
||||||
final Widget icon;
|
|
||||||
final void Function() onPressed;
|
|
||||||
final bool isExpanded;
|
|
||||||
final Color? backgroundColor;
|
|
||||||
const Action({
|
|
||||||
super.key,
|
|
||||||
required this.icon,
|
|
||||||
required this.text,
|
|
||||||
required this.onPressed,
|
|
||||||
this.isExpanded = true,
|
|
||||||
this.backgroundColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (isExpanded != true) {
|
|
||||||
return IconButton(
|
|
||||||
icon: icon,
|
|
||||||
onPressed: onPressed,
|
|
||||||
style: IconButton.styleFrom(
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
),
|
|
||||||
tooltip: text is Text
|
|
||||||
? (text as Text).data
|
|
||||||
: text.toStringShallow().split(",").last.replaceAll(
|
|
||||||
"\"",
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
tileColor: backgroundColor,
|
|
||||||
onTap: onPressed,
|
|
||||||
leading: icon,
|
|
||||||
title: text,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdaptiveActions extends HookWidget {
|
|
||||||
final List<Action> actions;
|
|
||||||
final bool? breakOn;
|
|
||||||
const AdaptiveActions({
|
|
||||||
required this.actions,
|
|
||||||
this.breakOn,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
|
||||||
|
|
||||||
if (breakOn ?? mediaQuery.lgAndUp) {
|
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(SpotubeIcons.moreHorizontal),
|
|
||||||
onPressed: () {
|
|
||||||
showPopover(
|
|
||||||
context: context,
|
|
||||||
direction: PopoverDirection.left,
|
|
||||||
bodyBuilder: (context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: actions
|
|
||||||
.map(
|
|
||||||
(action) => SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: action),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
backgroundColor: Theme.of(context).cardColor,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: actions.map((action) {
|
|
||||||
return Action(
|
|
||||||
icon: action.icon,
|
|
||||||
onPressed: action.onPressed,
|
|
||||||
text: action.text,
|
|
||||||
backgroundColor: action.backgroundColor,
|
|
||||||
isExpanded: false,
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
library bordered_text;
|
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
/// Adds stroke to text widget
|
|
||||||
/// We can apply a very thin and subtle stroke to a [Text]
|
|
||||||
/// ```dart
|
|
||||||
/// BorderedText(
|
|
||||||
/// strokeWidth: 1.0,
|
|
||||||
/// text: Text(
|
|
||||||
/// 'Bordered Text',
|
|
||||||
/// style: TextStyle(
|
|
||||||
/// decoration: TextDecoration.none,
|
|
||||||
/// decorationStyle: TextDecorationStyle.wavy,
|
|
||||||
/// decorationColor: Colors.red,
|
|
||||||
/// ),
|
|
||||||
/// ),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
class BorderedText extends StatelessWidget {
|
|
||||||
const BorderedText({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
this.strokeCap = StrokeCap.round,
|
|
||||||
this.strokeJoin = StrokeJoin.round,
|
|
||||||
this.strokeWidth = 6.0,
|
|
||||||
this.strokeColor = const Color.fromRGBO(53, 0, 71, 1),
|
|
||||||
});
|
|
||||||
|
|
||||||
/// the stroke cap style
|
|
||||||
final StrokeCap strokeCap;
|
|
||||||
|
|
||||||
/// the stroke joint style
|
|
||||||
final StrokeJoin strokeJoin;
|
|
||||||
|
|
||||||
/// the stroke width
|
|
||||||
final double strokeWidth;
|
|
||||||
|
|
||||||
/// the stroke color
|
|
||||||
final Color strokeColor;
|
|
||||||
|
|
||||||
/// the [Text] widget to apply stroke on
|
|
||||||
final Text child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
TextStyle style;
|
|
||||||
if (child.style != null) {
|
|
||||||
style = child.style!.copyWith(
|
|
||||||
foreground: Paint()
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeCap = strokeCap
|
|
||||||
..strokeJoin = strokeJoin
|
|
||||||
..strokeWidth = strokeWidth
|
|
||||||
..color = strokeColor,
|
|
||||||
color: null,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
style = TextStyle(
|
|
||||||
foreground: Paint()
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeCap = strokeCap
|
|
||||||
..strokeJoin = strokeJoin
|
|
||||||
..strokeWidth = strokeWidth
|
|
||||||
..color = strokeColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
textDirection: child.textDirection,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
child.data!,
|
|
||||||
style: style,
|
|
||||||
maxLines: child.maxLines,
|
|
||||||
overflow: child.overflow,
|
|
||||||
semanticsLabel: child.semanticsLabel,
|
|
||||||
softWrap: child.softWrap,
|
|
||||||
strutStyle: child.strutStyle,
|
|
||||||
textAlign: child.textAlign,
|
|
||||||
textDirection: child.textDirection,
|
|
||||||
textScaler: child.textScaler,
|
|
||||||
),
|
|
||||||
child,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
|
|
||||||
import 'package:popover/popover.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
|
||||||
|
|
||||||
class CompactSearch extends HookWidget {
|
|
||||||
final ValueChanged<String>? onChanged;
|
|
||||||
final String placeholder;
|
|
||||||
final IconData icon;
|
|
||||||
final Color? iconColor;
|
|
||||||
|
|
||||||
const CompactSearch({
|
|
||||||
super.key,
|
|
||||||
this.onChanged,
|
|
||||||
this.placeholder = "Search...",
|
|
||||||
this.icon = SpotubeIcons.search,
|
|
||||||
this.iconColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showPopover(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Theme.of(context).cardColor,
|
|
||||||
transitionDuration: const Duration(milliseconds: 100),
|
|
||||||
barrierColor: Colors.transparent,
|
|
||||||
arrowDxOffset: -6,
|
|
||||||
bodyBuilder: (context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
width: 300,
|
|
||||||
child: TextField(
|
|
||||||
autofocus: true,
|
|
||||||
onChanged: onChanged,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: placeholder,
|
|
||||||
prefixIcon: Icon(icon),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
height: 60,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
tooltip: placeholder,
|
|
||||||
icon: Icon(icon, color: iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
|
|
||||||
class PipedDownDialog extends HookConsumerWidget {
|
|
||||||
const PipedDownDialog({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, ref) {
|
|
||||||
final pipedInstance =
|
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.pipedInstance));
|
|
||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
|
||||||
|
|
||||||
return AlertDialog(
|
|
||||||
insetPadding: const EdgeInsets.all(6),
|
|
||||||
contentPadding: const EdgeInsets.all(6),
|
|
||||||
icon: Icon(
|
|
||||||
SpotubeIcons.error,
|
|
||||||
color: colorScheme.error,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
context.l10n.piped_api_down,
|
|
||||||
style: TextStyle(color: colorScheme.error),
|
|
||||||
),
|
|
||||||
content: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child:
|
|
||||||
Text(context.l10n.piped_down_error_instructions(pipedInstance)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(context.l10n.ok),
|
|
||||||
),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(context.l10n.settings),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,9 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
DefaultTextStyle(
|
DefaultTextStyle(
|
||||||
style: context.theme.typography.h4,
|
style: context.theme.typography.h4.copyWith(
|
||||||
|
color: context.theme.colorScheme.foreground,
|
||||||
|
),
|
||||||
child: title,
|
child: title,
|
||||||
),
|
),
|
||||||
if (titleTrailing != null) titleTrailing!,
|
if (titleTrailing != null) titleTrailing!,
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import 'package:spotify/spotify.dart';
|
|
||||||
|
|
||||||
extension CursorPageJson<T> on CursorPage<T> {
|
|
||||||
static CursorPage<T> fromJson<T>(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
T Function(dynamic json) itemFromJson,
|
|
||||||
) {
|
|
||||||
final metadata = Paging.fromJson(json["metadata"]);
|
|
||||||
final paging = CursorPaging<T>();
|
|
||||||
paging.cursors = Cursor.fromJson(json["metadata"])..after = json["after"];
|
|
||||||
paging.href = metadata.href;
|
|
||||||
paging.itemsNative = paging.itemsNative;
|
|
||||||
paging.limit = metadata.limit;
|
|
||||||
paging.next = metadata.next;
|
|
||||||
return CursorPage<T>(
|
|
||||||
paging,
|
|
||||||
itemFromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
"after": after,
|
|
||||||
"metadata": metadata.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PagingToJson<T> on Paging<T> {
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
"items": itemsNative,
|
|
||||||
"total": total,
|
|
||||||
"next": next,
|
|
||||||
"previous": previous,
|
|
||||||
"limit": limit,
|
|
||||||
"offset": offset,
|
|
||||||
"href": href,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PageJson<T> on Page<T> {
|
|
||||||
static Page<T> fromJson<T>(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
T Function(dynamic json) itemFromJson,
|
|
||||||
) {
|
|
||||||
return Page<T>(
|
|
||||||
Paging<T>.fromJson(
|
|
||||||
Map.castFrom<dynamic, dynamic, String, dynamic>(json["metadata"]),
|
|
||||||
),
|
|
||||||
itemFromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
"metadata": metadata.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:sidebarx/sidebarx.dart';
|
|
||||||
|
|
||||||
/// Creates [SidebarXController] that will be disposed automatically.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
/// - [SidebarXController]
|
|
||||||
SidebarXController useSidebarXController({
|
|
||||||
required int selectedIndex,
|
|
||||||
bool? extended,
|
|
||||||
List<Object?>? keys,
|
|
||||||
}) {
|
|
||||||
return use(
|
|
||||||
_SidebarXControllerHook(
|
|
||||||
selectedIndex: selectedIndex,
|
|
||||||
extended: extended,
|
|
||||||
keys: keys,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SidebarXControllerHook extends Hook<SidebarXController> {
|
|
||||||
const _SidebarXControllerHook({
|
|
||||||
required this.selectedIndex,
|
|
||||||
this.extended,
|
|
||||||
super.keys,
|
|
||||||
});
|
|
||||||
|
|
||||||
final int selectedIndex;
|
|
||||||
final bool? extended;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HookState<SidebarXController, Hook<SidebarXController>> createState() =>
|
|
||||||
_SidebarXControllerHookState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SidebarXControllerHookState
|
|
||||||
extends HookState<SidebarXController, _SidebarXControllerHook> {
|
|
||||||
late final SidebarXController controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initHook() {
|
|
||||||
super.initHook();
|
|
||||||
controller = SidebarXController(
|
|
||||||
selectedIndex: hook.selectedIndex,
|
|
||||||
extended: hook.extended,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
SidebarXController build(BuildContext context) => controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() => controller.dispose();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get debugLabel => 'useSidebarXController';
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
|
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:home_widget/home_widget.dart';
|
import 'package:home_widget/home_widget.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:local_notifier/local_notifier.dart';
|
import 'package:local_notifier/local_notifier.dart';
|
||||||
@ -43,7 +43,6 @@ import 'package:spotube/services/wm_tools/wm_tools.dart';
|
|||||||
import 'package:spotube/utils/migrations/sandbox.dart';
|
import 'package:spotube/utils/migrations/sandbox.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
@ -100,11 +99,6 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
await KVStoreService.initialize();
|
await KVStoreService.initialize();
|
||||||
await EncryptedKvStoreService.initialize();
|
await EncryptedKvStoreService.initialize();
|
||||||
|
|
||||||
final hiveCacheDir =
|
|
||||||
kIsWeb ? null : (await getApplicationSupportDirectory()).path;
|
|
||||||
|
|
||||||
Hive.init(hiveCacheDir);
|
|
||||||
|
|
||||||
final database = AppDatabase();
|
final database = AppDatabase();
|
||||||
|
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
@ -222,6 +216,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
colorScheme: ColorSchemes.lightBlue(),
|
colorScheme: ColorSchemes.lightBlue(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
|
typography: const Typography.geist(),
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
radius: .5,
|
radius: .5,
|
||||||
@ -229,6 +224,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
colorScheme: ColorSchemes.darkNeutral(),
|
colorScheme: ColorSchemes.darkNeutral(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
|
typography: const Typography.geist(),
|
||||||
),
|
),
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
|
@ -4,8 +4,8 @@ import 'package:drift/drift.dart' as i1;
|
|||||||
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/services/sourced_track/enums.dart';
|
import 'package:spotube/services/sourced_track/enums.dart';
|
||||||
import 'package:spotube/utils/migrations/adapters.dart';
|
|
||||||
|
|
||||||
// GENERATED BY drift_dev, DO NOT MODIFY.
|
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||||
final class Schema2 extends i0.VersionedSchema {
|
final class Schema2 extends i0.VersionedSchema {
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
class SpotifySpotubeCredentials {
|
|
||||||
String clientId;
|
|
||||||
String accessToken;
|
|
||||||
DateTime expiration;
|
|
||||||
bool isAnonymous;
|
|
||||||
|
|
||||||
SpotifySpotubeCredentials({
|
|
||||||
required this.clientId,
|
|
||||||
required this.accessToken,
|
|
||||||
required this.expiration,
|
|
||||||
required this.isAnonymous,
|
|
||||||
});
|
|
||||||
|
|
||||||
SpotifySpotubeCredentials.fromJson(Map<String, dynamic> json)
|
|
||||||
: clientId = json['clientId'],
|
|
||||||
accessToken = json['accessToken'],
|
|
||||||
expiration = DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
json['accessTokenExpirationTimestampMs'],
|
|
||||||
),
|
|
||||||
isAnonymous = json['isAnonymous'];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'clientId': clientId,
|
|
||||||
'accessToken': accessToken,
|
|
||||||
'accessTokenExpirationTimestampMs': expiration.millisecondsSinceEpoch,
|
|
||||||
'isAnonymous': isAnonymous,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,6 +42,9 @@ class FriendItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
|
style: context.theme.typography.normal.copyWith(
|
||||||
|
color: context.theme.colorScheme.foreground,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: friend.track.name,
|
text: friend.track.name,
|
||||||
|
@ -12,7 +12,6 @@ import 'package:spotube/modules/album/album_card.dart';
|
|||||||
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication/authentication.dart';
|
import 'package:spotube/provider/authentication/authentication.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
@ -77,12 +76,14 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(10),
|
const SliverGap(10),
|
||||||
SliverLayoutBuilder(builder: (context, constrains) {
|
SliverPadding(
|
||||||
return SliverGrid.builder(
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
sliver: SliverGrid.builder(
|
||||||
itemCount: albums.isEmpty ? 6 : albums.length + 1,
|
itemCount: albums.isEmpty ? 6 : albums.length + 1,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate:
|
||||||
maxCrossAxisExtent: 200,
|
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
maxCrossAxisExtent: 150,
|
||||||
|
mainAxisExtent: 225,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
),
|
),
|
||||||
@ -110,8 +111,8 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -15,7 +15,6 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
|||||||
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/modules/playlist/playlist_card.dart';
|
import 'package:spotube/modules/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/components/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
||||||
import 'package:spotube/provider/authentication/authentication.dart';
|
import 'package:spotube/provider/authentication/authentication.dart';
|
||||||
@ -126,12 +125,13 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(10),
|
const SliverGap(10),
|
||||||
SliverLayoutBuilder(builder: (context, constrains) {
|
SliverPadding(
|
||||||
return SliverGrid.builder(
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
sliver: SliverGrid.builder(
|
||||||
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 200,
|
maxCrossAxisExtent: 150,
|
||||||
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
mainAxisExtent: 225,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
),
|
),
|
||||||
@ -157,8 +157,8 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
FakeData.playlistSimple,
|
FakeData.playlistSimple,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
})
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -6,12 +6,10 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/provider/authentication/authentication.dart';
|
import 'package:spotube/provider/authentication/authentication.dart';
|
||||||
import 'package:spotube/provider/database/database.dart';
|
import 'package:spotube/provider/database/database.dart';
|
||||||
import 'package:spotube/provider/spotify/utils/json_cast.dart';
|
|
||||||
import 'package:spotube/services/logger/logger.dart';
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:lrc/lrc.dart';
|
import 'package:lrc/lrc.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@ -70,7 +68,6 @@ part 'views/view.dart';
|
|||||||
part 'utils/mixin.dart';
|
part 'utils/mixin.dart';
|
||||||
part 'utils/state.dart';
|
part 'utils/state.dart';
|
||||||
part 'utils/provider.dart';
|
part 'utils/provider.dart';
|
||||||
part 'utils/persistence.dart';
|
|
||||||
part 'utils/async.dart';
|
part 'utils/async.dart';
|
||||||
|
|
||||||
part 'utils/provider/paginated.dart';
|
part 'utils/provider/paginated.dart';
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
part of '../spotify.dart';
|
|
||||||
|
|
||||||
// ignore: invalid_use_of_internal_member
|
|
||||||
mixin Persistence<T> on BuildlessAsyncNotifier<T> {
|
|
||||||
LazyBox get store => Hive.lazyBox("spotube_cache");
|
|
||||||
|
|
||||||
FutureOr<T> fromJson(Map<String, dynamic> json);
|
|
||||||
Map<String, dynamic> toJson(T data);
|
|
||||||
|
|
||||||
FutureOr<void> onInit() {}
|
|
||||||
|
|
||||||
Future<void> load() async {
|
|
||||||
final json = await store.get(runtimeType.toString());
|
|
||||||
if (json != null ||
|
|
||||||
(json is Map && json.entries.isNotEmpty) ||
|
|
||||||
(json is List && json.isNotEmpty)) {
|
|
||||||
state = AsyncData(
|
|
||||||
await fromJson(
|
|
||||||
castNestedJson(json),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await onInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> save() async {
|
|
||||||
await store.put(
|
|
||||||
runtimeType.toString(),
|
|
||||||
state.value == null ? null : toJson(state.value as T),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
set state(AsyncValue<T> value) {
|
|
||||||
if (state == value) return;
|
|
||||||
super.state = value;
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,320 +0,0 @@
|
|||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
|
|
||||||
import 'package:spotube/services/sourced_track/enums.dart';
|
|
||||||
|
|
||||||
part 'adapters.g.dart';
|
|
||||||
part 'adapters.freezed.dart';
|
|
||||||
|
|
||||||
@HiveType(typeId: 2)
|
|
||||||
class SkipSegment {
|
|
||||||
@HiveField(0)
|
|
||||||
final int start;
|
|
||||||
@HiveField(1)
|
|
||||||
final int end;
|
|
||||||
SkipSegment(this.start, this.end);
|
|
||||||
|
|
||||||
static String version = 'v1';
|
|
||||||
static final boxName = "oss.krtirtho.spotube.skip_segments.$version";
|
|
||||||
static LazyBox get box => Hive.lazyBox(boxName);
|
|
||||||
|
|
||||||
SkipSegment.fromJson(Map<String, dynamic> json)
|
|
||||||
: start = json['start'],
|
|
||||||
end = json['end'];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'start': start,
|
|
||||||
'end': end,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
@HiveType(typeId: 5)
|
|
||||||
enum SourceType {
|
|
||||||
@HiveField(0)
|
|
||||||
youtube._("YouTube"),
|
|
||||||
|
|
||||||
@HiveField(1)
|
|
||||||
youtubeMusic._("YouTube Music"),
|
|
||||||
|
|
||||||
@HiveField(2)
|
|
||||||
jiosaavn._("JioSaavn");
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const SourceType._(this.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
@HiveType(typeId: 6)
|
|
||||||
class SourceMatch {
|
|
||||||
@HiveField(0)
|
|
||||||
String id;
|
|
||||||
|
|
||||||
@HiveField(1)
|
|
||||||
String sourceId;
|
|
||||||
|
|
||||||
@HiveField(2)
|
|
||||||
SourceType sourceType;
|
|
||||||
|
|
||||||
@HiveField(3)
|
|
||||||
DateTime createdAt;
|
|
||||||
|
|
||||||
SourceMatch({
|
|
||||||
required this.id,
|
|
||||||
required this.sourceId,
|
|
||||||
required this.sourceType,
|
|
||||||
required this.createdAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory SourceMatch.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SourceMatchFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$SourceMatchToJson(this);
|
|
||||||
|
|
||||||
static String version = 'v1';
|
|
||||||
static final boxName = "oss.krtirtho.spotube.source_matches.$version";
|
|
||||||
|
|
||||||
static LazyBox<SourceMatch> get box => Hive.lazyBox<SourceMatch>(boxName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class AuthenticationCredentials {
|
|
||||||
String cookie;
|
|
||||||
String accessToken;
|
|
||||||
DateTime expiration;
|
|
||||||
|
|
||||||
AuthenticationCredentials({
|
|
||||||
required this.cookie,
|
|
||||||
required this.accessToken,
|
|
||||||
required this.expiration,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory AuthenticationCredentials.fromJson(Map<String, dynamic> json) {
|
|
||||||
return AuthenticationCredentials(
|
|
||||||
cookie: json['cookie'] as String,
|
|
||||||
accessToken: json['accessToken'] as String,
|
|
||||||
expiration: DateTime.parse(json['expiration'] as String),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'cookie': cookie,
|
|
||||||
'accessToken': accessToken,
|
|
||||||
'expiration': expiration.toIso8601String(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
enum LayoutMode {
|
|
||||||
compact,
|
|
||||||
extended,
|
|
||||||
adaptive,
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
enum CloseBehavior {
|
|
||||||
minimizeToTray,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
enum AudioSource {
|
|
||||||
youtube,
|
|
||||||
piped,
|
|
||||||
jiosaavn;
|
|
||||||
|
|
||||||
String get label => name[0].toUpperCase() + name.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
enum MusicCodec {
|
|
||||||
m4a._("M4a (Best for downloaded music)"),
|
|
||||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
const MusicCodec._(this.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonEnum()
|
|
||||||
enum SearchMode {
|
|
||||||
youtube._("YouTube"),
|
|
||||||
youtubeMusic._("YouTube Music");
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const SearchMode._(this.label);
|
|
||||||
|
|
||||||
factory SearchMode.fromString(String key) {
|
|
||||||
return SearchMode.values.firstWhere((e) => e.name == key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class UserPreferences with _$UserPreferences {
|
|
||||||
const factory UserPreferences({
|
|
||||||
@Default(SourceQualities.high) SourceQualities audioQuality,
|
|
||||||
@Default(true) bool albumColorSync,
|
|
||||||
@Default(false) bool amoledDarkTheme,
|
|
||||||
@Default(true) bool checkUpdate,
|
|
||||||
@Default(false) bool normalizeAudio,
|
|
||||||
@Default(false) bool showSystemTrayIcon,
|
|
||||||
@Default(false) bool skipNonMusic,
|
|
||||||
@Default(false) bool systemTitleBar,
|
|
||||||
@Default(CloseBehavior.close) CloseBehavior closeBehavior,
|
|
||||||
@Default(SpotubeColor(0xFF2196F3, name: "Blue"))
|
|
||||||
@JsonKey(
|
|
||||||
fromJson: UserPreferences._accentColorSchemeFromJson,
|
|
||||||
toJson: UserPreferences._accentColorSchemeToJson,
|
|
||||||
readValue: UserPreferences._accentColorSchemeReadValue,
|
|
||||||
)
|
|
||||||
SpotubeColor accentColorScheme,
|
|
||||||
@Default(LayoutMode.adaptive) LayoutMode layoutMode,
|
|
||||||
@Default(Locale("system", "system"))
|
|
||||||
@JsonKey(
|
|
||||||
fromJson: UserPreferences._localeFromJson,
|
|
||||||
toJson: UserPreferences._localeToJson,
|
|
||||||
readValue: UserPreferences._localeReadValue,
|
|
||||||
)
|
|
||||||
Locale locale,
|
|
||||||
@Default(Market.US) Market recommendationMarket,
|
|
||||||
@Default(SearchMode.youtube) SearchMode searchMode,
|
|
||||||
@Default("") String downloadLocation,
|
|
||||||
@Default([]) List<String> localLibraryLocation,
|
|
||||||
@Default("https://pipedapi.kavin.rocks") String pipedInstance,
|
|
||||||
@Default(ThemeMode.system) ThemeMode themeMode,
|
|
||||||
@Default(AudioSource.youtube) AudioSource audioSource,
|
|
||||||
@Default(SourceCodecs.weba) SourceCodecs streamMusicCodec,
|
|
||||||
@Default(SourceCodecs.m4a) SourceCodecs downloadMusicCodec,
|
|
||||||
@Default(true) bool discordPresence,
|
|
||||||
@Default(true) bool endlessPlayback,
|
|
||||||
@Default(false) bool enableConnect,
|
|
||||||
}) = _UserPreferences;
|
|
||||||
factory UserPreferences.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$UserPreferencesFromJson(json);
|
|
||||||
|
|
||||||
factory UserPreferences.withDefaults() => UserPreferences.fromJson({});
|
|
||||||
|
|
||||||
static SpotubeColor _accentColorSchemeFromJson(Map<String, dynamic> json) {
|
|
||||||
return SpotubeColor.fromString(json["color"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic>? _accentColorSchemeReadValue(
|
|
||||||
Map<dynamic, dynamic> json, String key) {
|
|
||||||
if (json[key] is String) {
|
|
||||||
return {"color": json[key]};
|
|
||||||
}
|
|
||||||
|
|
||||||
return json[key] as Map<String, dynamic>?;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic> _accentColorSchemeToJson(SpotubeColor color) {
|
|
||||||
return {"color": color.toString()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static Locale _localeFromJson(Map<String, dynamic> json) {
|
|
||||||
return Locale(json["languageCode"], json["countryCode"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic> _localeToJson(Locale locale) {
|
|
||||||
return {
|
|
||||||
"languageCode": locale.languageCode,
|
|
||||||
"countryCode": locale.countryCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic>? _localeReadValue(
|
|
||||||
Map<dynamic, dynamic> json, String key) {
|
|
||||||
if (json[key] is String) {
|
|
||||||
final map = jsonDecode(json[key]);
|
|
||||||
return {
|
|
||||||
"languageCode": map["lc"],
|
|
||||||
"countryCode": map["cc"],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return json[key] as Map<String, dynamic>?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BlacklistedType {
|
|
||||||
artist,
|
|
||||||
track;
|
|
||||||
|
|
||||||
static BlacklistedType fromName(String name) =>
|
|
||||||
BlacklistedType.values.firstWhere((e) => e.name == name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class BlacklistedElement {
|
|
||||||
final String id;
|
|
||||||
final String name;
|
|
||||||
final BlacklistedType type;
|
|
||||||
|
|
||||||
BlacklistedElement.fromJson(Map<String, dynamic> json)
|
|
||||||
: id = json['id'],
|
|
||||||
name = json['name'],
|
|
||||||
type = BlacklistedType.fromName(json['type']);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {'id': id, 'type': type.name, 'name': name};
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class PlaybackHistoryItem with _$PlaybackHistoryItem {
|
|
||||||
factory PlaybackHistoryItem.playlist({
|
|
||||||
required DateTime date,
|
|
||||||
required PlaylistSimple playlist,
|
|
||||||
}) = PlaybackHistoryPlaylist;
|
|
||||||
|
|
||||||
factory PlaybackHistoryItem.album({
|
|
||||||
required DateTime date,
|
|
||||||
required AlbumSimple album,
|
|
||||||
}) = PlaybackHistoryAlbum;
|
|
||||||
|
|
||||||
factory PlaybackHistoryItem.track({
|
|
||||||
required DateTime date,
|
|
||||||
required Track track,
|
|
||||||
}) = PlaybackHistoryTrack;
|
|
||||||
|
|
||||||
factory PlaybackHistoryItem.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$PlaybackHistoryItemFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlaybackHistoryState {
|
|
||||||
final List<PlaybackHistoryItem> items;
|
|
||||||
const PlaybackHistoryState({this.items = const []});
|
|
||||||
|
|
||||||
factory PlaybackHistoryState.fromJson(Map<String, dynamic> json) {
|
|
||||||
return PlaybackHistoryState(
|
|
||||||
items: json["items"]
|
|
||||||
?.map(
|
|
||||||
(json) => PlaybackHistoryItem.fromJson(json),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.cast<PlaybackHistoryItem>() ??
|
|
||||||
<PlaybackHistoryItem>[],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScrobblerState {
|
|
||||||
final String username;
|
|
||||||
final String passwordHash;
|
|
||||||
|
|
||||||
ScrobblerState({
|
|
||||||
required this.username,
|
|
||||||
required this.passwordHash,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ScrobblerState.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ScrobblerState(
|
|
||||||
username: json["username"],
|
|
||||||
passwordHash: json["passwordHash"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,600 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'adapters.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// TypeAdapterGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
class SkipSegmentAdapter extends TypeAdapter<SkipSegment> {
|
|
||||||
@override
|
|
||||||
final int typeId = 2;
|
|
||||||
|
|
||||||
@override
|
|
||||||
SkipSegment read(BinaryReader reader) {
|
|
||||||
final numOfFields = reader.readByte();
|
|
||||||
final fields = <int, dynamic>{
|
|
||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
|
||||||
};
|
|
||||||
return SkipSegment(
|
|
||||||
fields[0] as int,
|
|
||||||
fields[1] as int,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(BinaryWriter writer, SkipSegment obj) {
|
|
||||||
writer
|
|
||||||
..writeByte(2)
|
|
||||||
..writeByte(0)
|
|
||||||
..write(obj.start)
|
|
||||||
..writeByte(1)
|
|
||||||
..write(obj.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => typeId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is SkipSegmentAdapter &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
typeId == other.typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceMatchAdapter extends TypeAdapter<SourceMatch> {
|
|
||||||
@override
|
|
||||||
final int typeId = 6;
|
|
||||||
|
|
||||||
@override
|
|
||||||
SourceMatch read(BinaryReader reader) {
|
|
||||||
final numOfFields = reader.readByte();
|
|
||||||
final fields = <int, dynamic>{
|
|
||||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
|
||||||
};
|
|
||||||
return SourceMatch(
|
|
||||||
id: fields[0] as String,
|
|
||||||
sourceId: fields[1] as String,
|
|
||||||
sourceType: fields[2] as SourceType,
|
|
||||||
createdAt: fields[3] as DateTime,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(BinaryWriter writer, SourceMatch obj) {
|
|
||||||
writer
|
|
||||||
..writeByte(4)
|
|
||||||
..writeByte(0)
|
|
||||||
..write(obj.id)
|
|
||||||
..writeByte(1)
|
|
||||||
..write(obj.sourceId)
|
|
||||||
..writeByte(2)
|
|
||||||
..write(obj.sourceType)
|
|
||||||
..writeByte(3)
|
|
||||||
..write(obj.createdAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => typeId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is SourceMatchAdapter &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
typeId == other.typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceTypeAdapter extends TypeAdapter<SourceType> {
|
|
||||||
@override
|
|
||||||
final int typeId = 5;
|
|
||||||
|
|
||||||
@override
|
|
||||||
SourceType read(BinaryReader reader) {
|
|
||||||
switch (reader.readByte()) {
|
|
||||||
case 0:
|
|
||||||
return SourceType.youtube;
|
|
||||||
case 1:
|
|
||||||
return SourceType.youtubeMusic;
|
|
||||||
case 2:
|
|
||||||
return SourceType.jiosaavn;
|
|
||||||
default:
|
|
||||||
return SourceType.youtube;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void write(BinaryWriter writer, SourceType obj) {
|
|
||||||
switch (obj) {
|
|
||||||
case SourceType.youtube:
|
|
||||||
writer.writeByte(0);
|
|
||||||
break;
|
|
||||||
case SourceType.youtubeMusic:
|
|
||||||
writer.writeByte(1);
|
|
||||||
break;
|
|
||||||
case SourceType.jiosaavn:
|
|
||||||
writer.writeByte(2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => typeId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is SourceTypeAdapter &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
typeId == other.typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
SourceMatch _$SourceMatchFromJson(Map json) => SourceMatch(
|
|
||||||
id: json['id'] as String,
|
|
||||||
sourceId: json['sourceId'] as String,
|
|
||||||
sourceType: $enumDecode(_$SourceTypeEnumMap, json['sourceType']),
|
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SourceMatchToJson(SourceMatch instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'sourceId': instance.sourceId,
|
|
||||||
'sourceType': _$SourceTypeEnumMap[instance.sourceType]!,
|
|
||||||
'createdAt': instance.createdAt.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$SourceTypeEnumMap = {
|
|
||||||
SourceType.youtube: 'youtube',
|
|
||||||
SourceType.youtubeMusic: 'youtubeMusic',
|
|
||||||
SourceType.jiosaavn: 'jiosaavn',
|
|
||||||
};
|
|
||||||
|
|
||||||
AuthenticationCredentials _$AuthenticationCredentialsFromJson(Map json) =>
|
|
||||||
AuthenticationCredentials(
|
|
||||||
cookie: json['cookie'] as String,
|
|
||||||
accessToken: json['accessToken'] as String,
|
|
||||||
expiration: DateTime.parse(json['expiration'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$AuthenticationCredentialsToJson(
|
|
||||||
AuthenticationCredentials instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'cookie': instance.cookie,
|
|
||||||
'accessToken': instance.accessToken,
|
|
||||||
'expiration': instance.expiration.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_$UserPreferencesImpl _$$UserPreferencesImplFromJson(Map json) =>
|
|
||||||
_$UserPreferencesImpl(
|
|
||||||
audioQuality:
|
|
||||||
$enumDecodeNullable(_$SourceQualitiesEnumMap, json['audioQuality']) ??
|
|
||||||
SourceQualities.high,
|
|
||||||
albumColorSync: json['albumColorSync'] as bool? ?? true,
|
|
||||||
amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false,
|
|
||||||
checkUpdate: json['checkUpdate'] as bool? ?? true,
|
|
||||||
normalizeAudio: json['normalizeAudio'] as bool? ?? false,
|
|
||||||
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? false,
|
|
||||||
skipNonMusic: json['skipNonMusic'] as bool? ?? false,
|
|
||||||
systemTitleBar: json['systemTitleBar'] as bool? ?? false,
|
|
||||||
closeBehavior:
|
|
||||||
$enumDecodeNullable(_$CloseBehaviorEnumMap, json['closeBehavior']) ??
|
|
||||||
CloseBehavior.close,
|
|
||||||
accentColorScheme: UserPreferences._accentColorSchemeReadValue(
|
|
||||||
json, 'accentColorScheme') ==
|
|
||||||
null
|
|
||||||
? const SpotubeColor(0xFF2196F3, name: "Blue")
|
|
||||||
: UserPreferences._accentColorSchemeFromJson(
|
|
||||||
UserPreferences._accentColorSchemeReadValue(
|
|
||||||
json, 'accentColorScheme') as Map<String, dynamic>),
|
|
||||||
layoutMode:
|
|
||||||
$enumDecodeNullable(_$LayoutModeEnumMap, json['layoutMode']) ??
|
|
||||||
LayoutMode.adaptive,
|
|
||||||
locale: UserPreferences._localeReadValue(json, 'locale') == null
|
|
||||||
? const Locale("system", "system")
|
|
||||||
: UserPreferences._localeFromJson(
|
|
||||||
UserPreferences._localeReadValue(json, 'locale')
|
|
||||||
as Map<String, dynamic>),
|
|
||||||
recommendationMarket:
|
|
||||||
$enumDecodeNullable(_$MarketEnumMap, json['recommendationMarket']) ??
|
|
||||||
Market.US,
|
|
||||||
searchMode:
|
|
||||||
$enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ??
|
|
||||||
SearchMode.youtube,
|
|
||||||
downloadLocation: json['downloadLocation'] as String? ?? "",
|
|
||||||
localLibraryLocation: (json['localLibraryLocation'] as List<dynamic>?)
|
|
||||||
?.map((e) => e as String)
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
pipedInstance:
|
|
||||||
json['pipedInstance'] as String? ?? "https://pipedapi.kavin.rocks",
|
|
||||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
|
||||||
ThemeMode.system,
|
|
||||||
audioSource:
|
|
||||||
$enumDecodeNullable(_$AudioSourceEnumMap, json['audioSource']) ??
|
|
||||||
AudioSource.youtube,
|
|
||||||
streamMusicCodec: $enumDecodeNullable(
|
|
||||||
_$SourceCodecsEnumMap, json['streamMusicCodec']) ??
|
|
||||||
SourceCodecs.weba,
|
|
||||||
downloadMusicCodec: $enumDecodeNullable(
|
|
||||||
_$SourceCodecsEnumMap, json['downloadMusicCodec']) ??
|
|
||||||
SourceCodecs.m4a,
|
|
||||||
discordPresence: json['discordPresence'] as bool? ?? true,
|
|
||||||
endlessPlayback: json['endlessPlayback'] as bool? ?? true,
|
|
||||||
enableConnect: json['enableConnect'] as bool? ?? false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$UserPreferencesImplToJson(
|
|
||||||
_$UserPreferencesImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'audioQuality': _$SourceQualitiesEnumMap[instance.audioQuality]!,
|
|
||||||
'albumColorSync': instance.albumColorSync,
|
|
||||||
'amoledDarkTheme': instance.amoledDarkTheme,
|
|
||||||
'checkUpdate': instance.checkUpdate,
|
|
||||||
'normalizeAudio': instance.normalizeAudio,
|
|
||||||
'showSystemTrayIcon': instance.showSystemTrayIcon,
|
|
||||||
'skipNonMusic': instance.skipNonMusic,
|
|
||||||
'systemTitleBar': instance.systemTitleBar,
|
|
||||||
'closeBehavior': _$CloseBehaviorEnumMap[instance.closeBehavior]!,
|
|
||||||
'accentColorScheme':
|
|
||||||
UserPreferences._accentColorSchemeToJson(instance.accentColorScheme),
|
|
||||||
'layoutMode': _$LayoutModeEnumMap[instance.layoutMode]!,
|
|
||||||
'locale': UserPreferences._localeToJson(instance.locale),
|
|
||||||
'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!,
|
|
||||||
'searchMode': _$SearchModeEnumMap[instance.searchMode]!,
|
|
||||||
'downloadLocation': instance.downloadLocation,
|
|
||||||
'localLibraryLocation': instance.localLibraryLocation,
|
|
||||||
'pipedInstance': instance.pipedInstance,
|
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
|
||||||
'audioSource': _$AudioSourceEnumMap[instance.audioSource]!,
|
|
||||||
'streamMusicCodec': _$SourceCodecsEnumMap[instance.streamMusicCodec]!,
|
|
||||||
'downloadMusicCodec': _$SourceCodecsEnumMap[instance.downloadMusicCodec]!,
|
|
||||||
'discordPresence': instance.discordPresence,
|
|
||||||
'endlessPlayback': instance.endlessPlayback,
|
|
||||||
'enableConnect': instance.enableConnect,
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$SourceQualitiesEnumMap = {
|
|
||||||
SourceQualities.high: 'high',
|
|
||||||
SourceQualities.medium: 'medium',
|
|
||||||
SourceQualities.low: 'low',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$CloseBehaviorEnumMap = {
|
|
||||||
CloseBehavior.minimizeToTray: 'minimizeToTray',
|
|
||||||
CloseBehavior.close: 'close',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$LayoutModeEnumMap = {
|
|
||||||
LayoutMode.compact: 'compact',
|
|
||||||
LayoutMode.extended: 'extended',
|
|
||||||
LayoutMode.adaptive: 'adaptive',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$MarketEnumMap = {
|
|
||||||
Market.AD: 'AD',
|
|
||||||
Market.AE: 'AE',
|
|
||||||
Market.AF: 'AF',
|
|
||||||
Market.AG: 'AG',
|
|
||||||
Market.AI: 'AI',
|
|
||||||
Market.AL: 'AL',
|
|
||||||
Market.AM: 'AM',
|
|
||||||
Market.AO: 'AO',
|
|
||||||
Market.AQ: 'AQ',
|
|
||||||
Market.AR: 'AR',
|
|
||||||
Market.AS: 'AS',
|
|
||||||
Market.AT: 'AT',
|
|
||||||
Market.AU: 'AU',
|
|
||||||
Market.AW: 'AW',
|
|
||||||
Market.AX: 'AX',
|
|
||||||
Market.AZ: 'AZ',
|
|
||||||
Market.BA: 'BA',
|
|
||||||
Market.BB: 'BB',
|
|
||||||
Market.BD: 'BD',
|
|
||||||
Market.BE: 'BE',
|
|
||||||
Market.BF: 'BF',
|
|
||||||
Market.BG: 'BG',
|
|
||||||
Market.BH: 'BH',
|
|
||||||
Market.BI: 'BI',
|
|
||||||
Market.BJ: 'BJ',
|
|
||||||
Market.BL: 'BL',
|
|
||||||
Market.BM: 'BM',
|
|
||||||
Market.BN: 'BN',
|
|
||||||
Market.BO: 'BO',
|
|
||||||
Market.BQ: 'BQ',
|
|
||||||
Market.BR: 'BR',
|
|
||||||
Market.BS: 'BS',
|
|
||||||
Market.BT: 'BT',
|
|
||||||
Market.BV: 'BV',
|
|
||||||
Market.BW: 'BW',
|
|
||||||
Market.BY: 'BY',
|
|
||||||
Market.BZ: 'BZ',
|
|
||||||
Market.CA: 'CA',
|
|
||||||
Market.CC: 'CC',
|
|
||||||
Market.CD: 'CD',
|
|
||||||
Market.CF: 'CF',
|
|
||||||
Market.CG: 'CG',
|
|
||||||
Market.CH: 'CH',
|
|
||||||
Market.CI: 'CI',
|
|
||||||
Market.CK: 'CK',
|
|
||||||
Market.CL: 'CL',
|
|
||||||
Market.CM: 'CM',
|
|
||||||
Market.CN: 'CN',
|
|
||||||
Market.CO: 'CO',
|
|
||||||
Market.CR: 'CR',
|
|
||||||
Market.CU: 'CU',
|
|
||||||
Market.CV: 'CV',
|
|
||||||
Market.CW: 'CW',
|
|
||||||
Market.CX: 'CX',
|
|
||||||
Market.CY: 'CY',
|
|
||||||
Market.CZ: 'CZ',
|
|
||||||
Market.DE: 'DE',
|
|
||||||
Market.DJ: 'DJ',
|
|
||||||
Market.DK: 'DK',
|
|
||||||
Market.DM: 'DM',
|
|
||||||
Market.DO: 'DO',
|
|
||||||
Market.DZ: 'DZ',
|
|
||||||
Market.EC: 'EC',
|
|
||||||
Market.EE: 'EE',
|
|
||||||
Market.EG: 'EG',
|
|
||||||
Market.EH: 'EH',
|
|
||||||
Market.ER: 'ER',
|
|
||||||
Market.ES: 'ES',
|
|
||||||
Market.ET: 'ET',
|
|
||||||
Market.FI: 'FI',
|
|
||||||
Market.FJ: 'FJ',
|
|
||||||
Market.FK: 'FK',
|
|
||||||
Market.FM: 'FM',
|
|
||||||
Market.FO: 'FO',
|
|
||||||
Market.FR: 'FR',
|
|
||||||
Market.GA: 'GA',
|
|
||||||
Market.GB: 'GB',
|
|
||||||
Market.GD: 'GD',
|
|
||||||
Market.GE: 'GE',
|
|
||||||
Market.GF: 'GF',
|
|
||||||
Market.GG: 'GG',
|
|
||||||
Market.GH: 'GH',
|
|
||||||
Market.GI: 'GI',
|
|
||||||
Market.GL: 'GL',
|
|
||||||
Market.GM: 'GM',
|
|
||||||
Market.GN: 'GN',
|
|
||||||
Market.GP: 'GP',
|
|
||||||
Market.GQ: 'GQ',
|
|
||||||
Market.GR: 'GR',
|
|
||||||
Market.GS: 'GS',
|
|
||||||
Market.GT: 'GT',
|
|
||||||
Market.GU: 'GU',
|
|
||||||
Market.GW: 'GW',
|
|
||||||
Market.GY: 'GY',
|
|
||||||
Market.HK: 'HK',
|
|
||||||
Market.HM: 'HM',
|
|
||||||
Market.HN: 'HN',
|
|
||||||
Market.HR: 'HR',
|
|
||||||
Market.HT: 'HT',
|
|
||||||
Market.HU: 'HU',
|
|
||||||
Market.ID: 'ID',
|
|
||||||
Market.IE: 'IE',
|
|
||||||
Market.IL: 'IL',
|
|
||||||
Market.IM: 'IM',
|
|
||||||
Market.IN: 'IN',
|
|
||||||
Market.IO: 'IO',
|
|
||||||
Market.IQ: 'IQ',
|
|
||||||
Market.IR: 'IR',
|
|
||||||
Market.IS: 'IS',
|
|
||||||
Market.IT: 'IT',
|
|
||||||
Market.JE: 'JE',
|
|
||||||
Market.JM: 'JM',
|
|
||||||
Market.JO: 'JO',
|
|
||||||
Market.JP: 'JP',
|
|
||||||
Market.KE: 'KE',
|
|
||||||
Market.KG: 'KG',
|
|
||||||
Market.KH: 'KH',
|
|
||||||
Market.KI: 'KI',
|
|
||||||
Market.KM: 'KM',
|
|
||||||
Market.KN: 'KN',
|
|
||||||
Market.KP: 'KP',
|
|
||||||
Market.KR: 'KR',
|
|
||||||
Market.KW: 'KW',
|
|
||||||
Market.KY: 'KY',
|
|
||||||
Market.KZ: 'KZ',
|
|
||||||
Market.LA: 'LA',
|
|
||||||
Market.LB: 'LB',
|
|
||||||
Market.LC: 'LC',
|
|
||||||
Market.LI: 'LI',
|
|
||||||
Market.LK: 'LK',
|
|
||||||
Market.LR: 'LR',
|
|
||||||
Market.LS: 'LS',
|
|
||||||
Market.LT: 'LT',
|
|
||||||
Market.LU: 'LU',
|
|
||||||
Market.LV: 'LV',
|
|
||||||
Market.LY: 'LY',
|
|
||||||
Market.MA: 'MA',
|
|
||||||
Market.MC: 'MC',
|
|
||||||
Market.MD: 'MD',
|
|
||||||
Market.ME: 'ME',
|
|
||||||
Market.MF: 'MF',
|
|
||||||
Market.MG: 'MG',
|
|
||||||
Market.MH: 'MH',
|
|
||||||
Market.MK: 'MK',
|
|
||||||
Market.ML: 'ML',
|
|
||||||
Market.MM: 'MM',
|
|
||||||
Market.MN: 'MN',
|
|
||||||
Market.MO: 'MO',
|
|
||||||
Market.MP: 'MP',
|
|
||||||
Market.MQ: 'MQ',
|
|
||||||
Market.MR: 'MR',
|
|
||||||
Market.MS: 'MS',
|
|
||||||
Market.MT: 'MT',
|
|
||||||
Market.MU: 'MU',
|
|
||||||
Market.MV: 'MV',
|
|
||||||
Market.MW: 'MW',
|
|
||||||
Market.MX: 'MX',
|
|
||||||
Market.MY: 'MY',
|
|
||||||
Market.MZ: 'MZ',
|
|
||||||
Market.NA: 'NA',
|
|
||||||
Market.NC: 'NC',
|
|
||||||
Market.NE: 'NE',
|
|
||||||
Market.NF: 'NF',
|
|
||||||
Market.NG: 'NG',
|
|
||||||
Market.NI: 'NI',
|
|
||||||
Market.NL: 'NL',
|
|
||||||
Market.NO: 'NO',
|
|
||||||
Market.NP: 'NP',
|
|
||||||
Market.NR: 'NR',
|
|
||||||
Market.NU: 'NU',
|
|
||||||
Market.NZ: 'NZ',
|
|
||||||
Market.OM: 'OM',
|
|
||||||
Market.PA: 'PA',
|
|
||||||
Market.PE: 'PE',
|
|
||||||
Market.PF: 'PF',
|
|
||||||
Market.PG: 'PG',
|
|
||||||
Market.PH: 'PH',
|
|
||||||
Market.PK: 'PK',
|
|
||||||
Market.PL: 'PL',
|
|
||||||
Market.PM: 'PM',
|
|
||||||
Market.PN: 'PN',
|
|
||||||
Market.PR: 'PR',
|
|
||||||
Market.PS: 'PS',
|
|
||||||
Market.PT: 'PT',
|
|
||||||
Market.PW: 'PW',
|
|
||||||
Market.PY: 'PY',
|
|
||||||
Market.QA: 'QA',
|
|
||||||
Market.RE: 'RE',
|
|
||||||
Market.RO: 'RO',
|
|
||||||
Market.RS: 'RS',
|
|
||||||
Market.RU: 'RU',
|
|
||||||
Market.RW: 'RW',
|
|
||||||
Market.SA: 'SA',
|
|
||||||
Market.SB: 'SB',
|
|
||||||
Market.SC: 'SC',
|
|
||||||
Market.SD: 'SD',
|
|
||||||
Market.SE: 'SE',
|
|
||||||
Market.SG: 'SG',
|
|
||||||
Market.SH: 'SH',
|
|
||||||
Market.SI: 'SI',
|
|
||||||
Market.SJ: 'SJ',
|
|
||||||
Market.SK: 'SK',
|
|
||||||
Market.SL: 'SL',
|
|
||||||
Market.SM: 'SM',
|
|
||||||
Market.SN: 'SN',
|
|
||||||
Market.SO: 'SO',
|
|
||||||
Market.SR: 'SR',
|
|
||||||
Market.SS: 'SS',
|
|
||||||
Market.ST: 'ST',
|
|
||||||
Market.SV: 'SV',
|
|
||||||
Market.SX: 'SX',
|
|
||||||
Market.SY: 'SY',
|
|
||||||
Market.SZ: 'SZ',
|
|
||||||
Market.TC: 'TC',
|
|
||||||
Market.TD: 'TD',
|
|
||||||
Market.TF: 'TF',
|
|
||||||
Market.TG: 'TG',
|
|
||||||
Market.TH: 'TH',
|
|
||||||
Market.TJ: 'TJ',
|
|
||||||
Market.TK: 'TK',
|
|
||||||
Market.TL: 'TL',
|
|
||||||
Market.TM: 'TM',
|
|
||||||
Market.TN: 'TN',
|
|
||||||
Market.TO: 'TO',
|
|
||||||
Market.TR: 'TR',
|
|
||||||
Market.TT: 'TT',
|
|
||||||
Market.TV: 'TV',
|
|
||||||
Market.TW: 'TW',
|
|
||||||
Market.TZ: 'TZ',
|
|
||||||
Market.UA: 'UA',
|
|
||||||
Market.UG: 'UG',
|
|
||||||
Market.UM: 'UM',
|
|
||||||
Market.US: 'US',
|
|
||||||
Market.UY: 'UY',
|
|
||||||
Market.UZ: 'UZ',
|
|
||||||
Market.VA: 'VA',
|
|
||||||
Market.VC: 'VC',
|
|
||||||
Market.VE: 'VE',
|
|
||||||
Market.VG: 'VG',
|
|
||||||
Market.VI: 'VI',
|
|
||||||
Market.VN: 'VN',
|
|
||||||
Market.VU: 'VU',
|
|
||||||
Market.WF: 'WF',
|
|
||||||
Market.WS: 'WS',
|
|
||||||
Market.XK: 'XK',
|
|
||||||
Market.YE: 'YE',
|
|
||||||
Market.YT: 'YT',
|
|
||||||
Market.ZA: 'ZA',
|
|
||||||
Market.ZM: 'ZM',
|
|
||||||
Market.ZW: 'ZW',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$SearchModeEnumMap = {
|
|
||||||
SearchMode.youtube: 'youtube',
|
|
||||||
SearchMode.youtubeMusic: 'youtubeMusic',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$ThemeModeEnumMap = {
|
|
||||||
ThemeMode.system: 'system',
|
|
||||||
ThemeMode.light: 'light',
|
|
||||||
ThemeMode.dark: 'dark',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$AudioSourceEnumMap = {
|
|
||||||
AudioSource.youtube: 'youtube',
|
|
||||||
AudioSource.piped: 'piped',
|
|
||||||
AudioSource.jiosaavn: 'jiosaavn',
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$SourceCodecsEnumMap = {
|
|
||||||
SourceCodecs.m4a: 'm4a',
|
|
||||||
SourceCodecs.weba: 'weba',
|
|
||||||
};
|
|
||||||
|
|
||||||
_$PlaybackHistoryPlaylistImpl _$$PlaybackHistoryPlaylistImplFromJson(
|
|
||||||
Map json) =>
|
|
||||||
_$PlaybackHistoryPlaylistImpl(
|
|
||||||
date: DateTime.parse(json['date'] as String),
|
|
||||||
playlist: PlaylistSimple.fromJson(
|
|
||||||
Map<String, dynamic>.from(json['playlist'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryPlaylistImplToJson(
|
|
||||||
_$PlaybackHistoryPlaylistImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'date': instance.date.toIso8601String(),
|
|
||||||
'playlist': instance.playlist.toJson(),
|
|
||||||
'runtimeType': instance.$type,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$PlaybackHistoryAlbumImpl _$$PlaybackHistoryAlbumImplFromJson(Map json) =>
|
|
||||||
_$PlaybackHistoryAlbumImpl(
|
|
||||||
date: DateTime.parse(json['date'] as String),
|
|
||||||
album:
|
|
||||||
AlbumSimple.fromJson(Map<String, dynamic>.from(json['album'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryAlbumImplToJson(
|
|
||||||
_$PlaybackHistoryAlbumImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'date': instance.date.toIso8601String(),
|
|
||||||
'album': instance.album.toJson(),
|
|
||||||
'runtimeType': instance.$type,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$PlaybackHistoryTrackImpl _$$PlaybackHistoryTrackImplFromJson(Map json) =>
|
|
||||||
_$PlaybackHistoryTrackImpl(
|
|
||||||
date: DateTime.parse(json['date'] as String),
|
|
||||||
track: Track.fromJson(Map<String, dynamic>.from(json['track'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryTrackImplToJson(
|
|
||||||
_$PlaybackHistoryTrackImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'date': instance.date.toIso8601String(),
|
|
||||||
'track': instance.track.toJson(),
|
|
||||||
'runtimeType': instance.$type,
|
|
||||||
};
|
|
@ -1,100 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:spotube/provider/spotify/utils/json_cast.dart';
|
|
||||||
import 'package:spotube/services/kv_store/encrypted_kv_store.dart';
|
|
||||||
import 'package:spotube/utils/platform.dart';
|
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
|
||||||
|
|
||||||
const kKeyBoxName = "spotube_box_name";
|
|
||||||
const kNoEncryptionWarningShownKey = "showedNoEncryptionWarning";
|
|
||||||
const kIsUsingEncryption = "isUsingEncryption";
|
|
||||||
String getBoxKey(String boxName) => "spotube_box_$boxName";
|
|
||||||
|
|
||||||
class PersistenceCacheBox<T> {
|
|
||||||
static late LazyBox _box;
|
|
||||||
static late LazyBox _encryptedBox;
|
|
||||||
|
|
||||||
final String cacheKey;
|
|
||||||
final bool encrypted;
|
|
||||||
|
|
||||||
final T Function(Map<String, dynamic>) fromJson;
|
|
||||||
|
|
||||||
PersistenceCacheBox(
|
|
||||||
this.cacheKey, {
|
|
||||||
required this.fromJson,
|
|
||||||
this.encrypted = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
static Future<String?> read(String key) async {
|
|
||||||
final localStorage = await SharedPreferences.getInstance();
|
|
||||||
if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
|
|
||||||
return localStorage.getString(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await localStorage.setBool(kIsUsingEncryption, true);
|
|
||||||
return await EncryptedKvStoreService.storage.read(key: key);
|
|
||||||
} catch (e) {
|
|
||||||
await localStorage.setBool(kIsUsingEncryption, false);
|
|
||||||
return localStorage.getString(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> write(String key, String value) async {
|
|
||||||
final localStorage = await SharedPreferences.getInstance();
|
|
||||||
if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
|
|
||||||
await localStorage.setString(key, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await localStorage.setBool(kIsUsingEncryption, true);
|
|
||||||
await EncryptedKvStoreService.storage.write(key: key, value: value);
|
|
||||||
} catch (e) {
|
|
||||||
await localStorage.setBool(kIsUsingEncryption, false);
|
|
||||||
await localStorage.setString(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> initializeBoxes({required String? path}) async {
|
|
||||||
String? boxName = await read(kKeyBoxName);
|
|
||||||
|
|
||||||
if (boxName == null) {
|
|
||||||
boxName = "spotube-${PrimitiveUtils.uuid.v4()}";
|
|
||||||
await write(kKeyBoxName, boxName);
|
|
||||||
}
|
|
||||||
|
|
||||||
String? encryptionKey = await read(getBoxKey(boxName));
|
|
||||||
|
|
||||||
if (encryptionKey == null) {
|
|
||||||
encryptionKey = base64Url.encode(Hive.generateSecureKey());
|
|
||||||
await write(getBoxKey(boxName), encryptionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
_encryptedBox = await Hive.openLazyBox(
|
|
||||||
boxName,
|
|
||||||
encryptionCipher: HiveAesCipher(base64Url.decode(encryptionKey)),
|
|
||||||
);
|
|
||||||
|
|
||||||
_box = await Hive.openLazyBox(
|
|
||||||
"spotube_cache",
|
|
||||||
path: path,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyBox get box => encrypted ? _encryptedBox : _box;
|
|
||||||
|
|
||||||
Future<T?> getData() async {
|
|
||||||
final json = await box.get(cacheKey);
|
|
||||||
|
|
||||||
if (json != null ||
|
|
||||||
(json is Map && json.entries.isNotEmpty) ||
|
|
||||||
(json is List && json.isNotEmpty)) {
|
|
||||||
return fromJson(castNestedJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
34
pubspec.lock
34
pubspec.lock
@ -334,14 +334,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.9.2"
|
version: "8.9.2"
|
||||||
buttons_tabbar:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: buttons_tabbar
|
|
||||||
sha256: "6e541377ab96d4223d8f072bc4f35c9d32dafe042005cad93530e0cd9d02801f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.14"
|
|
||||||
cached_network_image:
|
cached_network_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -486,14 +478,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
curved_navigation_bar:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: curved_navigation_bar
|
|
||||||
sha256: bb4ab128fcb6f4a9f0f1f72d227db531818b20218984789777f049fcbf919279
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.6"
|
|
||||||
dart_des:
|
dart_des:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1025,7 +1009,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2"
|
sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2"
|
||||||
@ -1892,14 +1876,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
popover:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: popover
|
|
||||||
sha256: "0606f3e10f92fc0459f5c52fd917738c29e7552323b28694d50c2d3312d0e1a2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
posix:
|
posix:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2149,14 +2125,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2"
|
version: "0.1.2"
|
||||||
sidebarx:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: sidebarx
|
|
||||||
sha256: abe39d6db237fb8e25c600e8039ffab80fa7fe71acab03e9c378c31f912d2766
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.17.1"
|
|
||||||
simple_icons:
|
simple_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -21,10 +21,8 @@ dependencies:
|
|||||||
audio_session: ^0.1.19
|
audio_session: ^0.1.19
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
bonsoir: ^5.1.10
|
bonsoir: ^5.1.10
|
||||||
buttons_tabbar: ^1.3.8
|
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
curved_navigation_bar: ^1.0.3
|
|
||||||
desktop_webview_window:
|
desktop_webview_window:
|
||||||
git:
|
git:
|
||||||
path: packages/desktop_webview_window
|
path: packages/desktop_webview_window
|
||||||
@ -62,7 +60,6 @@ dependencies:
|
|||||||
flutter_riverpod: ^2.5.1
|
flutter_riverpod: ^2.5.1
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
flutter_sharing_intent: ^1.1.0
|
flutter_sharing_intent: ^1.1.0
|
||||||
flutter_svg: ^1.1.6
|
|
||||||
form_validator: ^2.1.1
|
form_validator: ^2.1.1
|
||||||
freezed_annotation: ^2.4.1
|
freezed_annotation: ^2.4.1
|
||||||
fuzzywuzzy: ^1.1.6
|
fuzzywuzzy: ^1.1.6
|
||||||
@ -95,7 +92,6 @@ dependencies:
|
|||||||
path_provider: ^2.1.3
|
path_provider: ^2.1.3
|
||||||
permission_handler: ^11.3.1
|
permission_handler: ^11.3.1
|
||||||
piped_client: ^0.1.1
|
piped_client: ^0.1.1
|
||||||
popover: ^0.3.0
|
|
||||||
riverpod: ^2.5.1
|
riverpod: ^2.5.1
|
||||||
scrobblenaut:
|
scrobblenaut:
|
||||||
git:
|
git:
|
||||||
@ -107,7 +103,6 @@ dependencies:
|
|||||||
shelf: ^1.4.1
|
shelf: ^1.4.1
|
||||||
shelf_router: ^1.1.4
|
shelf_router: ^1.1.4
|
||||||
shelf_web_socket: ^2.0.0
|
shelf_web_socket: ^2.0.0
|
||||||
sidebarx: ^0.17.1
|
|
||||||
simple_icons: ^10.1.3
|
simple_icons: ^10.1.3
|
||||||
skeletonizer: ^1.1.1
|
skeletonizer: ^1.1.1
|
||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
|
Loading…
Reference in New Issue
Block a user