feat(metadata-plugin): add local storage api

This commit is contained in:
Kingkor Roy Tirtho 2025-05-02 13:52:07 +06:00
parent 8ac30c0031
commit abe04b28b2
6 changed files with 361 additions and 3 deletions

View File

@ -14,3 +14,5 @@ part 'playlist.dart';
part 'search.dart';
part 'track.dart';
part 'user.dart';
part 'plugin.dart';

View File

@ -2430,3 +2430,241 @@ abstract class _SpotubeUserObject implements SpotubeUserObject {
_$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith =>
throw _privateConstructorUsedError;
}
PluginConfiguration _$PluginConfigurationFromJson(Map<String, dynamic> json) {
return _PluginConfiguration.fromJson(json);
}
/// @nodoc
mixin _$PluginConfiguration {
PluginType get type => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
String get version => throw _privateConstructorUsedError;
String get author => throw _privateConstructorUsedError;
/// Serializes this PluginConfiguration to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of PluginConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PluginConfigurationCopyWith<PluginConfiguration> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PluginConfigurationCopyWith<$Res> {
factory $PluginConfigurationCopyWith(
PluginConfiguration value, $Res Function(PluginConfiguration) then) =
_$PluginConfigurationCopyWithImpl<$Res, PluginConfiguration>;
@useResult
$Res call(
{PluginType type,
String name,
String description,
String version,
String author});
}
/// @nodoc
class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration>
implements $PluginConfigurationCopyWith<$Res> {
_$PluginConfigurationCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PluginConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? name = null,
Object? description = null,
Object? version = null,
Object? author = null,
}) {
return _then(_value.copyWith(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as PluginType,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as String,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$PluginConfigurationImplCopyWith<$Res>
implements $PluginConfigurationCopyWith<$Res> {
factory _$$PluginConfigurationImplCopyWith(_$PluginConfigurationImpl value,
$Res Function(_$PluginConfigurationImpl) then) =
__$$PluginConfigurationImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{PluginType type,
String name,
String description,
String version,
String author});
}
/// @nodoc
class __$$PluginConfigurationImplCopyWithImpl<$Res>
extends _$PluginConfigurationCopyWithImpl<$Res, _$PluginConfigurationImpl>
implements _$$PluginConfigurationImplCopyWith<$Res> {
__$$PluginConfigurationImplCopyWithImpl(_$PluginConfigurationImpl _value,
$Res Function(_$PluginConfigurationImpl) _then)
: super(_value, _then);
/// Create a copy of PluginConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? name = null,
Object? description = null,
Object? version = null,
Object? author = null,
}) {
return _then(_$PluginConfigurationImpl(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as PluginType,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as String,
author: null == author
? _value.author
: author // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PluginConfigurationImpl extends _PluginConfiguration {
_$PluginConfigurationImpl(
{required this.type,
required this.name,
required this.description,
required this.version,
required this.author})
: super._();
factory _$PluginConfigurationImpl.fromJson(Map<String, dynamic> json) =>
_$$PluginConfigurationImplFromJson(json);
@override
final PluginType type;
@override
final String name;
@override
final String description;
@override
final String version;
@override
final String author;
@override
String toString() {
return 'PluginConfiguration(type: $type, name: $name, description: $description, version: $version, author: $author)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PluginConfigurationImpl &&
(identical(other.type, type) || other.type == type) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.version, version) || other.version == version) &&
(identical(other.author, author) || other.author == author));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, type, name, description, version, author);
/// Create a copy of PluginConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith =>
__$$PluginConfigurationImplCopyWithImpl<_$PluginConfigurationImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$PluginConfigurationImplToJson(
this,
);
}
}
abstract class _PluginConfiguration extends PluginConfiguration {
factory _PluginConfiguration(
{required final PluginType type,
required final String name,
required final String description,
required final String version,
required final String author}) = _$PluginConfigurationImpl;
_PluginConfiguration._() : super._();
factory _PluginConfiguration.fromJson(Map<String, dynamic> json) =
_$PluginConfigurationImpl.fromJson;
@override
PluginType get type;
@override
String get name;
@override
String get description;
@override
String get version;
@override
String get author;
/// Create a copy of PluginConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -223,3 +223,26 @@ Map<String, dynamic> _$$SpotubeUserObjectImplToJson(
'externalUrl': instance.externalUrl,
'displayName': instance.displayName,
};
_$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) =>
_$PluginConfigurationImpl(
type: $enumDecode(_$PluginTypeEnumMap, json['type']),
name: json['name'] as String,
description: json['description'] as String,
version: json['version'] as String,
author: json['author'] as String,
);
Map<String, dynamic> _$$PluginConfigurationImplToJson(
_$PluginConfigurationImpl instance) =>
<String, dynamic>{
'type': _$PluginTypeEnumMap[instance.type]!,
'name': instance.name,
'description': instance.description,
'version': instance.version,
'author': instance.author,
};
const _$PluginTypeEnumMap = {
PluginType.metadata: 'metadata',
};

View File

@ -0,0 +1,21 @@
part of 'metadata.dart';
enum PluginType { metadata }
@freezed
class PluginConfiguration with _$PluginConfiguration {
const PluginConfiguration._();
factory PluginConfiguration({
required PluginType type,
required String name,
required String description,
required String version,
required String author,
}) = _PluginConfiguration;
factory PluginConfiguration.fromJson(Map<String, dynamic> json) =>
_$PluginConfigurationFromJson(json);
String get slug => name.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '-');
}

View File

@ -0,0 +1,60 @@
import 'package:flutter_js/flutter_js.dart';
import 'package:shared_preferences/shared_preferences.dart';
class PluginLocalStorageApi {
final JavascriptRuntime runtime;
final SharedPreferences sharedPreferences;
final String pluginName;
PluginLocalStorageApi({
required this.runtime,
required this.sharedPreferences,
required this.pluginName,
}) {
runtime.onMessage("LocalStorage.getItem", (args) {
final key = args[0];
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 {
await sharedPreferences.setString("plugin.$pluginName.$key", value);
}
String? getItem(String key) {
return sharedPreferences.getString("plugin.$pluginName.$key");
}
void removeItem(String key) async {
await sharedPreferences.remove("plugin.$pluginName.$key");
}
void clear() async {
final keys = sharedPreferences.getKeys();
for (String key in keys) {
if (key.startsWith("plugin.$pluginName.")) {
await sharedPreferences.remove(key);
}
}
}
}

View File

@ -4,8 +4,10 @@ import 'dart:convert';
import 'package:flutter_js/extensions/fetch.dart';
import 'package:flutter_js/extensions/xhr.dart';
import 'package:flutter_js/flutter_js.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/metadata/apis/localstorage.dart';
const defaultMetadataLimit = "20";
@ -13,10 +15,12 @@ const defaultMetadataLimit = "20";
/// objects e.g. SpotubeTrack, SpotubePlaylist, etc.
class MetadataApiSignature {
final JavascriptRuntime runtime;
final PluginLocalStorageApi localStorageApi;
MetadataApiSignature._(this.runtime);
MetadataApiSignature._(this.runtime, this.localStorageApi);
static Future<MetadataApiSignature> init(String libraryCode) async {
static Future<MetadataApiSignature> init(
String libraryCode, PluginConfiguration config) async {
final runtime = getJavascriptRuntime(xhr: true).enableXhr();
runtime.enableHandlePromises();
await runtime.enableFetch();
@ -41,7 +45,17 @@ class MetadataApiSignature {
);
}
return MetadataApiSignature._(runtime);
// Create all the PluginAPIs after library code is evaluated
final localStorageApi = PluginLocalStorageApi(
runtime: runtime,
sharedPreferences: await SharedPreferences.getInstance(),
pluginName: config.slug,
);
return MetadataApiSignature._(
runtime,
localStorageApi,
);
}
void dispose() {