mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
test: add get_it and turn ever KVStore into a singleton service
This commit is contained in:
parent
3b25b227d6
commit
472f379f3d
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -29,5 +29,6 @@
|
||||
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
|
||||
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
|
||||
},
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.29.2"
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.29.2",
|
||||
"makefile.configureOnOpen": false
|
||||
}
|
@ -30,7 +30,7 @@ class AppRouter extends RootStackRouter {
|
||||
(resolver, router) async {
|
||||
final auth = await ref.read(authenticationProvider.future);
|
||||
|
||||
if (auth == null && !KVStoreService.doneGettingStarted) {
|
||||
if (auth == null && !KVStoreService().doneGettingStarted) {
|
||||
resolver.redirect(const GettingStartedRoute());
|
||||
} else {
|
||||
resolver.next(true);
|
||||
|
3
lib/collections/vars.dart
Normal file
3
lib/collections/vars.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
@ -21,7 +21,7 @@ void useCheckYtDlpInstalled(WidgetRef ref) {
|
||||
);
|
||||
|
||||
final customPath =
|
||||
KVStoreService.getYoutubeEnginePath(YoutubeClientEngine.ytDlp);
|
||||
KVStoreService().getYoutubeEnginePath(YoutubeClientEngine.ytDlp);
|
||||
|
||||
if (youtubeEngine == YoutubeClientEngine.ytDlp &&
|
||||
!await YtDlpEngine.isInstalled() &&
|
||||
|
@ -6,7 +6,7 @@ import 'package:spotube/utils/platform.dart';
|
||||
|
||||
void useDisableBatteryOptimizations() {
|
||||
useAsyncEffect(() async {
|
||||
if (!kIsAndroid || KVStoreService.askedForBatteryOptimization) return;
|
||||
if (!kIsAndroid || KVStoreService().askedForBatteryOptimization) return;
|
||||
|
||||
await DisableBatteryOptimization.showDisableBatteryOptimizationSettings();
|
||||
|
||||
@ -16,6 +16,6 @@ void useDisableBatteryOptimizations() {
|
||||
"Follow the steps and disable the optimizations to allow smooth functioning of this app",
|
||||
);
|
||||
|
||||
await KVStoreService.setAskedForBatteryOptimization(true);
|
||||
await KVStoreService().setAskedForBatteryOptimization(true);
|
||||
}, null, []);
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:local_notifier/local_notifier.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:metadata_god/metadata_god.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:smtc_windows/smtc_windows.dart';
|
||||
import 'package:spotube/collections/env.dart';
|
||||
import 'package:spotube/collections/initializers.dart';
|
||||
@ -65,6 +67,14 @@ Future<void> main(List<String> rawArgs) async {
|
||||
AppLogger.runZoned(() async {
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
GetIt.I.registerSingleton<SharedPreferences>(
|
||||
await SharedPreferences.getInstance(),
|
||||
);
|
||||
GetIt.I.registerSingletonWithDependencies<KVStoreService>(
|
||||
() => KVStoreService.init(),
|
||||
dependsOn: [SharedPreferences],
|
||||
);
|
||||
|
||||
await registerWindowsScheme("spotify");
|
||||
|
||||
tz.initializeTimeZones();
|
||||
@ -85,13 +95,11 @@ Future<void> main(List<String> rawArgs) async {
|
||||
MetadataGod.initialize();
|
||||
}
|
||||
|
||||
await KVStoreService.initialize();
|
||||
|
||||
if (kIsDesktop) {
|
||||
await windowManager.setPreventClose(true);
|
||||
await YtDlp.instance
|
||||
.setBinaryLocation(
|
||||
KVStoreService.getYoutubeEnginePath(YoutubeClientEngine.ytDlp) ??
|
||||
KVStoreService().getYoutubeEnginePath(YoutubeClientEngine.ytDlp) ??
|
||||
"yt-dlp${kIsWindows ? '.exe' : ''}",
|
||||
)
|
||||
.catchError((e, stack) => null);
|
||||
|
@ -16,7 +16,7 @@ class DecryptedText {
|
||||
return DecryptedText(
|
||||
_encrypter!.decrypt(
|
||||
Encrypted.fromBase64(value),
|
||||
iv: KVStoreService.ivKey,
|
||||
iv: KVStoreService().ivKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -27,7 +27,7 @@ class DecryptedText {
|
||||
Key.fromUtf8(EncryptedKvStoreService.encryptionKeySync),
|
||||
),
|
||||
);
|
||||
return _encrypter!.encrypt(value, iv: KVStoreService.ivKey).base64;
|
||||
return _encrypter!.encrypt(value, iv: KVStoreService().ivKey).base64;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ class YouTubeEngineNotInstalledDialog extends HookConsumerWidget {
|
||||
?.invalidate(context.l10n.file_not_found);
|
||||
return;
|
||||
}
|
||||
await KVStoreService.setYoutubeEnginePath(
|
||||
await KVStoreService().setYoutubeEnginePath(
|
||||
engine,
|
||||
controller.text,
|
||||
);
|
||||
|
@ -110,7 +110,7 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
||||
Button.secondary(
|
||||
leading: const Icon(SpotubeIcons.anonymous),
|
||||
onPressed: () async {
|
||||
await KVStoreService.setDoneGettingStarted(true);
|
||||
await KVStoreService().setDoneGettingStarted(true);
|
||||
if (context.mounted) {
|
||||
context.navigateTo(const HomeRoute());
|
||||
}
|
||||
@ -134,7 +134,7 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
onPressed: () async {
|
||||
await KVStoreService.setDoneGettingStarted(true);
|
||||
await KVStoreService().setDoneGettingStarted(true);
|
||||
await onLogin();
|
||||
},
|
||||
child: Text(
|
||||
|
@ -62,10 +62,10 @@ class SearchPage extends HookConsumerWidget {
|
||||
if (value.trim().isEmpty) {
|
||||
return;
|
||||
}
|
||||
KVStoreService.setRecentSearches(
|
||||
KVStoreService().setRecentSearches(
|
||||
{
|
||||
value,
|
||||
...KVStoreService.recentSearches,
|
||||
...KVStoreService().recentSearches,
|
||||
}.toList(),
|
||||
);
|
||||
}
|
||||
@ -96,8 +96,9 @@ class SearchPage extends HookConsumerWidget {
|
||||
listenable: controller,
|
||||
builder: (context, _) {
|
||||
final suggestions = controller.text.isEmpty
|
||||
? KVStoreService.recentSearches
|
||||
: KVStoreService.recentSearches
|
||||
? KVStoreService().recentSearches
|
||||
: KVStoreService()
|
||||
.recentSearches
|
||||
.where(
|
||||
(s) =>
|
||||
weightedRatio(
|
||||
|
@ -410,7 +410,8 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
if (value == YoutubeClientEngine.ytDlp) {
|
||||
final customPath = KVStoreService.getYoutubeEnginePath(value);
|
||||
final customPath =
|
||||
KVStoreService().getYoutubeEnginePath(value);
|
||||
if (!await YtDlpEngine.isInstalled() &&
|
||||
(customPath == null ||
|
||||
!await File(customPath).exists()) &&
|
||||
|
@ -8,6 +8,8 @@ import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/provider/user_preferences/default_download_dir_provider.dart';
|
||||
import 'package:spotube/provider/window_manager/window_manager.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
|
@ -5,20 +5,25 @@ import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
|
||||
class VolumeProvider extends Notifier<double> {
|
||||
VolumeProvider();
|
||||
final SpotubeAudioPlayer _audioPlayer;
|
||||
|
||||
VolumeProvider({
|
||||
required SpotubeAudioPlayer audioPlayer,
|
||||
}) : _audioPlayer = audioPlayer;
|
||||
|
||||
@override
|
||||
build() {
|
||||
audioPlayer.setVolume(KVStoreService.volume);
|
||||
return KVStoreService.volume;
|
||||
_audioPlayer.setVolume(KVStoreService().volume);
|
||||
return KVStoreService().volume;
|
||||
}
|
||||
|
||||
Future<void> setVolume(double volume) async {
|
||||
state = volume;
|
||||
await audioPlayer.setVolume(volume);
|
||||
KVStoreService.setVolume(volume);
|
||||
await _audioPlayer.setVolume(volume);
|
||||
KVStoreService().setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
final volumeProvider =
|
||||
NotifierProvider<VolumeProvider, double>(() => VolumeProvider());
|
||||
final volumeProvider = NotifierProvider<VolumeProvider, double>(() {
|
||||
return VolumeProvider(audioPlayer: audioPlayer);
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ abstract class EncryptedKvStoreService {
|
||||
|
||||
static Future<String> get encryptionKey async {
|
||||
if (isUnsupportedPlatform) {
|
||||
return KVStoreService.encryptionKey;
|
||||
return KVStoreService().encryptionKey;
|
||||
}
|
||||
try {
|
||||
final value = await _storage.read(key: 'encryption');
|
||||
@ -38,20 +38,20 @@ abstract class EncryptedKvStoreService {
|
||||
|
||||
return value;
|
||||
} catch (e) {
|
||||
return KVStoreService.encryptionKey;
|
||||
return KVStoreService().encryptionKey;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> setEncryptionKey(String key) async {
|
||||
if (isUnsupportedPlatform) {
|
||||
await KVStoreService.setEncryptionKey(key);
|
||||
await KVStoreService().setEncryptionKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _storage.write(key: 'encryption', value: key);
|
||||
} catch (e) {
|
||||
await KVStoreService.setEncryptionKey(key);
|
||||
await KVStoreService().setEncryptionKey(key);
|
||||
} finally {
|
||||
_encryptionKeySync = key;
|
||||
}
|
||||
|
@ -2,35 +2,37 @@ import 'dart:convert';
|
||||
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotube/collections/vars.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
abstract class KVStoreService {
|
||||
static SharedPreferences? _sharedPreferences;
|
||||
static SharedPreferences get sharedPreferences => _sharedPreferences!;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
_sharedPreferences = await SharedPreferences.getInstance();
|
||||
final class KVStoreService {
|
||||
factory KVStoreService() {
|
||||
return getIt<KVStoreService>();
|
||||
}
|
||||
|
||||
static bool get doneGettingStarted =>
|
||||
KVStoreService.init();
|
||||
|
||||
SharedPreferences get sharedPreferences => getIt<SharedPreferences>();
|
||||
|
||||
bool get doneGettingStarted =>
|
||||
sharedPreferences.getBool('doneGettingStarted') ?? false;
|
||||
static Future<void> setDoneGettingStarted(bool value) async =>
|
||||
Future<void> setDoneGettingStarted(bool value) async =>
|
||||
await sharedPreferences.setBool('doneGettingStarted', value);
|
||||
|
||||
static bool get askedForBatteryOptimization =>
|
||||
bool get askedForBatteryOptimization =>
|
||||
sharedPreferences.getBool('askedForBatteryOptimization') ?? false;
|
||||
static Future<void> setAskedForBatteryOptimization(bool value) async =>
|
||||
Future<void> setAskedForBatteryOptimization(bool value) async =>
|
||||
await sharedPreferences.setBool('askedForBatteryOptimization', value);
|
||||
|
||||
static List<String> get recentSearches =>
|
||||
List<String> get recentSearches =>
|
||||
sharedPreferences.getStringList('recentSearches') ?? [];
|
||||
|
||||
static Future<void> setRecentSearches(List<String> value) async =>
|
||||
Future<void> setRecentSearches(List<String> value) async =>
|
||||
await sharedPreferences.setStringList('recentSearches', value);
|
||||
|
||||
static WindowSize? get windowSize {
|
||||
WindowSize? get windowSize {
|
||||
final raw = sharedPreferences.getString('windowSize');
|
||||
|
||||
if (raw == null) {
|
||||
@ -39,7 +41,7 @@ abstract class KVStoreService {
|
||||
return WindowSize.fromJson(jsonDecode(raw));
|
||||
}
|
||||
|
||||
static Future<void> setWindowSize(WindowSize value) async =>
|
||||
Future<void> setWindowSize(WindowSize value) async =>
|
||||
await sharedPreferences.setString(
|
||||
'windowSize',
|
||||
jsonEncode(
|
||||
@ -47,7 +49,7 @@ abstract class KVStoreService {
|
||||
),
|
||||
);
|
||||
|
||||
static String get encryptionKey {
|
||||
String get encryptionKey {
|
||||
final value = sharedPreferences.getString('encryption');
|
||||
|
||||
final key = const Uuid().v4();
|
||||
@ -59,11 +61,11 @@ abstract class KVStoreService {
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<void> setEncryptionKey(String key) async {
|
||||
Future<void> setEncryptionKey(String key) async {
|
||||
await sharedPreferences.setString('encryption', key);
|
||||
}
|
||||
|
||||
static IV get ivKey {
|
||||
IV get ivKey {
|
||||
final iv = sharedPreferences.getString('iv');
|
||||
final value = IV.fromSecureRandom(8);
|
||||
|
||||
@ -76,20 +78,20 @@ abstract class KVStoreService {
|
||||
return IV.fromBase64(iv);
|
||||
}
|
||||
|
||||
static Future<void> setIVKey(IV iv) async {
|
||||
Future<void> setIVKey(IV iv) async {
|
||||
await sharedPreferences.setString('iv', iv.base64);
|
||||
}
|
||||
|
||||
static double get volume => sharedPreferences.getDouble('volume') ?? 1.0;
|
||||
static Future<void> setVolume(double value) async =>
|
||||
double get volume => sharedPreferences.getDouble('volume') ?? 1.0;
|
||||
Future<void> setVolume(double value) async =>
|
||||
await sharedPreferences.setDouble('volume', value);
|
||||
|
||||
static bool get hasMigratedToDrift =>
|
||||
bool get hasMigratedToDrift =>
|
||||
sharedPreferences.getBool('hasMigratedToDrift') ?? false;
|
||||
static Future<void> setHasMigratedToDrift(bool value) async =>
|
||||
Future<void> setHasMigratedToDrift(bool value) async =>
|
||||
await sharedPreferences.setBool('hasMigratedToDrift', value);
|
||||
|
||||
static Map<String, dynamic>? get _youtubeEnginePaths {
|
||||
Map<String, dynamic>? get _youtubeEnginePaths {
|
||||
final jsonRaw = sharedPreferences.getString('ytDlpPath');
|
||||
|
||||
if (jsonRaw == null) {
|
||||
@ -99,11 +101,11 @@ abstract class KVStoreService {
|
||||
return jsonDecode(jsonRaw);
|
||||
}
|
||||
|
||||
static String? getYoutubeEnginePath(YoutubeClientEngine engine) {
|
||||
String? getYoutubeEnginePath(YoutubeClientEngine engine) {
|
||||
return _youtubeEnginePaths?[engine.name];
|
||||
}
|
||||
|
||||
static Future<void> setYoutubeEnginePath(
|
||||
Future<void> setYoutubeEnginePath(
|
||||
YoutubeClientEngine engine,
|
||||
String path,
|
||||
) async {
|
||||
|
@ -47,7 +47,7 @@ class WindowManagerTools with WidgetsBindingObserver {
|
||||
center: true,
|
||||
),
|
||||
() async {
|
||||
final savedSize = KVStoreService.windowSize;
|
||||
final savedSize = KVStoreService().windowSize;
|
||||
await windowManager.setResizable(true);
|
||||
if (savedSize?.maximized == true &&
|
||||
!(await windowManager.isMaximized())) {
|
||||
@ -77,7 +77,7 @@ class WindowManagerTools with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
final isMaximized = await windowManager.isMaximized();
|
||||
await KVStoreService.setWindowSize(
|
||||
await KVStoreService().setWindowSize(
|
||||
WindowSize(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
|
11
pubspec.lock
11
pubspec.lock
@ -1106,6 +1106,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1566,7 +1574,7 @@ packages:
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "2.0.0"
|
||||
mocktail:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1575,7 +1583,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "2.0.0"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -142,6 +142,7 @@ dependencies:
|
||||
collection: any
|
||||
otp_util: ^1.0.2
|
||||
dio_http2_adapter: ^2.6.0
|
||||
get_it: ^8.0.3
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
|
40
test/provider/volume_provider_test.dart
Normal file
40
test/provider/volume_provider_test.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/volume_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
|
||||
void main() {
|
||||
late ProviderContainer container;
|
||||
late VolumeProvider volumeProvider;
|
||||
|
||||
setUp(() {
|
||||
container = ProviderContainer();
|
||||
volumeProvider = container.read(volumeProvider.notifier);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
container.dispose();
|
||||
});
|
||||
|
||||
test('initial volume is set from KVStore', () {
|
||||
expect(container.read(volumeProvider), KVStoreService().volume);
|
||||
});
|
||||
|
||||
test('setVolume updates state and KVStore', () async {
|
||||
const testVolume = 0.75;
|
||||
await volumeProvider.setVolume(testVolume);
|
||||
|
||||
expect(container.read(volumeProvider), testVolume);
|
||||
expect(KVStoreService().volume, testVolume);
|
||||
});
|
||||
|
||||
test('setVolume updates audio player', () async {
|
||||
const testVolume = 0.5;
|
||||
await volumeProvider.setVolume(testVolume);
|
||||
|
||||
// Verify that the audio player's volume was set
|
||||
// Note: This assumes audioPlayer.setVolume is properly mocked or can be verified
|
||||
expect(audioPlayer.volume, testVolume);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user