mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: implement metadata plugins based on hetu
This commit is contained in:
parent
69c0333327
commit
7a6821f28d
@ -235,9 +235,5 @@ class AppRouter extends RootStackRouter {
|
|||||||
page: LastFMLoginRoute.page,
|
page: LastFMLoginRoute.page,
|
||||||
// parentNavigatorKey: rootNavigatorKey,
|
// parentNavigatorKey: rootNavigatorKey,
|
||||||
),
|
),
|
||||||
AutoRoute(
|
|
||||||
path: "/webview",
|
|
||||||
page: WebviewRoute.page,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -147,7 +147,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
ref.listen(bonsoirProvider, (_, __) {});
|
ref.listen(bonsoirProvider, (_, __) {});
|
||||||
ref.listen(connectClientsProvider, (_, __) {});
|
ref.listen(connectClientsProvider, (_, __) {});
|
||||||
ref.listen(metadataPluginsProvider, (_, __) {});
|
ref.listen(metadataPluginsProvider, (_, __) {});
|
||||||
ref.listen(metadataPluginApiProvider, (_, __) {});
|
ref.listen(metadataPluginProvider, (_, __) {});
|
||||||
ref.listen(serverProvider, (_, __) {});
|
ref.listen(serverProvider, (_, __) {});
|
||||||
ref.listen(trayManagerProvider, (_, __) {});
|
ref.listen(trayManagerProvider, (_, __) {});
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@ part of 'metadata.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class SpotubeImageObject with _$SpotubeImageObject {
|
class SpotubeImageObject with _$SpotubeImageObject {
|
||||||
factory SpotubeImageObject({
|
factory SpotubeImageObject({
|
||||||
required final String url,
|
required String url,
|
||||||
required final int width,
|
int? width,
|
||||||
required final int height,
|
int? height,
|
||||||
}) = _SpotubeImageObject;
|
}) = _SpotubeImageObject;
|
||||||
|
|
||||||
factory SpotubeImageObject.fromJson(Map<String, dynamic> json) =>
|
factory SpotubeImageObject.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -775,8 +775,8 @@ SpotubeImageObject _$SpotubeImageObjectFromJson(Map<String, dynamic> json) {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SpotubeImageObject {
|
mixin _$SpotubeImageObject {
|
||||||
String get url => throw _privateConstructorUsedError;
|
String get url => throw _privateConstructorUsedError;
|
||||||
int get width => throw _privateConstructorUsedError;
|
int? get width => throw _privateConstructorUsedError;
|
||||||
int get height => throw _privateConstructorUsedError;
|
int? get height => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SpotubeImageObject to a JSON map.
|
/// Serializes this SpotubeImageObject to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@ -794,7 +794,7 @@ abstract class $SpotubeImageObjectCopyWith<$Res> {
|
|||||||
SpotubeImageObject value, $Res Function(SpotubeImageObject) then) =
|
SpotubeImageObject value, $Res Function(SpotubeImageObject) then) =
|
||||||
_$SpotubeImageObjectCopyWithImpl<$Res, SpotubeImageObject>;
|
_$SpotubeImageObjectCopyWithImpl<$Res, SpotubeImageObject>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({String url, int width, int height});
|
$Res call({String url, int? width, int? height});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -813,22 +813,22 @@ class _$SpotubeImageObjectCopyWithImpl<$Res, $Val extends SpotubeImageObject>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? url = null,
|
Object? url = null,
|
||||||
Object? width = null,
|
Object? width = freezed,
|
||||||
Object? height = null,
|
Object? height = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
url: null == url
|
url: null == url
|
||||||
? _value.url
|
? _value.url
|
||||||
: url // ignore: cast_nullable_to_non_nullable
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
width: null == width
|
width: freezed == width
|
||||||
? _value.width
|
? _value.width
|
||||||
: width // ignore: cast_nullable_to_non_nullable
|
: width // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int?,
|
||||||
height: null == height
|
height: freezed == height
|
||||||
? _value.height
|
? _value.height
|
||||||
: height // ignore: cast_nullable_to_non_nullable
|
: height // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -841,7 +841,7 @@ abstract class _$$SpotubeImageObjectImplCopyWith<$Res>
|
|||||||
__$$SpotubeImageObjectImplCopyWithImpl<$Res>;
|
__$$SpotubeImageObjectImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({String url, int width, int height});
|
$Res call({String url, int? width, int? height});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -858,22 +858,22 @@ class __$$SpotubeImageObjectImplCopyWithImpl<$Res>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? url = null,
|
Object? url = null,
|
||||||
Object? width = null,
|
Object? width = freezed,
|
||||||
Object? height = null,
|
Object? height = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SpotubeImageObjectImpl(
|
return _then(_$SpotubeImageObjectImpl(
|
||||||
url: null == url
|
url: null == url
|
||||||
? _value.url
|
? _value.url
|
||||||
: url // ignore: cast_nullable_to_non_nullable
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
width: null == width
|
width: freezed == width
|
||||||
? _value.width
|
? _value.width
|
||||||
: width // ignore: cast_nullable_to_non_nullable
|
: width // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int?,
|
||||||
height: null == height
|
height: freezed == height
|
||||||
? _value.height
|
? _value.height
|
||||||
: height // ignore: cast_nullable_to_non_nullable
|
: height // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -881,8 +881,7 @@ class __$$SpotubeImageObjectImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$SpotubeImageObjectImpl implements _SpotubeImageObject {
|
class _$SpotubeImageObjectImpl implements _SpotubeImageObject {
|
||||||
_$SpotubeImageObjectImpl(
|
_$SpotubeImageObjectImpl({required this.url, this.width, this.height});
|
||||||
{required this.url, required this.width, required this.height});
|
|
||||||
|
|
||||||
factory _$SpotubeImageObjectImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SpotubeImageObjectImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$SpotubeImageObjectImplFromJson(json);
|
_$$SpotubeImageObjectImplFromJson(json);
|
||||||
@ -890,9 +889,9 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject {
|
|||||||
@override
|
@override
|
||||||
final String url;
|
final String url;
|
||||||
@override
|
@override
|
||||||
final int width;
|
final int? width;
|
||||||
@override
|
@override
|
||||||
final int height;
|
final int? height;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -933,8 +932,8 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject {
|
|||||||
abstract class _SpotubeImageObject implements SpotubeImageObject {
|
abstract class _SpotubeImageObject implements SpotubeImageObject {
|
||||||
factory _SpotubeImageObject(
|
factory _SpotubeImageObject(
|
||||||
{required final String url,
|
{required final String url,
|
||||||
required final int width,
|
final int? width,
|
||||||
required final int height}) = _$SpotubeImageObjectImpl;
|
final int? height}) = _$SpotubeImageObjectImpl;
|
||||||
|
|
||||||
factory _SpotubeImageObject.fromJson(Map<String, dynamic> json) =
|
factory _SpotubeImageObject.fromJson(Map<String, dynamic> json) =
|
||||||
_$SpotubeImageObjectImpl.fromJson;
|
_$SpotubeImageObjectImpl.fromJson;
|
||||||
@ -942,9 +941,9 @@ abstract class _SpotubeImageObject implements SpotubeImageObject {
|
|||||||
@override
|
@override
|
||||||
String get url;
|
String get url;
|
||||||
@override
|
@override
|
||||||
int get width;
|
int? get width;
|
||||||
@override
|
@override
|
||||||
int get height;
|
int? get height;
|
||||||
|
|
||||||
/// Create a copy of SpotubeImageObject
|
/// Create a copy of SpotubeImageObject
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -2192,11 +2191,10 @@ SpotubeUserObject _$SpotubeUserObjectFromJson(Map<String, dynamic> json) {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SpotubeUserObject {
|
mixin _$SpotubeUserObject {
|
||||||
String get uid => throw _privateConstructorUsedError;
|
String get id => throw _privateConstructorUsedError;
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name => throw _privateConstructorUsedError;
|
||||||
List<SpotubeImageObject> get avatars => throw _privateConstructorUsedError;
|
List<SpotubeImageObject> get images => throw _privateConstructorUsedError;
|
||||||
String get externalUrl => throw _privateConstructorUsedError;
|
String get externalUri => throw _privateConstructorUsedError;
|
||||||
String get displayName => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Serializes this SpotubeUserObject to a JSON map.
|
/// Serializes this SpotubeUserObject to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@ -2215,11 +2213,10 @@ abstract class $SpotubeUserObjectCopyWith<$Res> {
|
|||||||
_$SpotubeUserObjectCopyWithImpl<$Res, SpotubeUserObject>;
|
_$SpotubeUserObjectCopyWithImpl<$Res, SpotubeUserObject>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String uid,
|
{String id,
|
||||||
String name,
|
String name,
|
||||||
List<SpotubeImageObject> avatars,
|
List<SpotubeImageObject> images,
|
||||||
String externalUrl,
|
String externalUri});
|
||||||
String displayName});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -2237,32 +2234,27 @@ class _$SpotubeUserObjectCopyWithImpl<$Res, $Val extends SpotubeUserObject>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? uid = null,
|
Object? id = null,
|
||||||
Object? name = null,
|
Object? name = null,
|
||||||
Object? avatars = null,
|
Object? images = null,
|
||||||
Object? externalUrl = null,
|
Object? externalUri = null,
|
||||||
Object? displayName = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
uid: null == uid
|
id: null == id
|
||||||
? _value.uid
|
? _value.id
|
||||||
: uid // ignore: cast_nullable_to_non_nullable
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
name: null == name
|
name: null == name
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
avatars: null == avatars
|
images: null == images
|
||||||
? _value.avatars
|
? _value.images
|
||||||
: avatars // ignore: cast_nullable_to_non_nullable
|
: images // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SpotubeImageObject>,
|
as List<SpotubeImageObject>,
|
||||||
externalUrl: null == externalUrl
|
externalUri: null == externalUri
|
||||||
? _value.externalUrl
|
? _value.externalUri
|
||||||
: externalUrl // ignore: cast_nullable_to_non_nullable
|
: externalUri // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
|
||||||
displayName: null == displayName
|
|
||||||
? _value.displayName
|
|
||||||
: displayName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
as String,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
@ -2277,11 +2269,10 @@ abstract class _$$SpotubeUserObjectImplCopyWith<$Res>
|
|||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String uid,
|
{String id,
|
||||||
String name,
|
String name,
|
||||||
List<SpotubeImageObject> avatars,
|
List<SpotubeImageObject> images,
|
||||||
String externalUrl,
|
String externalUri});
|
||||||
String displayName});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -2297,32 +2288,27 @@ class __$$SpotubeUserObjectImplCopyWithImpl<$Res>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? uid = null,
|
Object? id = null,
|
||||||
Object? name = null,
|
Object? name = null,
|
||||||
Object? avatars = null,
|
Object? images = null,
|
||||||
Object? externalUrl = null,
|
Object? externalUri = null,
|
||||||
Object? displayName = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SpotubeUserObjectImpl(
|
return _then(_$SpotubeUserObjectImpl(
|
||||||
uid: null == uid
|
id: null == id
|
||||||
? _value.uid
|
? _value.id
|
||||||
: uid // ignore: cast_nullable_to_non_nullable
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
name: null == name
|
name: null == name
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
avatars: null == avatars
|
images: null == images
|
||||||
? _value._avatars
|
? _value._images
|
||||||
: avatars // ignore: cast_nullable_to_non_nullable
|
: images // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SpotubeImageObject>,
|
as List<SpotubeImageObject>,
|
||||||
externalUrl: null == externalUrl
|
externalUri: null == externalUri
|
||||||
? _value.externalUrl
|
? _value.externalUri
|
||||||
: externalUrl // ignore: cast_nullable_to_non_nullable
|
: externalUri // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
|
||||||
displayName: null == displayName
|
|
||||||
? _value.displayName
|
|
||||||
: displayName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -2332,37 +2318,34 @@ class __$$SpotubeUserObjectImplCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$SpotubeUserObjectImpl implements _SpotubeUserObject {
|
class _$SpotubeUserObjectImpl implements _SpotubeUserObject {
|
||||||
_$SpotubeUserObjectImpl(
|
_$SpotubeUserObjectImpl(
|
||||||
{required this.uid,
|
{required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
final List<SpotubeImageObject> avatars = const [],
|
final List<SpotubeImageObject> images = const [],
|
||||||
required this.externalUrl,
|
required this.externalUri})
|
||||||
required this.displayName})
|
: _images = images;
|
||||||
: _avatars = avatars;
|
|
||||||
|
|
||||||
factory _$SpotubeUserObjectImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SpotubeUserObjectImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$SpotubeUserObjectImplFromJson(json);
|
_$$SpotubeUserObjectImplFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String uid;
|
final String id;
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
final List<SpotubeImageObject> _avatars;
|
final List<SpotubeImageObject> _images;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
List<SpotubeImageObject> get avatars {
|
List<SpotubeImageObject> get images {
|
||||||
if (_avatars is EqualUnmodifiableListView) return _avatars;
|
if (_images is EqualUnmodifiableListView) return _images;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_avatars);
|
return EqualUnmodifiableListView(_images);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String externalUrl;
|
final String externalUri;
|
||||||
@override
|
|
||||||
final String displayName;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SpotubeUserObject(uid: $uid, name: $name, avatars: $avatars, externalUrl: $externalUrl, displayName: $displayName)';
|
return 'SpotubeUserObject(id: $id, name: $name, images: $images, externalUri: $externalUri)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -2370,19 +2353,17 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject {
|
|||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$SpotubeUserObjectImpl &&
|
other is _$SpotubeUserObjectImpl &&
|
||||||
(identical(other.uid, uid) || other.uid == uid) &&
|
(identical(other.id, id) || other.id == id) &&
|
||||||
(identical(other.name, name) || other.name == name) &&
|
(identical(other.name, name) || other.name == name) &&
|
||||||
const DeepCollectionEquality().equals(other._avatars, _avatars) &&
|
const DeepCollectionEquality().equals(other._images, _images) &&
|
||||||
(identical(other.externalUrl, externalUrl) ||
|
(identical(other.externalUri, externalUri) ||
|
||||||
other.externalUrl == externalUrl) &&
|
other.externalUri == externalUri));
|
||||||
(identical(other.displayName, displayName) ||
|
|
||||||
other.displayName == displayName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, uid, name,
|
int get hashCode => Object.hash(runtimeType, id, name,
|
||||||
const DeepCollectionEquality().hash(_avatars), externalUrl, displayName);
|
const DeepCollectionEquality().hash(_images), externalUri);
|
||||||
|
|
||||||
/// Create a copy of SpotubeUserObject
|
/// Create a copy of SpotubeUserObject
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -2403,25 +2384,22 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject {
|
|||||||
|
|
||||||
abstract class _SpotubeUserObject implements SpotubeUserObject {
|
abstract class _SpotubeUserObject implements SpotubeUserObject {
|
||||||
factory _SpotubeUserObject(
|
factory _SpotubeUserObject(
|
||||||
{required final String uid,
|
{required final String id,
|
||||||
required final String name,
|
required final String name,
|
||||||
final List<SpotubeImageObject> avatars,
|
final List<SpotubeImageObject> images,
|
||||||
required final String externalUrl,
|
required final String externalUri}) = _$SpotubeUserObjectImpl;
|
||||||
required final String displayName}) = _$SpotubeUserObjectImpl;
|
|
||||||
|
|
||||||
factory _SpotubeUserObject.fromJson(Map<String, dynamic> json) =
|
factory _SpotubeUserObject.fromJson(Map<String, dynamic> json) =
|
||||||
_$SpotubeUserObjectImpl.fromJson;
|
_$SpotubeUserObjectImpl.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get uid;
|
String get id;
|
||||||
@override
|
@override
|
||||||
String get name;
|
String get name;
|
||||||
@override
|
@override
|
||||||
List<SpotubeImageObject> get avatars;
|
List<SpotubeImageObject> get images;
|
||||||
@override
|
@override
|
||||||
String get externalUrl;
|
String get externalUri;
|
||||||
@override
|
|
||||||
String get displayName;
|
|
||||||
|
|
||||||
/// Create a copy of SpotubeUserObject
|
/// Create a copy of SpotubeUserObject
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@ -84,8 +84,8 @@ Map<String, dynamic> _$$SpotubeFeedObjectImplToJson(
|
|||||||
_$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) =>
|
_$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) =>
|
||||||
_$SpotubeImageObjectImpl(
|
_$SpotubeImageObjectImpl(
|
||||||
url: json['url'] as String,
|
url: json['url'] as String,
|
||||||
width: (json['width'] as num).toInt(),
|
width: (json['width'] as num?)?.toInt(),
|
||||||
height: (json['height'] as num).toInt(),
|
height: (json['height'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SpotubeImageObjectImplToJson(
|
Map<String, dynamic> _$$SpotubeImageObjectImplToJson(
|
||||||
@ -203,25 +203,23 @@ Map<String, dynamic> _$$SpotubeTrackObjectImplToJson(
|
|||||||
|
|
||||||
_$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) =>
|
_$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) =>
|
||||||
_$SpotubeUserObjectImpl(
|
_$SpotubeUserObjectImpl(
|
||||||
uid: json['uid'] as String,
|
id: json['id'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
avatars: (json['avatars'] as List<dynamic>?)
|
images: (json['images'] as List<dynamic>?)
|
||||||
?.map((e) => SpotubeImageObject.fromJson(
|
?.map((e) => SpotubeImageObject.fromJson(
|
||||||
Map<String, dynamic>.from(e as Map)))
|
Map<String, dynamic>.from(e as Map)))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
externalUrl: json['externalUrl'] as String,
|
externalUri: json['externalUri'] as String,
|
||||||
displayName: json['displayName'] as String,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SpotubeUserObjectImplToJson(
|
Map<String, dynamic> _$$SpotubeUserObjectImplToJson(
|
||||||
_$SpotubeUserObjectImpl instance) =>
|
_$SpotubeUserObjectImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'uid': instance.uid,
|
'id': instance.id,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'avatars': instance.avatars.map((e) => e.toJson()).toList(),
|
'images': instance.images.map((e) => e.toJson()).toList(),
|
||||||
'externalUrl': instance.externalUrl,
|
'externalUri': instance.externalUri,
|
||||||
'displayName': instance.displayName,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) =>
|
_$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) =>
|
||||||
|
@ -3,11 +3,10 @@ part of 'metadata.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class SpotubeUserObject with _$SpotubeUserObject {
|
class SpotubeUserObject with _$SpotubeUserObject {
|
||||||
factory SpotubeUserObject({
|
factory SpotubeUserObject({
|
||||||
required final String uid,
|
required final String id,
|
||||||
required final String name,
|
required final String name,
|
||||||
@Default([]) final List<SpotubeImageObject> avatars,
|
@Default([]) final List<SpotubeImageObject> images,
|
||||||
required final String externalUrl,
|
required final String externalUri,
|
||||||
required final String displayName,
|
|
||||||
}) = _SpotubeUserObject;
|
}) = _SpotubeUserObject;
|
||||||
|
|
||||||
factory SpotubeUserObject.fromJson(Map<String, dynamic> json) =>
|
factory SpotubeUserObject.fromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -7,6 +7,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/form/text_form_field.dart';
|
import 'package:spotube/components/form/text_form_field.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/auth.dart';
|
import 'package:spotube/provider/metadata_plugin/auth.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@ -21,10 +22,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final plugins = ref.watch(metadataPluginsProvider);
|
final plugins = ref.watch(metadataPluginsProvider);
|
||||||
final pluginsNotifier = ref.watch(metadataPluginsProvider.notifier);
|
final pluginsNotifier = ref.watch(metadataPluginsProvider.notifier);
|
||||||
final metadataApi = ref.watch(metadataPluginApiProvider);
|
final metadataPlugin = ref.watch(metadataPluginProvider);
|
||||||
final isAuthenticated = ref.watch(metadataAuthenticatedProvider);
|
final isAuthenticated = ref.watch(metadataPluginAuthenticatedProvider);
|
||||||
|
|
||||||
final artists = ref.watch(metadataUserArtistsProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
headers: const [
|
headers: const [
|
||||||
@ -111,9 +110,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget {
|
|||||||
final plugin = plugins.asData!.value.plugins[index];
|
final plugin = plugins.asData!.value.plugins[index];
|
||||||
final isDefault = plugins.asData!.value.defaultPlugin == index;
|
final isDefault = plugins.asData!.value.defaultPlugin == index;
|
||||||
final requiresAuth = isDefault &&
|
final requiresAuth = isDefault &&
|
||||||
metadataApi.hasValue &&
|
plugin.abilities.contains(PluginAbilities.authentication);
|
||||||
metadataApi.asData?.value?.signatureFlags.requiresAuth ==
|
|
||||||
true;
|
|
||||||
return Card(
|
return Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
@ -153,8 +150,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget {
|
|||||||
if (isAuthenticated.asData?.value != true)
|
if (isAuthenticated.asData?.value != true)
|
||||||
Button.primary(
|
Button.primary(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await metadataApi.asData?.value
|
await metadataPlugin.asData?.value?.auth
|
||||||
?.authenticate();
|
.authenticate();
|
||||||
},
|
},
|
||||||
leading: const Icon(SpotubeIcons.login),
|
leading: const Icon(SpotubeIcons.login),
|
||||||
child: const Text("Login"),
|
child: const Text("Login"),
|
||||||
@ -162,7 +159,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget {
|
|||||||
else
|
else
|
||||||
Button.destructive(
|
Button.destructive(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await metadataApi.asData?.value?.logout();
|
await metadataPlugin.asData?.value?.auth
|
||||||
|
.logout();
|
||||||
},
|
},
|
||||||
leading: const Icon(SpotubeIcons.logout),
|
leading: const Icon(SpotubeIcons.logout),
|
||||||
child: const Text("Logout"),
|
child: const Text("Logout"),
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
|
||||||
import 'package:spotube/components/button/back_button.dart';
|
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
|
||||||
import 'package:spotube/services/metadata/apis/webview.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class WebviewPage extends StatelessWidget {
|
|
||||||
final WebviewInitialSettings? initialSettings;
|
|
||||||
final String? url;
|
|
||||||
final void Function(InAppWebViewController controller, WebUri? url)?
|
|
||||||
onLoadStop;
|
|
||||||
|
|
||||||
const WebviewPage({
|
|
||||||
super.key,
|
|
||||||
this.initialSettings,
|
|
||||||
this.url,
|
|
||||||
this.onLoadStop,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SafeArea(
|
|
||||||
bottom: false,
|
|
||||||
child: Scaffold(
|
|
||||||
headers: const [
|
|
||||||
TitleBar(
|
|
||||||
leading: [BackButton(color: Colors.white)],
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
floatingHeader: true,
|
|
||||||
child: InAppWebView(
|
|
||||||
initialSettings: InAppWebViewSettings(
|
|
||||||
userAgent: initialSettings?.userAgent ??
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36",
|
|
||||||
incognito: initialSettings?.incognito ?? false,
|
|
||||||
clearCache: initialSettings?.clearCache ?? false,
|
|
||||||
clearSessionCache: initialSettings?.clearSessionCache ?? false,
|
|
||||||
),
|
|
||||||
initialUrlRequest: URLRequest(
|
|
||||||
url: WebUri("https://accounts.spotify.com/"),
|
|
||||||
),
|
|
||||||
onPermissionRequest: (controller, permissionRequest) async {
|
|
||||||
return PermissionResponse(
|
|
||||||
resources: permissionRequest.resources,
|
|
||||||
action: PermissionResponseAction.GRANT,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onLoadStop: onLoadStop,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +1,37 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:riverpod/riverpod.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||||
|
|
||||||
class MetadataAuthenticationNotifier extends AsyncNotifier<bool> {
|
class MetadataPluginAuthenticatedNotifier extends AsyncNotifier<bool> {
|
||||||
MetadataAuthenticationNotifier();
|
|
||||||
@override
|
@override
|
||||||
build() async {
|
FutureOr<bool> build() async {
|
||||||
final metadataApi = await ref.watch(metadataPluginApiProvider.future);
|
final defaultPluginConfig = ref.watch(metadataPluginsProvider);
|
||||||
|
if (defaultPluginConfig.asData?.value.defaultPluginConfig?.abilities
|
||||||
if (metadataApi?.signatureFlags.requiresAuth != true) {
|
.contains(PluginAbilities.authentication) !=
|
||||||
|
true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final subscription = metadataApi?.authenticatedStream.listen((event) {
|
final defaultPlugin = await ref.watch(metadataPluginProvider.future);
|
||||||
state = AsyncValue.data(event);
|
if (defaultPlugin == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final sub = defaultPlugin.auth.authStateStream.listen((event) {
|
||||||
|
state = AsyncData(defaultPlugin.auth.isAuthenticated());
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.onDispose(() {
|
ref.onDispose(() {
|
||||||
subscription?.cancel();
|
sub.cancel();
|
||||||
});
|
});
|
||||||
|
|
||||||
return await metadataApi?.isAuthenticated() ?? false;
|
return defaultPlugin.auth.isAuthenticated();
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> login() async {
|
|
||||||
final metadataApi = await ref.read(metadataPluginApiProvider.future);
|
|
||||||
|
|
||||||
if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await metadataApi.authenticate();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> logout() async {
|
|
||||||
final metadataApi = await ref.read(metadataPluginApiProvider.future);
|
|
||||||
|
|
||||||
if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await metadataApi.logout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final metadataAuthenticatedProvider =
|
final metadataPluginAuthenticatedProvider =
|
||||||
AsyncNotifierProvider<MetadataAuthenticationNotifier, bool>(
|
AsyncNotifierProvider<MetadataPluginAuthenticatedNotifier, bool>(
|
||||||
() => MetadataAuthenticationNotifier(),
|
MetadataPluginAuthenticatedNotifier.new,
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,6 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/models/metadata/metadata.dart';
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
import 'package:spotube/provider/database/database.dart';
|
import 'package:spotube/provider/database/database.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/auth.dart';
|
|
||||||
import 'package:spotube/services/dio/dio.dart';
|
import 'package:spotube/services/dio/dio.dart';
|
||||||
import 'package:spotube/services/metadata/metadata.dart';
|
import 'package:spotube/services/metadata/metadata.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
@ -315,20 +314,20 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getPluginLibraryCode(PluginConfiguration plugin) async {
|
Future<Uint8List> getPluginByteCode(PluginConfiguration plugin) async {
|
||||||
final pluginDir = await _getPluginDir();
|
final pluginDir = await _getPluginDir();
|
||||||
final pluginExtractionDirPath = join(
|
final pluginExtractionDirPath = join(
|
||||||
pluginDir.path,
|
pluginDir.path,
|
||||||
ServiceUtils.sanitizeFilename(plugin.name),
|
ServiceUtils.sanitizeFilename(plugin.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
final libraryFile = File(join(pluginExtractionDirPath, "dist", "index.js"));
|
final libraryFile = File(join(pluginExtractionDirPath, "plugin.out"));
|
||||||
|
|
||||||
if (!libraryFile.existsSync()) {
|
if (!libraryFile.existsSync()) {
|
||||||
throw Exception("No dist/index.js found");
|
throw Exception("No plugin.out (Bytecode) file found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await libraryFile.readAsString();
|
return await libraryFile.readAsBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,7 +336,7 @@ final metadataPluginsProvider =
|
|||||||
MetadataPluginNotifier.new,
|
MetadataPluginNotifier.new,
|
||||||
);
|
);
|
||||||
|
|
||||||
final metadataPluginApiProvider = FutureProvider<MetadataApiSignature?>(
|
final metadataPluginProvider = FutureProvider<MetadataPlugin?>(
|
||||||
(ref) async {
|
(ref) async {
|
||||||
final defaultPlugin = await ref.watch(
|
final defaultPlugin = await ref.watch(
|
||||||
metadataPluginsProvider.selectAsync((data) => data.defaultPluginConfig),
|
metadataPluginsProvider.selectAsync((data) => data.defaultPluginConfig),
|
||||||
@ -348,38 +347,9 @@ final metadataPluginApiProvider = FutureProvider<MetadataApiSignature?>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
final pluginsNotifier = ref.read(metadataPluginsProvider.notifier);
|
final pluginsNotifier = ref.read(metadataPluginsProvider.notifier);
|
||||||
final libraryCode =
|
final pluginByteCode =
|
||||||
await pluginsNotifier.getPluginLibraryCode(defaultPlugin);
|
await pluginsNotifier.getPluginByteCode(defaultPlugin);
|
||||||
|
|
||||||
return MetadataApiSignature.init(libraryCode, defaultPlugin);
|
return await MetadataPlugin.create(defaultPlugin, pluginByteCode);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final metadataProviderUserProvider = FutureProvider(
|
|
||||||
(ref) async {
|
|
||||||
final metadataApi = await ref.watch(metadataPluginApiProvider.future);
|
|
||||||
ref.watch(metadataAuthenticatedProvider);
|
|
||||||
|
|
||||||
if (metadataApi == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return metadataApi.getMe();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final metadataUserArtistsProvider =
|
|
||||||
FutureProvider<List<SpotubeArtistObject>>((ref) async {
|
|
||||||
final metadataApi = await ref.watch(metadataPluginApiProvider.future);
|
|
||||||
ref.watch(metadataAuthenticatedProvider);
|
|
||||||
|
|
||||||
final userId = await ref.watch(
|
|
||||||
metadataProviderUserProvider.selectAsync((data) => data?.uid),
|
|
||||||
);
|
|
||||||
if (metadataApi == null || userId == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = await metadataApi.listUserSavedArtists(userId);
|
|
||||||
|
|
||||||
return res.items as List<SpotubeArtistObject>;
|
|
||||||
});
|
|
||||||
|
16
lib/provider/metadata_plugin/user.dart
Normal file
16
lib/provider/metadata_plugin/user.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
|
import 'package:spotube/provider/metadata_plugin/auth.dart';
|
||||||
|
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||||
|
|
||||||
|
final metadataPluginUserProvider = FutureProvider<SpotubeUserObject?>(
|
||||||
|
(ref) async {
|
||||||
|
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
|
||||||
|
ref.watch(metadataPluginAuthenticatedProvider);
|
||||||
|
|
||||||
|
if (metadataPlugin == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return metadataPlugin.user.me();
|
||||||
|
},
|
||||||
|
);
|
@ -1,60 +1,78 @@
|
|||||||
import 'package:flutter_js/flutter_js.dart';
|
import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class PluginLocalStorageApi {
|
class SharedPreferencesLocalStorage implements Localstorage {
|
||||||
final JavascriptRuntime runtime;
|
final SharedPreferences _prefs;
|
||||||
final SharedPreferences sharedPreferences;
|
final String pluginSlug;
|
||||||
|
|
||||||
final String pluginName;
|
SharedPreferencesLocalStorage(this._prefs, this.pluginSlug);
|
||||||
|
|
||||||
PluginLocalStorageApi({
|
String prefix(String key) {
|
||||||
required this.runtime,
|
return 'spotube_plugin.$pluginSlug.$key';
|
||||||
required this.sharedPreferences,
|
|
||||||
required this.pluginName,
|
|
||||||
}) {
|
|
||||||
runtime.onMessage("LocalStorage.getItem", (args) {
|
|
||||||
final key = args[0]["key"];
|
|
||||||
final value = getItem(key);
|
|
||||||
runtime.evaluate(
|
|
||||||
"""
|
|
||||||
eventEmitter.emit('LocalStorage.getItem', ${value != null ? "'$value'" : "null"});
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.onMessage("LocalStorage.setItem", (args) {
|
|
||||||
final map = args[0] as Map<String, dynamic>;
|
|
||||||
setItem(map["key"], map["value"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.onMessage("LocalStorage.removeItem", (args) {
|
|
||||||
final map = args[0];
|
|
||||||
removeItem(map["key"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.onMessage("LocalStorage.clear", (args) {
|
|
||||||
clear();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setItem(String key, String value) async {
|
@override
|
||||||
await sharedPreferences.setString("plugin.$pluginName.$key", value);
|
Future<void> clear() {
|
||||||
|
return _prefs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getItem(String key) {
|
@override
|
||||||
return sharedPreferences.getString("plugin.$pluginName.$key");
|
Future<bool> containsKey(String key) async {
|
||||||
|
return _prefs.containsKey(prefix(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeItem(String key) async {
|
@override
|
||||||
await sharedPreferences.remove("plugin.$pluginName.$key");
|
Future<bool?> getBool(String key) async {
|
||||||
|
return _prefs.getBool(prefix(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() async {
|
@override
|
||||||
final keys = sharedPreferences.getKeys();
|
Future<double?> getDouble(String key) async {
|
||||||
for (String key in keys) {
|
return _prefs.getDouble(prefix(key));
|
||||||
if (key.startsWith("plugin.$pluginName.")) {
|
|
||||||
await sharedPreferences.remove(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int?> getInt(String key) async {
|
||||||
|
return _prefs.getInt(prefix(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getString(String key) async {
|
||||||
|
return _prefs.getString(prefix(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<String>?> getStringList(String key) async {
|
||||||
|
return _prefs.getStringList(prefix(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> remove(String key) async {
|
||||||
|
await _prefs.remove(prefix(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setBool(String key, bool value) async {
|
||||||
|
await _prefs.setBool(prefix(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setDouble(String key, double value) async {
|
||||||
|
await _prefs.setDouble(prefix(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setInt(String key, int value) async {
|
||||||
|
await _prefs.setInt(prefix(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setString(String key, String value) async {
|
||||||
|
await _prefs.setString(prefix(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setStringList(String key, List<String> value) async {
|
||||||
|
await _prefs.setStringList(prefix(key), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter_js/flutter_js.dart';
|
|
||||||
|
|
||||||
class PluginSetIntervalApi {
|
|
||||||
final JavascriptRuntime runtime;
|
|
||||||
|
|
||||||
final Map<String, Timer> _timers = {};
|
|
||||||
|
|
||||||
PluginSetIntervalApi(this.runtime) {
|
|
||||||
runtime.evaluate(
|
|
||||||
"""
|
|
||||||
var __NATIVE_FLUTTER_JS__setIntervalCount = -1;
|
|
||||||
var __NATIVE_FLUTTER_JS__setIntervalCallbacks = {};
|
|
||||||
function setInterval(fnInterval, interval) {
|
|
||||||
try {
|
|
||||||
__NATIVE_FLUTTER_JS__setIntervalCount += 1;
|
|
||||||
var intervalIndex = '' + __NATIVE_FLUTTER_JS__setIntervalCount;
|
|
||||||
__NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex] = fnInterval;
|
|
||||||
;
|
|
||||||
sendMessage('PluginSetIntervalApi.setInterval', JSON.stringify({ intervalIndex, interval}));
|
|
||||||
return intervalIndex;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('ERROR HERE',e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function clearInterval(intervalIndex) {
|
|
||||||
try {
|
|
||||||
delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex];
|
|
||||||
sendMessage('PluginSetIntervalApi.clearInterval', JSON.stringify({ intervalIndex}));
|
|
||||||
} catch (e) {
|
|
||||||
console.error('ERROR HERE',e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
1
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
|
|
||||||
runtime.onMessage('PluginSetIntervalApi.setInterval', (dynamic args) {
|
|
||||||
try {
|
|
||||||
int duration = args['interval'] ?? 0;
|
|
||||||
String idx = args['intervalIndex'];
|
|
||||||
|
|
||||||
_timers[idx] =
|
|
||||||
Timer.periodic(Duration(milliseconds: duration), (timer) {
|
|
||||||
runtime.evaluate("""
|
|
||||||
__NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx].call();
|
|
||||||
delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx];
|
|
||||||
""");
|
|
||||||
});
|
|
||||||
} on Exception catch (e) {
|
|
||||||
print('Exception no setInterval: $e');
|
|
||||||
} on Error catch (e) {
|
|
||||||
print('Erro no setInterval: $e');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.onMessage('PluginSetIntervalApi.clearInterval', (dynamic args) {
|
|
||||||
try {
|
|
||||||
String idx = args['intervalIndex'];
|
|
||||||
if (_timers.containsKey(idx)) {
|
|
||||||
_timers[idx]?.cancel();
|
|
||||||
_timers.remove(idx);
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
print('Exception no clearInterval: $e');
|
|
||||||
} on Error catch (e) {
|
|
||||||
print('Error no clearInterval: $e');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
for (var timer in _timers.values) {
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
_timers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import 'package:flutter_js/javascript_runtime.dart';
|
|
||||||
import 'package:otp_util/otp_util.dart';
|
|
||||||
// ignore: implementation_imports
|
|
||||||
import 'package:otp_util/src/utils/generic_util.dart';
|
|
||||||
|
|
||||||
class PluginTotpGenerator {
|
|
||||||
final JavascriptRuntime runtime;
|
|
||||||
|
|
||||||
PluginTotpGenerator(this.runtime) {
|
|
||||||
runtime.onMessage("TotpGenerator.generate", (args) {
|
|
||||||
final opts = args[0];
|
|
||||||
if (opts is! Map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final totp = TOTP(
|
|
||||||
secret: opts["secret"] as String,
|
|
||||||
algorithm: OTPAlgorithm.values.firstWhere(
|
|
||||||
(e) => e.name == opts["algorithm"],
|
|
||||||
orElse: () => OTPAlgorithm.SHA1,
|
|
||||||
),
|
|
||||||
digits: opts["digits"] as int? ?? 6,
|
|
||||||
interval: opts["interval"] as int? ?? 30,
|
|
||||||
);
|
|
||||||
|
|
||||||
final otp = totp.generateOTP(
|
|
||||||
input: Util.timeFormat(
|
|
||||||
time: DateTime.fromMillisecondsSinceEpoch(opts["period"]),
|
|
||||||
interval: 30,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
runtime.evaluate(
|
|
||||||
"""
|
|
||||||
eventEmitter.emit('TotpGenerator.generate', '$otp');
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:flutter_js/flutter_js.dart';
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart' hide join;
|
|
||||||
import 'package:spotube/collections/routes.dart';
|
|
||||||
import 'package:spotube/collections/routes.gr.dart';
|
|
||||||
import 'package:spotube/pages/mobile_login/no_webview_runtime_dialog.dart';
|
|
||||||
import 'package:spotube/utils/platform.dart';
|
|
||||||
|
|
||||||
class WebviewInitialSettings {
|
|
||||||
final String? userAgent;
|
|
||||||
final bool? incognito;
|
|
||||||
final bool? clearCache;
|
|
||||||
final bool? clearSessionCache;
|
|
||||||
|
|
||||||
WebviewInitialSettings({
|
|
||||||
this.userAgent,
|
|
||||||
this.incognito,
|
|
||||||
this.clearCache,
|
|
||||||
this.clearSessionCache,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory WebviewInitialSettings.fromJson(Map<String, dynamic> json) {
|
|
||||||
return WebviewInitialSettings(
|
|
||||||
userAgent: json["userAgent"],
|
|
||||||
incognito: json["incognito"],
|
|
||||||
clearCache: json["clearCache"],
|
|
||||||
clearSessionCache: json["clearSessionCache"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PluginWebViewApi {
|
|
||||||
JavascriptRuntime runtime;
|
|
||||||
|
|
||||||
PluginWebViewApi({
|
|
||||||
required this.runtime,
|
|
||||||
}) {
|
|
||||||
runtime.onMessage("WebView.show", (args) {
|
|
||||||
if (args[0] is! Map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showWebView(
|
|
||||||
url: args[0]["url"] as String,
|
|
||||||
initialSettings: args[0]["initialSettings"] != null
|
|
||||||
? WebviewInitialSettings.fromJson(
|
|
||||||
args[0]["initialSettings"],
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Webview? webviewWindow;
|
|
||||||
|
|
||||||
Future showWebView({
|
|
||||||
required String url,
|
|
||||||
WebviewInitialSettings? initialSettings,
|
|
||||||
}) async {
|
|
||||||
if (rootNavigatorKey.currentContext == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final context = rootNavigatorKey.currentContext!;
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
if (kIsMobile || kIsMacOS) {
|
|
||||||
context.pushRoute(WebviewRoute(
|
|
||||||
initialSettings: initialSettings,
|
|
||||||
url: url,
|
|
||||||
onLoadStop: (controller, uri) async {
|
|
||||||
if (uri == null) return;
|
|
||||||
final cookies = await CookieManager().getAllCookies();
|
|
||||||
|
|
||||||
final jsonCookies = cookies.map((e) {
|
|
||||||
return {
|
|
||||||
"name": e.name,
|
|
||||||
"value": e.value,
|
|
||||||
"domain": e.domain,
|
|
||||||
"path": e.path,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.onMessage("WebView.close", (args) {
|
|
||||||
context.back();
|
|
||||||
});
|
|
||||||
|
|
||||||
runtime.evaluate(
|
|
||||||
"""
|
|
||||||
eventEmitter.emit('WebView.onLoadFinish', {url: '${uri.toString()}', cookies: ${jsonEncode(jsonCookies)}});
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final applicationSupportDir = await getApplicationSupportDirectory();
|
|
||||||
final userDataFolder = Directory(
|
|
||||||
join(applicationSupportDir.path, "webview_window_Webview2"),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!await userDataFolder.exists()) {
|
|
||||||
await userDataFolder.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
final webview = await WebviewWindow.create(
|
|
||||||
configuration: CreateConfiguration(
|
|
||||||
title: "Webview",
|
|
||||||
titleBarTopPadding: kIsMacOS ? 20 : 0,
|
|
||||||
windowHeight: 720,
|
|
||||||
windowWidth: 1280,
|
|
||||||
userDataFolderWindows: userDataFolder.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
webviewWindow = webview;
|
|
||||||
|
|
||||||
runtime.onMessage("WebView.close", (args) {
|
|
||||||
webview.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
webview
|
|
||||||
..setBrightness(theme.colorScheme.brightness)
|
|
||||||
..launch(url)
|
|
||||||
..setOnUrlRequestCallback((url) {
|
|
||||||
() async {
|
|
||||||
final cookies = await webview.getAllCookies();
|
|
||||||
final jsonCookies = cookies.map((e) {
|
|
||||||
return {
|
|
||||||
"name": e.name,
|
|
||||||
"value": e.value,
|
|
||||||
"domain": e.domain,
|
|
||||||
"path": e.path,
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
runtime.evaluate(
|
|
||||||
"""
|
|
||||||
eventEmitter.emit('WebView.onLoadFinish', {url: '$url', cookies: ${jsonEncode(jsonCookies)}});
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
}();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (_) {
|
|
||||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return const NoWebviewRuntimeDialog();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
webviewWindow?.close();
|
|
||||||
webviewWindow = null;
|
|
||||||
}
|
|
||||||
}
|
|
0
lib/services/metadata/endpoints/album.dart
Normal file
0
lib/services/metadata/endpoints/album.dart
Normal file
0
lib/services/metadata/endpoints/artist.dart
Normal file
0
lib/services/metadata/endpoints/artist.dart
Normal file
33
lib/services/metadata/endpoints/auth.dart
Normal file
33
lib/services/metadata/endpoints/auth.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:hetu_script/hetu_script.dart';
|
||||||
|
import 'package:hetu_std/hetu_std.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
|
class MetadataAuthEndpoint {
|
||||||
|
final Hetu hetu;
|
||||||
|
|
||||||
|
MetadataAuthEndpoint(this.hetu);
|
||||||
|
|
||||||
|
Stream get authStateStream =>
|
||||||
|
hetu.eval("metadataPlugin.auth.authStateStream");
|
||||||
|
|
||||||
|
Future<void> authenticate() async {
|
||||||
|
await hetu.eval("metadataPlugin.auth.authenticate()");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAuthenticated() {
|
||||||
|
return hetu.eval("metadataPlugin.auth.isAuthenticated()") as bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
await hetu.eval("metadataPlugin.auth.logout()");
|
||||||
|
if (kIsMobile) {
|
||||||
|
WebStorageManager.instance().deleteAllData();
|
||||||
|
CookieManager.instance().deleteAllCookies();
|
||||||
|
}
|
||||||
|
if (kIsDesktop) {
|
||||||
|
await WebviewWindow.clearAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
lib/services/metadata/endpoints/browse.dart
Normal file
0
lib/services/metadata/endpoints/browse.dart
Normal file
0
lib/services/metadata/endpoints/playlist.dart
Normal file
0
lib/services/metadata/endpoints/playlist.dart
Normal file
0
lib/services/metadata/endpoints/search.dart
Normal file
0
lib/services/metadata/endpoints/search.dart
Normal file
0
lib/services/metadata/endpoints/track.dart
Normal file
0
lib/services/metadata/endpoints/track.dart
Normal file
15
lib/services/metadata/endpoints/user.dart
Normal file
15
lib/services/metadata/endpoints/user.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:hetu_script/hetu_script.dart';
|
||||||
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
|
|
||||||
|
class MetadataPluginUserEndpoint {
|
||||||
|
final Hetu hetu;
|
||||||
|
MetadataPluginUserEndpoint(this.hetu);
|
||||||
|
|
||||||
|
Future<SpotubeUserObject> me() async {
|
||||||
|
final raw = await hetu.eval("metadataPlugin.user.me()") as Map;
|
||||||
|
|
||||||
|
return SpotubeUserObject.fromJson(
|
||||||
|
raw.cast<String, dynamic>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,564 +1,81 @@
|
|||||||
import 'dart:async';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter_js/extensions/fetch.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter_js/extensions/xhr.dart';
|
import 'package:hetu_otp_util/hetu_otp_util.dart';
|
||||||
import 'package:flutter_js/flutter_js.dart';
|
import 'package:hetu_script/hetu_script.dart';
|
||||||
|
import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart';
|
||||||
|
import 'package:hetu_std/hetu_std.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:spotube/collections/routes.dart';
|
||||||
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/models/metadata/metadata.dart';
|
import 'package:spotube/models/metadata/metadata.dart';
|
||||||
import 'package:spotube/services/logger/logger.dart';
|
|
||||||
import 'package:spotube/services/metadata/apis/localstorage.dart';
|
import 'package:spotube/services/metadata/apis/localstorage.dart';
|
||||||
import 'package:spotube/services/metadata/apis/set_interval.dart';
|
import 'package:spotube/services/metadata/endpoints/auth.dart';
|
||||||
import 'package:spotube/services/metadata/apis/totp.dart';
|
import 'package:spotube/services/metadata/endpoints/user.dart';
|
||||||
import 'package:spotube/services/metadata/apis/webview.dart';
|
|
||||||
|
|
||||||
const defaultMetadataLimit = "20";
|
const defaultMetadataLimit = "20";
|
||||||
|
|
||||||
class MetadataSignatureFlags {
|
class MetadataPlugin {
|
||||||
final bool requiresAuth;
|
static Future<MetadataPlugin> create(
|
||||||
|
|
||||||
const MetadataSignatureFlags({
|
|
||||||
this.requiresAuth = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory MetadataSignatureFlags.fromJson(Map<String, dynamic> json) {
|
|
||||||
return MetadataSignatureFlags(
|
|
||||||
requiresAuth: json["requiresAuth"] ?? false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signature for metadata and related methods that will return Spotube native
|
|
||||||
/// objects e.g. SpotubeTrack, SpotubePlaylist, etc.
|
|
||||||
class MetadataApiSignature {
|
|
||||||
final JavascriptRuntime runtime;
|
|
||||||
final PluginLocalStorageApi localStorageApi;
|
|
||||||
final PluginWebViewApi webViewApi;
|
|
||||||
final PluginTotpGenerator totpGenerator;
|
|
||||||
final PluginSetIntervalApi setIntervalApi;
|
|
||||||
late MetadataSignatureFlags _signatureFlags;
|
|
||||||
|
|
||||||
final StreamController<bool> _authenticatedStreamController;
|
|
||||||
|
|
||||||
Stream<bool> get authenticatedStream => _authenticatedStreamController.stream;
|
|
||||||
|
|
||||||
MetadataSignatureFlags get signatureFlags => _signatureFlags;
|
|
||||||
|
|
||||||
MetadataApiSignature._(
|
|
||||||
this.runtime,
|
|
||||||
this.localStorageApi,
|
|
||||||
this.webViewApi,
|
|
||||||
this.totpGenerator,
|
|
||||||
this.setIntervalApi,
|
|
||||||
) : _authenticatedStreamController = StreamController<bool>.broadcast() {
|
|
||||||
runtime.onMessage("authenticatedStatus", (args) {
|
|
||||||
if (args[0] is Map && (args[0] as Map).containsKey("authenticated")) {
|
|
||||||
final authenticated = args[0]["authenticated"] as bool;
|
|
||||||
_authenticatedStreamController.add(authenticated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<MetadataApiSignature> init(
|
|
||||||
String libraryCode,
|
|
||||||
PluginConfiguration config,
|
PluginConfiguration config,
|
||||||
|
Uint8List byteCode,
|
||||||
) async {
|
) async {
|
||||||
final runtime = getJavascriptRuntime(xhr: true).enableXhr();
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
runtime.enableHandlePromises();
|
BuildContext? pageContext;
|
||||||
await runtime.enableFetch();
|
|
||||||
|
|
||||||
Timer.periodic(
|
final hetu = Hetu();
|
||||||
const Duration(milliseconds: 100),
|
hetu.init();
|
||||||
(timer) {
|
|
||||||
runtime.executePendingJob();
|
HetuStdLoader.loadBindings(hetu);
|
||||||
|
HetuSpotubePluginLoader.loadBindings(
|
||||||
|
hetu,
|
||||||
|
localStorageImpl: SharedPreferencesLocalStorage(
|
||||||
|
sharedPreferences,
|
||||||
|
config.slug,
|
||||||
|
),
|
||||||
|
onNavigatorPush: (route) {
|
||||||
|
return rootNavigatorKey.currentContext?.router
|
||||||
|
.pushWidget(Builder(builder: (context) {
|
||||||
|
pageContext = context;
|
||||||
|
return Scaffold(
|
||||||
|
headers: const [
|
||||||
|
TitleBar(
|
||||||
|
automaticallyImplyLeading: true,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
child: route,
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
onNavigatorPop: () {
|
||||||
|
pageContext?.maybePop();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create all the PluginAPIs after library code is evaluated
|
await HetuStdLoader.loadBytecodeFlutter(hetu);
|
||||||
final localStorageApi = PluginLocalStorageApi(
|
await HetuOtpUtilLoader.loadBytecodeFlutter(hetu);
|
||||||
runtime: runtime,
|
await HetuSpotubePluginLoader.loadBytecodeFlutter(hetu);
|
||||||
sharedPreferences: await SharedPreferences.getInstance(),
|
|
||||||
pluginName: config.slug,
|
|
||||||
);
|
|
||||||
|
|
||||||
final webViewApi = PluginWebViewApi(runtime: runtime);
|
hetu.loadBytecode(bytes: byteCode, moduleName: "plugin");
|
||||||
final totpGenerator = PluginTotpGenerator(runtime);
|
hetu.eval("""
|
||||||
final setIntervalApi = PluginSetIntervalApi(runtime);
|
import "module:plugin" as plugin
|
||||||
|
|
||||||
final metadataApi = MetadataApiSignature._(
|
var Plugin = plugin.${config.entryPoint}
|
||||||
runtime,
|
|
||||||
localStorageApi,
|
|
||||||
webViewApi,
|
|
||||||
totpGenerator,
|
|
||||||
setIntervalApi,
|
|
||||||
);
|
|
||||||
|
|
||||||
final res = runtime.evaluate(
|
var metadataPlugin = Plugin()
|
||||||
"""
|
""");
|
||||||
;$libraryCode;
|
|
||||||
const metadataApi = new MetadataApi();
|
|
||||||
""",
|
|
||||||
);
|
|
||||||
metadataApi._signatureFlags = await metadataApi._getSignatureFlags();
|
|
||||||
|
|
||||||
if (res.isError) {
|
return MetadataPlugin._(hetu);
|
||||||
AppLogger.reportError(
|
|
||||||
"Error evaluating code: $libraryCode\n${res.rawResult}",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadataApi;
|
final Hetu hetu;
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
late final MetadataAuthEndpoint auth;
|
||||||
setIntervalApi.dispose();
|
late final MetadataPluginUserEndpoint user;
|
||||||
webViewApi.dispose();
|
|
||||||
runtime.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future invoke(String method, [List? args]) async {
|
MetadataPlugin._(this.hetu) {
|
||||||
final completer = Completer();
|
auth = MetadataAuthEndpoint(hetu);
|
||||||
runtime.onMessage(method, (result) {
|
user = MetadataPluginUserEndpoint(hetu);
|
||||||
if (completer.isCompleted) return;
|
|
||||||
try {
|
|
||||||
if (result is Map && result.containsKey("error")) {
|
|
||||||
completer.completeError(result["error"]);
|
|
||||||
} else {
|
|
||||||
completer.complete(result is String ? jsonDecode(result) : result);
|
|
||||||
}
|
|
||||||
} catch (e, stack) {
|
|
||||||
AppLogger.reportError(
|
|
||||||
"[MetadataApiSignature][invoke] Error in $method: $e",
|
|
||||||
stack,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final code = """
|
|
||||||
$method(...${args != null ? jsonEncode(args) : "[]"})
|
|
||||||
.then((res) => {
|
|
||||||
try {
|
|
||||||
sendMessage("$method", res ? JSON.stringify(res) : "[]");
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to send message in $method.then: ", `\${e.toString()}\n\${e.stack.toString()}`);
|
|
||||||
}
|
|
||||||
}).catch((e) => {
|
|
||||||
try {
|
|
||||||
console.error("Error in $method: ", `\${e.toString()}\n\${e.stack.toString()}`);
|
|
||||||
sendMessage("$method", JSON.stringify({error: `\${e.toString()}\n\${e.stack.toString()}`}));
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to send message in $method.catch: ", `\${e.toString()}\n\${e.stack.toString()}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
""";
|
|
||||||
|
|
||||||
final res = await runtime.evaluateAsync(code);
|
|
||||||
|
|
||||||
if (res.isError) {
|
|
||||||
AppLogger.reportError("Error evaluating code: $code\n${res.rawResult}");
|
|
||||||
completer.completeError("Error evaluating code: $code\n${res.rawResult}");
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MetadataSignatureFlags> _getSignatureFlags() async {
|
|
||||||
final res = await invoke("metadataApi.getSignatureFlags");
|
|
||||||
|
|
||||||
return MetadataSignatureFlags.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Authentication ------
|
|
||||||
|
|
||||||
Future<void> authenticate() async {
|
|
||||||
await invoke("metadataApi.authenticate");
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> isAuthenticated() async {
|
|
||||||
final res = await invoke("metadataApi.isAuthenticated");
|
|
||||||
return res as bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> logout() async {
|
|
||||||
await invoke("metadataApi.logout");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Track ------
|
|
||||||
Future<SpotubeTrackObject> getTrack(String id) async {
|
|
||||||
final result = await invoke("metadataApi.getTrack", [id]);
|
|
||||||
return SpotubeTrackObject.fromJson(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeTrackObject>> listTracks({
|
|
||||||
List<String>? ids,
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final result = await invoke(
|
|
||||||
"metadataApi.listTracks",
|
|
||||||
[
|
|
||||||
ids,
|
|
||||||
limit,
|
|
||||||
cursor,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
|
|
||||||
result,
|
|
||||||
SpotubeTrackObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeTrackObject>> listTracksByAlbum(
|
|
||||||
String albumId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listTracksByAlbum",
|
|
||||||
[albumId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeTrackObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
|
|
||||||
listTopTracksByArtist(
|
|
||||||
String artistId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listTopTracksByArtist",
|
|
||||||
[artistId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeTrackObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
|
|
||||||
listTracksByPlaylist(
|
|
||||||
String playlistId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listTracksByPlaylist",
|
|
||||||
[playlistId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeTrackObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeTrackObject>>
|
|
||||||
listUserSavedTracks(
|
|
||||||
String userId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listUserSavedTracks",
|
|
||||||
[userId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeTrackObject>.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeTrackObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Album ------
|
|
||||||
Future<SpotubeAlbumObject> getAlbum(String id) async {
|
|
||||||
final res = await invoke("metadataApi.getAlbum", [id]);
|
|
||||||
|
|
||||||
return SpotubeAlbumObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeAlbumObject>> listAlbums({
|
|
||||||
List<String>? ids,
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listAlbums",
|
|
||||||
[ids, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject<SpotubeAlbumObject>.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeAlbumObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeAlbumObject>>
|
|
||||||
listAlbumsByArtist(
|
|
||||||
String artistId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listAlbumsByArtist",
|
|
||||||
[artistId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeAlbumObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeAlbumObject>>
|
|
||||||
listUserSavedAlbums(
|
|
||||||
String userId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listUserSavedAlbums",
|
|
||||||
[userId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeAlbumObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Playlist ------
|
|
||||||
Future<SpotubePlaylistObject> getPlaylist(String id) async {
|
|
||||||
final res = await invoke("metadataApi.getPlaylist", [id]);
|
|
||||||
|
|
||||||
return SpotubePlaylistObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubePlaylistObject>>
|
|
||||||
listFeedPlaylists(
|
|
||||||
String feedId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listFeedPlaylists",
|
|
||||||
[feedId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubePlaylistObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubePlaylistObject>>
|
|
||||||
listUserSavedPlaylists(
|
|
||||||
String userId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listUserSavedPlaylists",
|
|
||||||
[userId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubePlaylistObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePlaylistObject> createPlaylist(
|
|
||||||
String userId,
|
|
||||||
String name, {
|
|
||||||
String? description,
|
|
||||||
bool? public,
|
|
||||||
bool? collaborative,
|
|
||||||
String? imageBase64,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.createPlaylist",
|
|
||||||
[
|
|
||||||
userId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
public,
|
|
||||||
collaborative,
|
|
||||||
imageBase64,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePlaylistObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updatePlaylist(
|
|
||||||
String playlistId, {
|
|
||||||
String? name,
|
|
||||||
String? description,
|
|
||||||
bool? public,
|
|
||||||
bool? collaborative,
|
|
||||||
String? imageBase64,
|
|
||||||
}) async {
|
|
||||||
await invoke(
|
|
||||||
"metadataApi.updatePlaylist",
|
|
||||||
[
|
|
||||||
playlistId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
public,
|
|
||||||
collaborative,
|
|
||||||
imageBase64,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deletePlaylist(String userId, String playlistId) async {
|
|
||||||
await unsavePlaylist(userId, playlistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addTracksToPlaylist(
|
|
||||||
String playlistId,
|
|
||||||
List<String> trackIds, {
|
|
||||||
int? position,
|
|
||||||
}) async {
|
|
||||||
await invoke(
|
|
||||||
"metadataApi.addTracksToPlaylist",
|
|
||||||
[
|
|
||||||
playlistId,
|
|
||||||
trackIds,
|
|
||||||
position,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> removeTracksFromPlaylist(
|
|
||||||
String playlistId,
|
|
||||||
List<String> trackIds,
|
|
||||||
) async {
|
|
||||||
await invoke(
|
|
||||||
"metadataApi.removeTracksFromPlaylist",
|
|
||||||
[
|
|
||||||
playlistId,
|
|
||||||
trackIds,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Artist ------
|
|
||||||
Future<SpotubeArtistObject> getArtist(String id) async {
|
|
||||||
final res = await invoke("metadataApi.getArtist", [id]);
|
|
||||||
|
|
||||||
return SpotubeArtistObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeArtistObject>> listArtists({
|
|
||||||
List<String>? ids,
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listArtists",
|
|
||||||
[ids, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeArtistObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeArtistObject>>
|
|
||||||
listUserSavedArtists(
|
|
||||||
String userId, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.listUserSavedArtists",
|
|
||||||
[userId, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeArtistObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Search ------
|
|
||||||
Future<SpotubeSearchResponseObject> search(
|
|
||||||
String query, {
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke(
|
|
||||||
"metadataApi.search",
|
|
||||||
[query, limit, cursor],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SpotubeSearchResponseObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Feed ------
|
|
||||||
Future<SpotubeFeedObject> getFeed(String id) async {
|
|
||||||
final res = await invoke("metadataApi.getFeed", [id]);
|
|
||||||
|
|
||||||
return SpotubeFeedObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SpotubePaginationResponseObject<SpotubeFeedObject>> listFeeds({
|
|
||||||
String limit = defaultMetadataLimit,
|
|
||||||
String? cursor,
|
|
||||||
}) async {
|
|
||||||
final res = await invoke("metadataApi.listFeeds", [limit, cursor]);
|
|
||||||
|
|
||||||
return SpotubePaginationResponseObject.fromJson(
|
|
||||||
res,
|
|
||||||
SpotubeFeedObject.fromJson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- User ------
|
|
||||||
Future<SpotubeUserObject> getMe() async {
|
|
||||||
final res = await invoke("metadataApi.getMe");
|
|
||||||
|
|
||||||
return SpotubeUserObject.fromJson(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> followArtist(String userId, String artistId) async {
|
|
||||||
await invoke("metadataApi.followArtist", [userId, artistId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> unfollowArtist(String userId, String artistId) async {
|
|
||||||
await invoke("metadataApi.unfollowArtist", [userId, artistId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> savePlaylist(String userId, String playlistId) async {
|
|
||||||
await invoke("metadataApi.savePlaylist", [userId, playlistId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> unsavePlaylist(String userId, String playlistId) async {
|
|
||||||
await invoke("metadataApi.unsavePlaylist", [userId, playlistId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveAlbum(String userId, String albumId) async {
|
|
||||||
await invoke("metadataApi.saveAlbum", [userId, albumId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> unsaveAlbum(String userId, String albumId) async {
|
|
||||||
await invoke("metadataApi.unsaveAlbum", [userId, albumId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveTrack(String userId, String trackId) async {
|
|
||||||
await invoke("metadataApi.saveTrack", [userId, trackId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> unsaveTrack(String userId, String trackId) async {
|
|
||||||
await invoke("metadataApi.unsaveTrack", [userId, trackId]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_js/flutter_js_plugin.h>
|
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
#include <flutter_timezone/flutter_timezone_plugin.h>
|
||||||
#include <gtk/gtk_plugin.h>
|
#include <gtk/gtk_plugin.h>
|
||||||
#include <local_notifier/local_notifier_plugin.h>
|
#include <local_notifier/local_notifier_plugin.h>
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
@ -28,12 +28,12 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_js_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterJsPlugin");
|
|
||||||
flutter_js_plugin_register_with_registrar(flutter_js_registrar);
|
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) flutter_timezone_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin");
|
||||||
|
flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||||
gtk_plugin_register_with_registrar(gtk_registrar);
|
gtk_plugin_register_with_registrar(gtk_registrar);
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
desktop_webview_window
|
desktop_webview_window
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_js
|
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
|
flutter_timezone
|
||||||
gtk
|
gtk
|
||||||
local_notifier
|
local_notifier
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
|
@ -14,8 +14,8 @@ import desktop_webview_window
|
|||||||
import device_info_plus
|
import device_info_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_inappwebview_macos
|
import flutter_inappwebview_macos
|
||||||
import flutter_js
|
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
|
import flutter_timezone
|
||||||
import local_notifier
|
import local_notifier
|
||||||
import media_kit_libs_macos_audio
|
import media_kit_libs_macos_audio
|
||||||
import open_file_mac
|
import open_file_mac
|
||||||
@ -40,8 +40,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin"))
|
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
||||||
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
||||||
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
||||||
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
||||||
|
73
pubspec.lock
73
pubspec.lock
@ -540,10 +540,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.7.0"
|
version: "5.8.0+1"
|
||||||
dio_http2_adapter:
|
dio_http2_adapter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -658,6 +658,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
fast_noise:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fast_noise
|
||||||
|
sha256: "271031cebf1602fc064472970e658fa7ff2f3b55a979bb430337b715bd55690b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -909,14 +917,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.0"
|
||||||
flutter_js:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_js
|
|
||||||
sha256: "6b777cd4e468546f046a2f114d078a4596143269f6fa6bad5c29611d5b896369"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.8.2"
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -1048,6 +1048,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_timezone:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_timezone
|
||||||
|
sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
flutter_undraw:
|
flutter_undraw:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1122,6 +1130,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
|
get_it:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_it
|
||||||
|
sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.3"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1170,6 +1186,41 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
hetu_otp_util:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: "7790606751828758769596e809ba6cbb756f9b9a"
|
||||||
|
url: "https://github.com/hetu-community/hetu_otp_util.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0"
|
||||||
|
hetu_script:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hetu_script
|
||||||
|
sha256: a9a3f4f510ae10188d352b79e8fc45e27134cfa81ee1691004067395f45f17cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2+1"
|
||||||
|
hetu_spotube_plugin:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: "1aa924281d2dbe09aab27d8c2de1cffc853b0d16"
|
||||||
|
url: "https://github.com/KRTirtho/hetu_spotube_plugin.git"
|
||||||
|
source: git
|
||||||
|
version: "0.0.1"
|
||||||
|
hetu_std:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: d3720be2a92022f7b95a3082d40322d8458c70da
|
||||||
|
url: "https://github.com/hetu-community/hetu_std.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0"
|
||||||
home_widget:
|
home_widget:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -2783,5 +2834,5 @@ packages:
|
|||||||
source: git
|
source: git
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0-0 <4.0.0"
|
dart: ">=3.7.2 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.29.0"
|
||||||
|
22
pubspec.yaml
22
pubspec.yaml
@ -142,8 +142,21 @@ dependencies:
|
|||||||
collection: any
|
collection: any
|
||||||
otp_util: ^1.0.2
|
otp_util: ^1.0.2
|
||||||
dio_http2_adapter: ^2.6.0
|
dio_http2_adapter: ^2.6.0
|
||||||
flutter_js: ^0.8.2
|
|
||||||
archive: ^4.0.7
|
archive: ^4.0.7
|
||||||
|
hetu_script: ^0.4.2+1
|
||||||
|
hetu_std:
|
||||||
|
git:
|
||||||
|
url: https://github.com/hetu-community/hetu_std.git
|
||||||
|
ref: main
|
||||||
|
hetu_otp_util:
|
||||||
|
git:
|
||||||
|
url: https://github.com/hetu-community/hetu_otp_util.git
|
||||||
|
ref: main
|
||||||
|
hetu_spotube_plugin:
|
||||||
|
git:
|
||||||
|
url: https://github.com/KRTirtho/hetu_spotube_plugin.git
|
||||||
|
ref: main
|
||||||
|
get_it: ^8.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.13
|
build_runner: ^2.4.13
|
||||||
@ -170,9 +183,10 @@ dependency_overrides:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/KRTirtho/Bonsoir.git
|
url: https://github.com/KRTirtho/Bonsoir.git
|
||||||
path: packages/bonsoir_android
|
path: packages/bonsoir_android
|
||||||
web: ^1.1.0
|
|
||||||
meta: 1.16.0
|
meta: 1.16.0
|
||||||
|
web: ^1.1.0
|
||||||
flutter_svg: ^2.0.17
|
flutter_svg: ^2.0.17
|
||||||
|
intl: any
|
||||||
collection: any
|
collection: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
@ -195,6 +209,10 @@ flutter:
|
|||||||
- packages/flutter_undraw/assets/undraw/taken.svg
|
- packages/flutter_undraw/assets/undraw/taken.svg
|
||||||
- packages/flutter_undraw/assets/undraw/empty.svg
|
- packages/flutter_undraw/assets/undraw/empty.svg
|
||||||
- packages/flutter_undraw/assets/undraw/no_data.svg
|
- packages/flutter_undraw/assets/undraw/no_data.svg
|
||||||
|
# hetu script bytecode
|
||||||
|
- packages/hetu_std/assets/bytecode/std.out
|
||||||
|
- packages/hetu_otp_util/assets/bytecode/otp_util.out
|
||||||
|
- packages/hetu_spotube_plugin/assets/bytecode/spotube_plugin.out
|
||||||
fonts:
|
fonts:
|
||||||
- family: RadixIcons
|
- family: RadixIcons
|
||||||
fonts:
|
fonts:
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
#include <desktop_webview_window/desktop_webview_window_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
#include <flutter_js/flutter_js_plugin.h>
|
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
|
#include <flutter_timezone/flutter_timezone_plugin_c_api.h>
|
||||||
#include <local_notifier/local_notifier_plugin.h>
|
#include <local_notifier/local_notifier_plugin.h>
|
||||||
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
|
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
@ -37,10 +37,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
FlutterJsPluginRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("FlutterJsPlugin"));
|
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
|
FlutterTimezonePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi"));
|
||||||
LocalNotifierPluginRegisterWithRegistrar(
|
LocalNotifierPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
|
||||||
MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar(
|
MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar(
|
||||||
|
@ -9,8 +9,8 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
desktop_webview_window
|
desktop_webview_window
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
flutter_inappwebview_windows
|
flutter_inappwebview_windows
|
||||||
flutter_js
|
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
|
flutter_timezone
|
||||||
local_notifier
|
local_notifier
|
||||||
media_kit_libs_windows_audio
|
media_kit_libs_windows_audio
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
|
Loading…
Reference in New Issue
Block a user