mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-05 17:09:34 +00:00
something
This commit is contained in:
parent
dbf4ce1959
commit
a720c977c2
115 changed files with 8819 additions and 1 deletions
143
lib/api/api_provider.dart
Normal file
143
lib/api/api_provider.dart
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// provider to provide the api instance
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:whispering_pages/db/cache_manager.dart';
|
||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||
|
||||
part 'api_provider.g.dart';
|
||||
|
||||
Uri makeBaseUrl(String address) {
|
||||
if (!address.startsWith('http') && !address.startsWith('https')) {
|
||||
address = 'https://$address';
|
||||
}
|
||||
if (!Uri.parse(address).isAbsolute) {
|
||||
throw ArgumentError.value(address, 'address', 'Invalid address');
|
||||
}
|
||||
return Uri.parse(address);
|
||||
}
|
||||
|
||||
/// get the api instance for the given base url
|
||||
@riverpod
|
||||
AudiobookshelfApi audiobookshelfApi(AudiobookshelfApiRef ref, Uri? baseUrl) {
|
||||
// try to get the base url from app settings
|
||||
final apiSettings = ref.watch(apiSettingsProvider);
|
||||
baseUrl ??= apiSettings.activeServer?.serverUrl;
|
||||
if (baseUrl == null) {
|
||||
throw ArgumentError.notNull('baseUrl');
|
||||
}
|
||||
return AudiobookshelfApi(
|
||||
baseUrl: baseUrl,
|
||||
);
|
||||
}
|
||||
|
||||
/// get the api instance for the authenticated user
|
||||
///
|
||||
/// if the user is not authenticated throw an error
|
||||
@riverpod
|
||||
AudiobookshelfApi authenticatedApi(AuthenticatedApiRef ref) {
|
||||
final apiSettings = ref.watch(apiSettingsProvider);
|
||||
final user = apiSettings.activeUser;
|
||||
if (user == null) {
|
||||
throw StateError('No active user');
|
||||
}
|
||||
return AudiobookshelfApi(
|
||||
baseUrl: Uri.https(user.server.serverUrl.toString()),
|
||||
token: user.authToken,
|
||||
);
|
||||
}
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
@riverpod
|
||||
FutureOr<bool> isServerAlive(IsServerAliveRef ref, String address) async {
|
||||
// return (await ref.watch(audiobookshelfApiProvider).server.ping()) ?? false;
|
||||
// if address not starts with http or https, add https
|
||||
|
||||
// !remove this line
|
||||
// return true;
|
||||
|
||||
if (address.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
if (!address.startsWith('http') && !address.startsWith('https')) {
|
||||
address = 'https://$address';
|
||||
}
|
||||
|
||||
// check url is valid
|
||||
if (!Uri.parse(address).isAbsolute) {
|
||||
return false;
|
||||
}
|
||||
return await AudiobookshelfApi(baseUrl: Uri.parse(address)).server.ping() ??
|
||||
false;
|
||||
}
|
||||
|
||||
/// fetch the personalized view
|
||||
@riverpod
|
||||
class PersonalizedView extends _$PersonalizedView {
|
||||
@override
|
||||
Stream<List<Shelf>> build() async* {
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
final apiSettings = ref.watch(apiSettingsProvider);
|
||||
final user = apiSettings.activeUser;
|
||||
if (apiSettings.activeLibraryId == null) {
|
||||
// set it to default user library by logging in and getting the library id
|
||||
final login =
|
||||
await api.login(username: user!.username!, password: user.password!);
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(activeLibraryId: login!.userDefaultLibraryId),
|
||||
);
|
||||
}
|
||||
// try to find in cache
|
||||
// final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}';
|
||||
var key = 'personalizedView:${apiSettings.activeLibraryId! + user!.id!}';
|
||||
final cachedRes = await apiResponseCacheManager.getFileFromCache(
|
||||
key,
|
||||
);
|
||||
if (cachedRes != null) {
|
||||
final resJson = jsonDecode(await cachedRes.file.readAsString()) as List;
|
||||
final res = [
|
||||
for (final item in resJson)
|
||||
Shelf.fromJson(item as Map<String, dynamic>),
|
||||
];
|
||||
debugPrint('reading from cache: $cachedRes');
|
||||
yield res;
|
||||
}
|
||||
|
||||
// ! exagerated delay
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
final res = await api.libraries
|
||||
.getPersonalized(libraryId: apiSettings.activeLibraryId!);
|
||||
// debugPrint('personalizedView: ${res!.map((e) => e).toSet()}');
|
||||
// save to cache
|
||||
final newFile = await apiResponseCacheManager.putFile(
|
||||
key,
|
||||
utf8.encode(jsonEncode(res)),
|
||||
fileExtension: 'json',
|
||||
key: key,
|
||||
);
|
||||
debugPrint('writing to cache: $newFile');
|
||||
yield res!;
|
||||
}
|
||||
|
||||
// method to force refresh the view and ignore the cache
|
||||
Future<void> forceRefresh() async {
|
||||
// clear the cache
|
||||
return apiResponseCacheManager.emptyCache();
|
||||
}
|
||||
}
|
||||
|
||||
/// fetch continue listening audiobooks
|
||||
@riverpod
|
||||
FutureOr<GetUserSessionsResponse> fetchContinueListening(
|
||||
FetchContinueListeningRef ref,
|
||||
) async {
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
final res = await api.me.getSessions();
|
||||
// debugPrint(
|
||||
// 'fetchContinueListening: ${res.sessions.map((e) => e.libraryItemId).toSet()}',
|
||||
// );
|
||||
return res!;
|
||||
}
|
||||
370
lib/api/api_provider.g.dart
Normal file
370
lib/api/api_provider.g.dart
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'api_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$audiobookshelfApiHash() => r'5eb091c6b18c0bf5a0eec079fdb872a84c4f00d9';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
@ProviderFor(audiobookshelfApi)
|
||||
const audiobookshelfApiProvider = AudiobookshelfApiFamily();
|
||||
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
class AudiobookshelfApiFamily extends Family<AudiobookshelfApi> {
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
const AudiobookshelfApiFamily();
|
||||
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
AudiobookshelfApiProvider call(
|
||||
Uri? baseUrl,
|
||||
) {
|
||||
return AudiobookshelfApiProvider(
|
||||
baseUrl,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AudiobookshelfApiProvider getProviderOverride(
|
||||
covariant AudiobookshelfApiProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.baseUrl,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'audiobookshelfApiProvider';
|
||||
}
|
||||
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
class AudiobookshelfApiProvider extends AutoDisposeProvider<AudiobookshelfApi> {
|
||||
/// get the api instance for the given base url
|
||||
///
|
||||
/// Copied from [audiobookshelfApi].
|
||||
AudiobookshelfApiProvider(
|
||||
Uri? baseUrl,
|
||||
) : this._internal(
|
||||
(ref) => audiobookshelfApi(
|
||||
ref as AudiobookshelfApiRef,
|
||||
baseUrl,
|
||||
),
|
||||
from: audiobookshelfApiProvider,
|
||||
name: r'audiobookshelfApiProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$audiobookshelfApiHash,
|
||||
dependencies: AudiobookshelfApiFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AudiobookshelfApiFamily._allTransitiveDependencies,
|
||||
baseUrl: baseUrl,
|
||||
);
|
||||
|
||||
AudiobookshelfApiProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.baseUrl,
|
||||
}) : super.internal();
|
||||
|
||||
final Uri? baseUrl;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
AudiobookshelfApi Function(AudiobookshelfApiRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AudiobookshelfApiProvider._internal(
|
||||
(ref) => create(ref as AudiobookshelfApiRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
baseUrl: baseUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<AudiobookshelfApi> createElement() {
|
||||
return _AudiobookshelfApiProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AudiobookshelfApiProvider && other.baseUrl == baseUrl;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, baseUrl.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin AudiobookshelfApiRef on AutoDisposeProviderRef<AudiobookshelfApi> {
|
||||
/// The parameter `baseUrl` of this provider.
|
||||
Uri? get baseUrl;
|
||||
}
|
||||
|
||||
class _AudiobookshelfApiProviderElement
|
||||
extends AutoDisposeProviderElement<AudiobookshelfApi>
|
||||
with AudiobookshelfApiRef {
|
||||
_AudiobookshelfApiProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl;
|
||||
}
|
||||
|
||||
String _$authenticatedApiHash() => r'62213d5d0268eeaa2a16211cd60b1b6f0d19dd40';
|
||||
|
||||
/// get the api instance for the authenticated user
|
||||
///
|
||||
/// if the user is not authenticated throw an error
|
||||
///
|
||||
/// Copied from [authenticatedApi].
|
||||
@ProviderFor(authenticatedApi)
|
||||
final authenticatedApiProvider =
|
||||
AutoDisposeProvider<AudiobookshelfApi>.internal(
|
||||
authenticatedApi,
|
||||
name: r'authenticatedApiProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$authenticatedApiHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef AuthenticatedApiRef = AutoDisposeProviderRef<AudiobookshelfApi>;
|
||||
String _$isServerAliveHash() => r'f839350795fbdeb0ca1d5f0c84a9065cac4dd40a';
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
@ProviderFor(isServerAlive)
|
||||
const isServerAliveProvider = IsServerAliveFamily();
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
class IsServerAliveFamily extends Family<AsyncValue<bool>> {
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
const IsServerAliveFamily();
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
IsServerAliveProvider call(
|
||||
String address,
|
||||
) {
|
||||
return IsServerAliveProvider(
|
||||
address,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
IsServerAliveProvider getProviderOverride(
|
||||
covariant IsServerAliveProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.address,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'isServerAliveProvider';
|
||||
}
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
class IsServerAliveProvider extends AutoDisposeFutureProvider<bool> {
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
/// Copied from [isServerAlive].
|
||||
IsServerAliveProvider(
|
||||
String address,
|
||||
) : this._internal(
|
||||
(ref) => isServerAlive(
|
||||
ref as IsServerAliveRef,
|
||||
address,
|
||||
),
|
||||
from: isServerAliveProvider,
|
||||
name: r'isServerAliveProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$isServerAliveHash,
|
||||
dependencies: IsServerAliveFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
IsServerAliveFamily._allTransitiveDependencies,
|
||||
address: address,
|
||||
);
|
||||
|
||||
IsServerAliveProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.address,
|
||||
}) : super.internal();
|
||||
|
||||
final String address;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<bool> Function(IsServerAliveRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: IsServerAliveProvider._internal(
|
||||
(ref) => create(ref as IsServerAliveRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
address: address,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<bool> createElement() {
|
||||
return _IsServerAliveProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is IsServerAliveProvider && other.address == address;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, address.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin IsServerAliveRef on AutoDisposeFutureProviderRef<bool> {
|
||||
/// The parameter `address` of this provider.
|
||||
String get address;
|
||||
}
|
||||
|
||||
class _IsServerAliveProviderElement
|
||||
extends AutoDisposeFutureProviderElement<bool> with IsServerAliveRef {
|
||||
_IsServerAliveProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get address => (origin as IsServerAliveProvider).address;
|
||||
}
|
||||
|
||||
String _$fetchContinueListeningHash() =>
|
||||
r'f65fe3ac3a31b8ac074330525c5d2cc4b526802d';
|
||||
|
||||
/// fetch continue listening audiobooks
|
||||
///
|
||||
/// Copied from [fetchContinueListening].
|
||||
@ProviderFor(fetchContinueListening)
|
||||
final fetchContinueListeningProvider =
|
||||
AutoDisposeFutureProvider<GetUserSessionsResponse>.internal(
|
||||
fetchContinueListening,
|
||||
name: r'fetchContinueListeningProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchContinueListeningHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchContinueListeningRef
|
||||
= AutoDisposeFutureProviderRef<GetUserSessionsResponse>;
|
||||
String _$personalizedViewHash() => r'52a89c46ce668238ca11b5394fd1d14c910947f5';
|
||||
|
||||
/// fetch the personalized view
|
||||
///
|
||||
/// Copied from [PersonalizedView].
|
||||
@ProviderFor(PersonalizedView)
|
||||
final personalizedViewProvider =
|
||||
AutoDisposeStreamNotifierProvider<PersonalizedView, List<Shelf>>.internal(
|
||||
PersonalizedView.new,
|
||||
name: r'personalizedViewProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$personalizedViewHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$PersonalizedView = AutoDisposeStreamNotifier<List<Shelf>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
79
lib/api/authenticated_user_provider.dart
Normal file
79
lib/api/authenticated_user_provider.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/api/server_provider.dart'
|
||||
show audiobookShelfServerProvider;
|
||||
import 'package:whispering_pages/settings/models/audiobookshelf_server.dart';
|
||||
import 'package:whispering_pages/settings/models/authenticated_user.dart' as model;
|
||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||
import 'package:whispering_pages/db/storage.dart';
|
||||
|
||||
part 'authenticated_user_provider.g.dart';
|
||||
|
||||
final _box = AvailableHiveBoxes.authenticatedUserBox;
|
||||
|
||||
/// provides with a set of authenticated users
|
||||
@riverpod
|
||||
class AuthenticatedUser extends _$AuthenticatedUser {
|
||||
@override
|
||||
Set<model.AuthenticatedUser> build() {
|
||||
ref.listenSelf((_, __) {
|
||||
writeStateToBox();
|
||||
});
|
||||
// get the app settings
|
||||
final apiSettings = ref.read(apiSettingsProvider);
|
||||
|
||||
final availUsers = readFromBoxOrCreate();
|
||||
if (apiSettings.activeUser != null) {
|
||||
availUsers.add(apiSettings.activeUser!);
|
||||
}
|
||||
return availUsers;
|
||||
}
|
||||
|
||||
Set<model.AuthenticatedUser> readFromBoxOrCreate() {
|
||||
if (_box.isNotEmpty) {
|
||||
final foundData = _box.getRange(0, _box.length);
|
||||
debugPrint('found users in box: $foundData');
|
||||
return foundData.toSet();
|
||||
} else {
|
||||
debugPrint('no settings found in box');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void writeStateToBox() {
|
||||
_box.clear();
|
||||
if (state.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_box.addAll(state);
|
||||
debugPrint('writing state to box: $state');
|
||||
}
|
||||
|
||||
void addUser(model.AuthenticatedUser user) {
|
||||
state = state..add(user);
|
||||
}
|
||||
|
||||
void removeUsersOfServer(AudiobookShelfServer registeredServer) {
|
||||
state = state.where((user) => user.server != registeredServer).toSet();
|
||||
// remove the server from the server provider
|
||||
final serverProvider = ref.read(audiobookShelfServerProvider);
|
||||
if (serverProvider.contains(registeredServer)) {
|
||||
ref
|
||||
.read(audiobookShelfServerProvider.notifier)
|
||||
.removeServer(registeredServer);
|
||||
}
|
||||
}
|
||||
|
||||
void removeUser(model.AuthenticatedUser user) {
|
||||
state = state.where((u) => u != user).toSet();
|
||||
// also remove the user from the active user
|
||||
final apiSettings = ref.read(apiSettingsProvider);
|
||||
if (apiSettings.activeUser == user) {
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeUser: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
lib/api/authenticated_user_provider.g.dart
Normal file
28
lib/api/authenticated_user_provider.g.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'authenticated_user_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$authenticatedUserHash() => r'5702fb6ab1e83129d57c89ef02a65c5910f2a076';
|
||||
|
||||
/// provides with a set of authenticated users
|
||||
///
|
||||
/// Copied from [AuthenticatedUser].
|
||||
@ProviderFor(AuthenticatedUser)
|
||||
final authenticatedUserProvider = AutoDisposeNotifierProvider<AuthenticatedUser,
|
||||
Set<model.AuthenticatedUser>>.internal(
|
||||
AuthenticatedUser.new,
|
||||
name: r'authenticatedUserProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$authenticatedUserHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AuthenticatedUser = AutoDisposeNotifier<Set<model.AuthenticatedUser>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
55
lib/api/image_provider.dart
Normal file
55
lib/api/image_provider.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:whispering_pages/api/api_provider.dart';
|
||||
import 'package:whispering_pages/db/cache_manager.dart';
|
||||
|
||||
/// provides cover images for the audiobooks
|
||||
///
|
||||
/// is a stream provider that provides cover images first from the cache then from the server
|
||||
/// if the image is not found in the cache, it will be fetched from the server and saved to the cache
|
||||
/// if the image is not found in the server it will throw an error
|
||||
|
||||
part 'image_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class CoverImage extends _$CoverImage {
|
||||
@override
|
||||
Stream<Uint8List> build(LibraryItem libraryItem) async* {
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
|
||||
// try to get the image from the cache
|
||||
final file = await imageCacheManager.getFileFromCache(libraryItem.id);
|
||||
|
||||
if (file != null) {
|
||||
// if the image is in the cache, yield it
|
||||
yield await file.file.readAsBytes();
|
||||
// return if no need to fetch from the server
|
||||
if (libraryItem.updatedAt.isBefore(await file.file.lastModified())) {
|
||||
return;
|
||||
} else {
|
||||
debugPrint(
|
||||
'cover image stale for ${libraryItem.id}, fetching from the server',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check if the image is in the cache
|
||||
final coverImage = await api.items.getCover(
|
||||
libraryItemId: libraryItem.id,
|
||||
parameters: const GetImageReqParams(width: 500),
|
||||
);
|
||||
// save the image to the cache
|
||||
final newFile = await imageCacheManager.putFile(
|
||||
libraryItem.id,
|
||||
coverImage ?? Uint8List(0),
|
||||
key: libraryItem.id,
|
||||
);
|
||||
debugPrint(
|
||||
'cover image fetched for for ${libraryItem.id}, file time: ${await newFile.lastModified()}',
|
||||
);
|
||||
yield coverImage ?? Uint8List(0);
|
||||
}
|
||||
}
|
||||
174
lib/api/image_provider.g.dart
Normal file
174
lib/api/image_provider.g.dart
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'image_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$coverImageHash() => r'34c6aaf6831fea198984d22ecdf2c5b74e110891';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$CoverImage
|
||||
extends BuildlessAutoDisposeStreamNotifier<Uint8List> {
|
||||
late final LibraryItem libraryItem;
|
||||
|
||||
Stream<Uint8List> build(
|
||||
LibraryItem libraryItem,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [CoverImage].
|
||||
@ProviderFor(CoverImage)
|
||||
const coverImageProvider = CoverImageFamily();
|
||||
|
||||
/// See also [CoverImage].
|
||||
class CoverImageFamily extends Family<AsyncValue<Uint8List>> {
|
||||
/// See also [CoverImage].
|
||||
const CoverImageFamily();
|
||||
|
||||
/// See also [CoverImage].
|
||||
CoverImageProvider call(
|
||||
LibraryItem libraryItem,
|
||||
) {
|
||||
return CoverImageProvider(
|
||||
libraryItem,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
CoverImageProvider getProviderOverride(
|
||||
covariant CoverImageProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.libraryItem,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'coverImageProvider';
|
||||
}
|
||||
|
||||
/// See also [CoverImage].
|
||||
class CoverImageProvider
|
||||
extends AutoDisposeStreamNotifierProviderImpl<CoverImage, Uint8List> {
|
||||
/// See also [CoverImage].
|
||||
CoverImageProvider(
|
||||
LibraryItem libraryItem,
|
||||
) : this._internal(
|
||||
() => CoverImage()..libraryItem = libraryItem,
|
||||
from: coverImageProvider,
|
||||
name: r'coverImageProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$coverImageHash,
|
||||
dependencies: CoverImageFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
CoverImageFamily._allTransitiveDependencies,
|
||||
libraryItem: libraryItem,
|
||||
);
|
||||
|
||||
CoverImageProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.libraryItem,
|
||||
}) : super.internal();
|
||||
|
||||
final LibraryItem libraryItem;
|
||||
|
||||
@override
|
||||
Stream<Uint8List> runNotifierBuild(
|
||||
covariant CoverImage notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
libraryItem,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(CoverImage Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: CoverImageProvider._internal(
|
||||
() => create()..libraryItem = libraryItem,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
libraryItem: libraryItem,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamNotifierProviderElement<CoverImage, Uint8List>
|
||||
createElement() {
|
||||
return _CoverImageProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is CoverImageProvider && other.libraryItem == libraryItem;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, libraryItem.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin CoverImageRef on AutoDisposeStreamNotifierProviderRef<Uint8List> {
|
||||
/// The parameter `libraryItem` of this provider.
|
||||
LibraryItem get libraryItem;
|
||||
}
|
||||
|
||||
class _CoverImageProviderElement
|
||||
extends AutoDisposeStreamNotifierProviderElement<CoverImage, Uint8List>
|
||||
with CoverImageRef {
|
||||
_CoverImageProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
LibraryItem get libraryItem => (origin as CoverImageProvider).libraryItem;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
102
lib/api/server_provider.dart
Normal file
102
lib/api/server_provider.dart
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/api/authenticated_user_provider.dart';
|
||||
import 'package:whispering_pages/settings/models/audiobookshelf_server.dart' as model;
|
||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||
import 'package:whispering_pages/db/storage.dart';
|
||||
|
||||
part 'server_provider.g.dart';
|
||||
|
||||
final _box = AvailableHiveBoxes.serverBox;
|
||||
|
||||
class ServerAlreadyExistsException implements Exception {
|
||||
final model.AudiobookShelfServer server;
|
||||
|
||||
ServerAlreadyExistsException(this.server);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Server $server already exists';
|
||||
}
|
||||
}
|
||||
|
||||
/// provides with a set of servers added by the user
|
||||
@riverpod
|
||||
class AudiobookShelfServer extends _$AudiobookShelfServer {
|
||||
@override
|
||||
Set<model.AudiobookShelfServer> build() {
|
||||
ref.listenSelf((_, __) {
|
||||
writeStateToBox();
|
||||
});
|
||||
// get the app settings
|
||||
final apiSettings = ref.read(apiSettingsProvider);
|
||||
// is default server is present, add it to the set
|
||||
final availableServers = readFromBoxOrCreate();
|
||||
if (apiSettings.activeServer != null) {
|
||||
availableServers.add(apiSettings.activeServer!);
|
||||
}
|
||||
// also add server of the user
|
||||
if (apiSettings.activeUser != null) {
|
||||
availableServers.add(apiSettings.activeUser!.server);
|
||||
}
|
||||
return availableServers;
|
||||
}
|
||||
|
||||
Set<model.AudiobookShelfServer> readFromBoxOrCreate() {
|
||||
if (_box.isNotEmpty) {
|
||||
final foundServers = _box.getRange(0, _box.length);
|
||||
debugPrint('found servers in box: $foundServers');
|
||||
return foundServers.whereNotNull().toSet();
|
||||
} else {
|
||||
debugPrint('no settings found in box');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void writeStateToBox() {
|
||||
_box.clear();
|
||||
if (state.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_box.addAll(state);
|
||||
debugPrint('writing state to box: $state');
|
||||
}
|
||||
|
||||
void addServer(model.AudiobookShelfServer server) {
|
||||
if (state.contains(server)) {
|
||||
throw ServerAlreadyExistsException(server);
|
||||
}
|
||||
state = {...state, server};
|
||||
}
|
||||
|
||||
void removeServer(model.AudiobookShelfServer server,
|
||||
{
|
||||
bool removeUsers = false,
|
||||
}) {
|
||||
state = state.where((s) => s != server).toSet();
|
||||
// remove the server from the active server
|
||||
final apiSettings = ref.read(apiSettingsProvider);
|
||||
if (apiSettings.activeServer == server) {
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeServer: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
// remove the users of this server
|
||||
if (removeUsers) {
|
||||
ref.read(authenticatedUserProvider.notifier).removeUsersOfServer(server);
|
||||
}
|
||||
}
|
||||
|
||||
// ? this doesn't seem to be useful
|
||||
void updateServer(model.AudiobookShelfServer newServer) {
|
||||
state = state
|
||||
.map(
|
||||
(existingServer) =>
|
||||
existingServer == newServer ? newServer : existingServer,
|
||||
)
|
||||
.toSet();
|
||||
}
|
||||
}
|
||||
30
lib/api/server_provider.g.dart
Normal file
30
lib/api/server_provider.g.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'server_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$audiobookShelfServerHash() =>
|
||||
r'f0d645bb42233c59886bc43fdc473897484ceca1';
|
||||
|
||||
/// provides with a set of servers added by the user
|
||||
///
|
||||
/// Copied from [AudiobookShelfServer].
|
||||
@ProviderFor(AudiobookShelfServer)
|
||||
final audiobookShelfServerProvider = AutoDisposeNotifierProvider<
|
||||
AudiobookShelfServer, Set<model.AudiobookShelfServer>>.internal(
|
||||
AudiobookShelfServer.new,
|
||||
name: r'audiobookShelfServerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$audiobookShelfServerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AudiobookShelfServer
|
||||
= AutoDisposeNotifier<Set<model.AudiobookShelfServer>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
Loading…
Add table
Add a link
Reference in a new issue