mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-08-18 08:18:25 +02:00
feat: api token login
This commit is contained in:
parent
880960c745
commit
682631fb8e
17 changed files with 993 additions and 254 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
|
@ -10,6 +11,12 @@ import 'package:vaani/settings/api_settings_provider.dart';
|
|||
|
||||
part 'api_provider.g.dart';
|
||||
|
||||
// TODO: workaround for https://github.com/rrousselGit/riverpod/issues/3718
|
||||
typedef ResponseErrorHandler = void Function(
|
||||
Response response, [
|
||||
Object? error,
|
||||
]);
|
||||
|
||||
final _logger = Logger('api_provider');
|
||||
|
||||
Uri makeBaseUrl(String address) {
|
||||
|
@ -52,25 +59,33 @@ AudiobookshelfApi authenticatedApi(AuthenticatedApiRef ref) {
|
|||
/// 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) {
|
||||
try {
|
||||
return await AudiobookshelfApi(baseUrl: makeBaseUrl(address))
|
||||
.server
|
||||
.ping() ??
|
||||
false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return await AudiobookshelfApi(baseUrl: Uri.parse(address)).server.ping() ??
|
||||
false;
|
||||
}
|
||||
|
||||
/// fetch status of server
|
||||
@riverpod
|
||||
FutureOr<ServerStatusResponse?> serverStatus(
|
||||
ServerStatusRef ref,
|
||||
Uri baseUrl, [
|
||||
ResponseErrorHandler? responseErrorHandler,
|
||||
]) async {
|
||||
_logger.fine('fetching server status: $baseUrl');
|
||||
final api = ref.watch(audiobookshelfApiProvider(baseUrl));
|
||||
final res =
|
||||
await api.server.status(responseErrorHandler: responseErrorHandler);
|
||||
_logger.fine('server status: $res');
|
||||
return res;
|
||||
}
|
||||
|
||||
/// fetch the personalized view
|
||||
|
@ -97,13 +112,18 @@ class PersonalizedView extends _$PersonalizedView {
|
|||
) ??
|
||||
await apiResponseCacheManager.getFileFromCache(key);
|
||||
if (cachedRes != null) {
|
||||
_logger.fine('reading from cache: $cachedRes for key: $key');
|
||||
try {
|
||||
final resJson = jsonDecode(await cachedRes.file.readAsString()) as List;
|
||||
final res = [
|
||||
for (final item in resJson)
|
||||
Shelf.fromJson(item as Map<String, dynamic>),
|
||||
];
|
||||
_logger.fine('reading from cache: $cachedRes');
|
||||
_logger.fine('successfully read from cache key: $key');
|
||||
yield res;
|
||||
} catch (e) {
|
||||
_logger.warning('error reading from cache: $e\n$cachedRes');
|
||||
}
|
||||
}
|
||||
|
||||
// ! exagerated delay
|
||||
|
@ -112,6 +132,7 @@ class PersonalizedView extends _$PersonalizedView {
|
|||
.getPersonalized(libraryId: apiSettings.activeLibraryId!);
|
||||
// debugPrint('personalizedView: ${res!.map((e) => e).toSet()}');
|
||||
// save to cache
|
||||
if (res != null) {
|
||||
final newFile = await apiResponseCacheManager.putFile(
|
||||
key,
|
||||
utf8.encode(jsonEncode(res)),
|
||||
|
@ -119,7 +140,12 @@ class PersonalizedView extends _$PersonalizedView {
|
|||
key: key,
|
||||
);
|
||||
_logger.fine('writing to cache: $newFile');
|
||||
yield res!;
|
||||
yield res;
|
||||
} else {
|
||||
_logger.warning('failed to fetch personalized view');
|
||||
yield [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// method to force refresh the view and ignore the cache
|
||||
|
|
|
@ -187,7 +187,7 @@ final authenticatedApiProvider = Provider<AudiobookshelfApi>.internal(
|
|||
);
|
||||
|
||||
typedef AuthenticatedApiRef = ProviderRef<AudiobookshelfApi>;
|
||||
String _$isServerAliveHash() => r'f839350795fbdeb0ca1d5f0c84a9065cac4dd40a';
|
||||
String _$isServerAliveHash() => r'6ff90b6e0febd2cd4a4d3a5209a59afc778cd3b6';
|
||||
|
||||
/// ping the server to check if it is reachable
|
||||
///
|
||||
|
@ -327,6 +327,166 @@ class _IsServerAliveProviderElement
|
|||
String get address => (origin as IsServerAliveProvider).address;
|
||||
}
|
||||
|
||||
String _$serverStatusHash() => r'2739906a1862d09b098588ebd16749a09032ee99';
|
||||
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
@ProviderFor(serverStatus)
|
||||
const serverStatusProvider = ServerStatusFamily();
|
||||
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
class ServerStatusFamily extends Family<AsyncValue<ServerStatusResponse?>> {
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
const ServerStatusFamily();
|
||||
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
ServerStatusProvider call(
|
||||
Uri baseUrl, [
|
||||
void Function(Response, [Object?])? responseErrorHandler,
|
||||
]) {
|
||||
return ServerStatusProvider(
|
||||
baseUrl,
|
||||
responseErrorHandler,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ServerStatusProvider getProviderOverride(
|
||||
covariant ServerStatusProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.baseUrl,
|
||||
provider.responseErrorHandler,
|
||||
);
|
||||
}
|
||||
|
||||
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'serverStatusProvider';
|
||||
}
|
||||
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
class ServerStatusProvider
|
||||
extends AutoDisposeFutureProvider<ServerStatusResponse?> {
|
||||
/// fetch status of server
|
||||
///
|
||||
/// Copied from [serverStatus].
|
||||
ServerStatusProvider(
|
||||
Uri baseUrl, [
|
||||
void Function(Response, [Object?])? responseErrorHandler,
|
||||
]) : this._internal(
|
||||
(ref) => serverStatus(
|
||||
ref as ServerStatusRef,
|
||||
baseUrl,
|
||||
responseErrorHandler,
|
||||
),
|
||||
from: serverStatusProvider,
|
||||
name: r'serverStatusProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$serverStatusHash,
|
||||
dependencies: ServerStatusFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ServerStatusFamily._allTransitiveDependencies,
|
||||
baseUrl: baseUrl,
|
||||
responseErrorHandler: responseErrorHandler,
|
||||
);
|
||||
|
||||
ServerStatusProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.baseUrl,
|
||||
required this.responseErrorHandler,
|
||||
}) : super.internal();
|
||||
|
||||
final Uri baseUrl;
|
||||
final void Function(Response, [Object?])? responseErrorHandler;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<ServerStatusResponse?> Function(ServerStatusRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ServerStatusProvider._internal(
|
||||
(ref) => create(ref as ServerStatusRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
baseUrl: baseUrl,
|
||||
responseErrorHandler: responseErrorHandler,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<ServerStatusResponse?> createElement() {
|
||||
return _ServerStatusProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ServerStatusProvider &&
|
||||
other.baseUrl == baseUrl &&
|
||||
other.responseErrorHandler == responseErrorHandler;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, baseUrl.hashCode);
|
||||
hash = _SystemHash.combine(hash, responseErrorHandler.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ServerStatusRef on AutoDisposeFutureProviderRef<ServerStatusResponse?> {
|
||||
/// The parameter `baseUrl` of this provider.
|
||||
Uri get baseUrl;
|
||||
|
||||
/// The parameter `responseErrorHandler` of this provider.
|
||||
void Function(Response, [Object?])? get responseErrorHandler;
|
||||
}
|
||||
|
||||
class _ServerStatusProviderElement
|
||||
extends AutoDisposeFutureProviderElement<ServerStatusResponse?>
|
||||
with ServerStatusRef {
|
||||
_ServerStatusProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Uri get baseUrl => (origin as ServerStatusProvider).baseUrl;
|
||||
@override
|
||||
void Function(Response, [Object?])? get responseErrorHandler =>
|
||||
(origin as ServerStatusProvider).responseErrorHandler;
|
||||
}
|
||||
|
||||
String _$fetchContinueListeningHash() =>
|
||||
r'f65fe3ac3a31b8ac074330525c5d2cc4b526802d';
|
||||
|
||||
|
@ -361,7 +521,7 @@ final meProvider = AutoDisposeFutureProvider<User>.internal(
|
|||
);
|
||||
|
||||
typedef MeRef = AutoDisposeFutureProviderRef<User>;
|
||||
String _$personalizedViewHash() => r'dada8d72845ffd516f731f88193941f7ebdd47ed';
|
||||
String _$personalizedViewHash() => r'4c392ece4650bdc36d7195a0ddb8810e8fe4caa9';
|
||||
|
||||
/// fetch the personalized view
|
||||
///
|
||||
|
|
|
@ -52,8 +52,16 @@ class AuthenticatedUser extends _$AuthenticatedUser {
|
|||
_logger.fine('writing state to box: $state');
|
||||
}
|
||||
|
||||
void addUser(model.AuthenticatedUser user) {
|
||||
void addUser(model.AuthenticatedUser user, {bool setActive = false}) {
|
||||
state = state..add(user);
|
||||
if (setActive) {
|
||||
final apiSettings = ref.read(apiSettingsProvider);
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeUser: user,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void removeUsersOfServer(AudiobookShelfServer registeredServer) {
|
||||
|
|
|
@ -6,7 +6,7 @@ part of 'authenticated_user_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$authenticatedUserHash() => r'8578d7fda1755ecacce6853076da4149e4ebe3e7';
|
||||
String _$authenticatedUserHash() => r'308f19b33ae04af6340fb83167fa64aa23400a09';
|
||||
|
||||
/// provides with a set of authenticated users
|
||||
///
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/api/authenticated_user_provider.dart';
|
||||
import 'package:vaani/api/server_provider.dart';
|
||||
import 'package:vaani/features/onboarding/view/user_login.dart';
|
||||
import 'package:vaani/router/router.dart';
|
||||
import 'package:vaani/settings/api_settings_provider.dart';
|
||||
import 'package:vaani/settings/models/models.dart' as model;
|
||||
import 'package:vaani/shared/utils.dart';
|
||||
import 'package:vaani/shared/widgets/add_new_server.dart';
|
||||
|
||||
|
@ -26,81 +21,23 @@ class OnboardingSinglePage extends HookConsumerWidget {
|
|||
);
|
||||
var audiobookshelfUri = makeBaseUrl(serverUriController.text);
|
||||
|
||||
final api = ref.watch(audiobookshelfApiProvider(audiobookshelfUri));
|
||||
final canUserLogin = useState(apiSettings.activeServer != null);
|
||||
|
||||
final isUserLoginAvailable = useState(apiSettings.activeServer != null);
|
||||
|
||||
final usernameController = useTextEditingController();
|
||||
final passwordController = useTextEditingController();
|
||||
|
||||
void addServer() {
|
||||
var newServer = serverUriController.text.isEmpty
|
||||
? null
|
||||
: model.AudiobookShelfServer(
|
||||
serverUrl: Uri.parse(serverUriController.text),
|
||||
);
|
||||
try {
|
||||
// add the server to the list of servers
|
||||
if (newServer != null) {
|
||||
ref.read(audiobookShelfServerProvider.notifier).addServer(
|
||||
newServer,
|
||||
);
|
||||
}
|
||||
// else remove the server from the list of servers
|
||||
else if (apiSettings.activeServer != null) {
|
||||
ref
|
||||
.read(audiobookShelfServerProvider.notifier)
|
||||
.removeServer(apiSettings.activeServer!);
|
||||
}
|
||||
} on ServerAlreadyExistsException catch (e) {
|
||||
newServer = e.server;
|
||||
} finally {
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeServer: newServer,
|
||||
fadeSlideTransitionBuilder(
|
||||
Widget child,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.3),
|
||||
end: const Offset(0, 0),
|
||||
).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Login to the server and save the user
|
||||
Future<void> loginAndSave() async {
|
||||
final username = usernameController.text;
|
||||
final password = passwordController.text;
|
||||
final success = await api.login(username: username, password: password);
|
||||
if (success != null) {
|
||||
// save the server
|
||||
addServer();
|
||||
var authenticatedUser = model.AuthenticatedUser(
|
||||
server: model.AudiobookShelfServer(
|
||||
serverUrl: Uri.parse(serverUriController.text),
|
||||
),
|
||||
id: success.user.id,
|
||||
password: password,
|
||||
username: username,
|
||||
authToken: api.token!,
|
||||
);
|
||||
// add the user to the list of users
|
||||
ref.read(authenticatedUserProvider.notifier).addUser(authenticatedUser);
|
||||
|
||||
// set the active user
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeUser: authenticatedUser,
|
||||
),
|
||||
);
|
||||
|
||||
// redirect to the library page
|
||||
GoRouter.of(context).goNamed(Routes.home.name);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Login failed. Please check your credentials.'),
|
||||
),
|
||||
);
|
||||
// give focus back to the username field
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
|
@ -118,9 +55,20 @@ class OnboardingSinglePage extends HookConsumerWidget {
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Please enter the URL of your AudiobookShelf Server',
|
||||
child: AnimatedSwitcher(
|
||||
duration: 500.ms,
|
||||
transitionBuilder: fadeSlideTransitionBuilder,
|
||||
child: canUserLogin.value
|
||||
? Text(
|
||||
'Server connected, please login',
|
||||
key: const ValueKey('connected'),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
)
|
||||
: Text(
|
||||
'Please enter the URL of your AudiobookShelf Server',
|
||||
key: const ValueKey('not_connected'),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
@ -129,30 +77,16 @@ class OnboardingSinglePage extends HookConsumerWidget {
|
|||
controller: serverUriController,
|
||||
allowEmpty: true,
|
||||
onPressed: () {
|
||||
isUserLoginAvailable.value =
|
||||
serverUriController.text.isNotEmpty;
|
||||
canUserLogin.value = serverUriController.text.isNotEmpty;
|
||||
},
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: 500.ms,
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.3),
|
||||
end: const Offset(0, 0),
|
||||
).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: isUserLoginAvailable.value
|
||||
? UserLogin(
|
||||
passwordController: passwordController,
|
||||
usernameController: usernameController,
|
||||
onPressed: loginAndSave,
|
||||
transitionBuilder: fadeSlideTransitionBuilder,
|
||||
child: canUserLogin.value
|
||||
? UserLoginWidget(
|
||||
server: audiobookshelfUri,
|
||||
)
|
||||
// ).animate().fade(duration: 600.ms).slideY(begin: 0.3, end: 0)
|
||||
: const RedirectToABS().animate().fadeIn().slideY(
|
||||
|
|
|
@ -1,30 +1,343 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/api/authenticated_user_provider.dart';
|
||||
import 'package:vaani/api/server_provider.dart';
|
||||
import 'package:vaani/hacks/fix_autofill_losing_focus.dart';
|
||||
import 'package:vaani/models/error_response.dart';
|
||||
import 'package:vaani/router/router.dart';
|
||||
import 'package:vaani/settings/api_settings_provider.dart';
|
||||
import 'package:vaani/settings/models/models.dart' as model;
|
||||
|
||||
class UserLogin extends HookConsumerWidget {
|
||||
UserLogin({
|
||||
class UserLoginWidget extends HookConsumerWidget {
|
||||
UserLoginWidget({
|
||||
super.key,
|
||||
this.usernameController,
|
||||
this.passwordController,
|
||||
required this.server,
|
||||
});
|
||||
|
||||
final Uri server;
|
||||
final serverStatusError = ErrorResponse();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final serverStatus =
|
||||
ref.watch(serverStatusProvider(server, serverStatusError.storeError));
|
||||
|
||||
final api = ref.watch(audiobookshelfApiProvider(server));
|
||||
|
||||
return serverStatus.when(
|
||||
data: (value) {
|
||||
if (value == null) {
|
||||
// check the error message
|
||||
return Text(serverStatusError.response.body);
|
||||
}
|
||||
// check available authentication methods and return the correct widget
|
||||
return UserLoginMultipleAuth(
|
||||
server: server,
|
||||
localAvailable:
|
||||
value.authMethods?.contains(AuthMethod.local) ?? false,
|
||||
openidAvailable:
|
||||
value.authMethods?.contains(AuthMethod.openid) ?? false,
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
error: (error, _) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Server is not reachable: $error'),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.invalidate(
|
||||
serverStatusProvider(
|
||||
server,
|
||||
serverStatusError.storeError,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Try again'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum AuthMethodChoice {
|
||||
local,
|
||||
openid,
|
||||
authToken,
|
||||
}
|
||||
|
||||
class UserLoginMultipleAuth extends HookConsumerWidget {
|
||||
const UserLoginMultipleAuth({
|
||||
super.key,
|
||||
required this.server,
|
||||
this.localAvailable = false,
|
||||
this.openidAvailable = false,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
TextEditingController? usernameController;
|
||||
TextEditingController? passwordController;
|
||||
final Uri server;
|
||||
final bool localAvailable;
|
||||
final bool openidAvailable;
|
||||
final void Function()? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
usernameController ??= useTextEditingController();
|
||||
passwordController ??= useTextEditingController();
|
||||
// will show choice chips for the available authentication methods
|
||||
// authToken method is always available
|
||||
final methodChoice = useState<AuthMethodChoice>(
|
||||
localAvailable ? AuthMethodChoice.local : AuthMethodChoice.authToken,
|
||||
);
|
||||
|
||||
final apiSettings = ref.watch(apiSettingsProvider);
|
||||
|
||||
model.AudiobookShelfServer addServer() {
|
||||
var newServer = model.AudiobookShelfServer(
|
||||
serverUrl: server,
|
||||
);
|
||||
try {
|
||||
// add the server to the list of servers
|
||||
ref.read(audiobookShelfServerProvider.notifier).addServer(
|
||||
newServer,
|
||||
);
|
||||
} on ServerAlreadyExistsException catch (e) {
|
||||
newServer = e.server;
|
||||
} finally {
|
||||
ref.read(apiSettingsProvider.notifier).updateState(
|
||||
apiSettings.copyWith(
|
||||
activeServer: newServer,
|
||||
),
|
||||
);
|
||||
}
|
||||
return newServer;
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: InactiveFocusScopeObserver(
|
||||
child: AutofillGroup(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 10,
|
||||
runAlignment: WrapAlignment.center,
|
||||
runSpacing: 10,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
// a small label to show the user what to do
|
||||
if (localAvailable)
|
||||
ChoiceChip(
|
||||
label: const Text('Local'),
|
||||
selected: methodChoice.value == AuthMethodChoice.local,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
methodChoice.value = AuthMethodChoice.local;
|
||||
}
|
||||
},
|
||||
),
|
||||
if (openidAvailable)
|
||||
ChoiceChip(
|
||||
label: const Text('OpenID'),
|
||||
selected: methodChoice.value == AuthMethodChoice.openid,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
methodChoice.value = AuthMethodChoice.openid;
|
||||
}
|
||||
},
|
||||
),
|
||||
ChoiceChip(
|
||||
label: const Text('Token'),
|
||||
selected:
|
||||
methodChoice.value == AuthMethodChoice.authToken,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
methodChoice.value = AuthMethodChoice.authToken;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox.square(
|
||||
dimension: 8,
|
||||
),
|
||||
switch (methodChoice.value) {
|
||||
AuthMethodChoice.local => UserLoginWithPassword(
|
||||
server: server,
|
||||
addServer: addServer,
|
||||
),
|
||||
AuthMethodChoice.openid => _UserLoginWithOpenID(
|
||||
server: server,
|
||||
addServer: addServer,
|
||||
),
|
||||
AuthMethodChoice.authToken => UserLoginWithToken(
|
||||
server: server,
|
||||
addServer: addServer,
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserLoginWithOpenID extends HookConsumerWidget {
|
||||
const _UserLoginWithOpenID({
|
||||
super.key,
|
||||
required this.server,
|
||||
required this.addServer,
|
||||
});
|
||||
|
||||
final Uri server;
|
||||
final model.AudiobookShelfServer Function() addServer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// TODO: implement build
|
||||
return const Text('OpenID');
|
||||
}
|
||||
}
|
||||
|
||||
class UserLoginWithToken extends HookConsumerWidget {
|
||||
UserLoginWithToken({
|
||||
super.key,
|
||||
required this.server,
|
||||
required this.addServer,
|
||||
});
|
||||
|
||||
final Uri server;
|
||||
final model.AudiobookShelfServer Function() addServer;
|
||||
final serverErrorResponse = ErrorResponse();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final authTokensController = useTextEditingController();
|
||||
|
||||
final api = ref.watch(audiobookshelfApiProvider(server));
|
||||
Future<void> loginAndSave() async {
|
||||
api.token = authTokensController.text;
|
||||
model.AuthenticatedUser? authenticatedUser;
|
||||
LoginResponse? success;
|
||||
try {
|
||||
success = await api.misc.authorize(
|
||||
responseErrorHandler: serverErrorResponse.storeError,
|
||||
);
|
||||
if (success == null) {
|
||||
throw StateError('No response from server');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Login failed. Got response: ${serverErrorResponse.response.body} (${serverErrorResponse.response.statusCode})',
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'See Error',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Error'),
|
||||
content: Text(e.toString()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
authenticatedUser = model.AuthenticatedUser(
|
||||
server: addServer(),
|
||||
id: success.user.id,
|
||||
username: success.user.username,
|
||||
authToken: api.token!,
|
||||
);
|
||||
|
||||
ref
|
||||
.read(authenticatedUserProvider.notifier)
|
||||
.addUser(authenticatedUser, setActive: true);
|
||||
|
||||
context.goNamed(Routes.home.name);
|
||||
}
|
||||
|
||||
return Form(
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: authTokensController,
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
maxLines: 10,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'API Token',
|
||||
labelStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter an API token';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onFieldSubmitted: (_) async {
|
||||
await loginAndSave();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: loginAndSave,
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// class _UserLoginWithToken extends HookConsumerWidget {
|
||||
|
||||
class UserLoginWithPassword extends HookConsumerWidget {
|
||||
UserLoginWithPassword({
|
||||
super.key,
|
||||
required this.server,
|
||||
required this.addServer,
|
||||
});
|
||||
|
||||
final Uri server;
|
||||
final model.AudiobookShelfServer Function() addServer;
|
||||
final serverErrorResponse = ErrorResponse();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final usernameController = useTextEditingController();
|
||||
final passwordController = useTextEditingController();
|
||||
final isPasswordVisibleAnimationController = useAnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
|
||||
var isPasswordVisible = useState(false);
|
||||
final api = ref.watch(audiobookshelfApiProvider(server));
|
||||
|
||||
// forward animation when the password visibility changes
|
||||
useEffect(
|
||||
|
@ -39,6 +352,61 @@ class UserLogin extends HookConsumerWidget {
|
|||
[isPasswordVisible.value],
|
||||
);
|
||||
|
||||
/// Login to the server and save the user
|
||||
Future<void> loginAndSave() async {
|
||||
final username = usernameController.text;
|
||||
final password = passwordController.text;
|
||||
|
||||
LoginResponse? success;
|
||||
try {
|
||||
success = await api.login(
|
||||
username: username,
|
||||
password: password,
|
||||
responseErrorHandler: serverErrorResponse.storeError,
|
||||
);
|
||||
if (success == null) {
|
||||
throw StateError('No response from server');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Login failed. Got response: ${serverErrorResponse.response.body} (${serverErrorResponse.response.statusCode})',
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'See Error',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Error'),
|
||||
content: Text(e.toString()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
// save the server
|
||||
final authenticatedUser = model.AuthenticatedUser(
|
||||
server: addServer(),
|
||||
id: success.user.id,
|
||||
password: password,
|
||||
username: username,
|
||||
authToken: api.token!,
|
||||
);
|
||||
// add the user to the list of users
|
||||
ref
|
||||
.read(authenticatedUserProvider.notifier)
|
||||
.addUser(authenticatedUser, setActive: true);
|
||||
|
||||
// redirect to the library page
|
||||
GoRouter.of(context).goNamed(Routes.home.name);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: InactiveFocusScopeObserver(
|
||||
child: AutofillGroup(
|
||||
|
@ -69,11 +437,9 @@ class UserLogin extends HookConsumerWidget {
|
|||
autofillHints: const [AutofillHints.password],
|
||||
textInputAction: TextInputAction.done,
|
||||
obscureText: !isPasswordVisible.value,
|
||||
onFieldSubmitted: onPressed != null
|
||||
? (_) {
|
||||
onPressed!();
|
||||
}
|
||||
: null,
|
||||
onFieldSubmitted: (_) {
|
||||
loginAndSave();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
labelStyle: TextStyle(
|
||||
|
@ -109,7 +475,7 @@ class UserLogin extends HookConsumerWidget {
|
|||
),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
onPressed: loginAndSave,
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/api/authenticated_user_provider.dart';
|
||||
import 'package:vaani/api/server_provider.dart';
|
||||
import 'package:vaani/models/error_response.dart';
|
||||
import 'package:vaani/router/router.dart';
|
||||
import 'package:vaani/settings/api_settings_provider.dart';
|
||||
import 'package:vaani/settings/models/models.dart' as model;
|
||||
|
@ -227,7 +228,7 @@ class ServerManagerPage extends HookConsumerWidget {
|
|||
if (formKey.currentState!.validate()) {
|
||||
try {
|
||||
final newServer = model.AudiobookShelfServer(
|
||||
serverUrl: Uri.parse(serverURIController.text),
|
||||
serverUrl: makeBaseUrl(serverURIController.text),
|
||||
);
|
||||
ref
|
||||
.read(audiobookShelfServerProvider.notifier)
|
||||
|
@ -285,12 +286,16 @@ class _AddUserDialog extends HookConsumerWidget {
|
|||
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
final serverErrorResponse = ErrorResponse();
|
||||
|
||||
/// Login to the server and save the user
|
||||
Future<model.AuthenticatedUser?> loginAndSave() async {
|
||||
model.AuthenticatedUser? authenticatedUser;
|
||||
if (isMethodAuth.value) {
|
||||
api.token = authTokensController.text;
|
||||
final success = await api.misc.authorize();
|
||||
final success = await api.misc.authorize(
|
||||
responseErrorHandler: serverErrorResponse.storeError,
|
||||
);
|
||||
if (success != null) {
|
||||
authenticatedUser = model.AuthenticatedUser(
|
||||
server: server,
|
||||
|
@ -302,7 +307,11 @@ class _AddUserDialog extends HookConsumerWidget {
|
|||
} else {
|
||||
final username = usernameController.text;
|
||||
final password = passwordController.text;
|
||||
final success = await api.login(username: username, password: password);
|
||||
final success = await api.login(
|
||||
username: username,
|
||||
password: password,
|
||||
responseErrorHandler: serverErrorResponse.storeError,
|
||||
);
|
||||
if (success != null) {
|
||||
authenticatedUser = model.AuthenticatedUser(
|
||||
server: server,
|
||||
|
@ -317,8 +326,10 @@ class _AddUserDialog extends HookConsumerWidget {
|
|||
ref.read(authenticatedUserProvider.notifier).addUser(authenticatedUser);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Login failed. Please check your credentials.'),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Login failed. Got response: ${serverErrorResponse.response.body} (${serverErrorResponse.response.statusCode})',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -326,7 +337,23 @@ class _AddUserDialog extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Add User'),
|
||||
// title: Text('Add User for ${server.serverUrl}'),
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Add User for ',
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
TextSpan(
|
||||
text: server.serverUrl.toString(),
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
content: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
|
|
21
lib/models/error_response.dart
Normal file
21
lib/models/error_response.dart
Normal file
|
@ -0,0 +1,21 @@
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final _logger = Logger('ErrorResponse');
|
||||
|
||||
class ErrorResponse {
|
||||
String? name;
|
||||
http.Response _response;
|
||||
|
||||
ErrorResponse({
|
||||
this.name,
|
||||
http.Response? response,
|
||||
}) : _response = response ?? http.Response('', 418);
|
||||
|
||||
void storeError(http.Response response, [Object? error]) {
|
||||
_logger.warning('for $name got response: $response');
|
||||
_response = response;
|
||||
}
|
||||
|
||||
http.Response get response => _response;
|
||||
}
|
|
@ -39,6 +39,23 @@ class HomePage extends HookConsumerWidget {
|
|||
body: Container(
|
||||
child: views.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('No shelves to display'),
|
||||
// try again button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.invalidate(personalizedViewProvider);
|
||||
},
|
||||
child: const Text('Try again'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
final shelvesToDisplay = data
|
||||
// .where((element) => !element.id.contains('discover'))
|
||||
.map((shelf) {
|
||||
|
|
|
@ -20,7 +20,9 @@ mixin _$LibraryItemExtras {
|
|||
String get heroTagSuffix => throw _privateConstructorUsedError;
|
||||
Uint8List? get coverImage => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of LibraryItemExtras
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$LibraryItemExtrasCopyWith<LibraryItemExtras> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -44,6 +46,8 @@ class _$LibraryItemExtrasCopyWithImpl<$Res, $Val extends LibraryItemExtras>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of LibraryItemExtras
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -87,6 +91,8 @@ class __$$LibraryItemExtrasImplCopyWithImpl<$Res>
|
|||
$Res Function(_$LibraryItemExtrasImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of LibraryItemExtras
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -146,7 +152,9 @@ class _$LibraryItemExtrasImpl implements _LibraryItemExtras {
|
|||
int get hashCode => Object.hash(runtimeType, book, heroTagSuffix,
|
||||
const DeepCollectionEquality().hash(coverImage));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of LibraryItemExtras
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith =>
|
||||
|
@ -166,8 +174,11 @@ abstract class _LibraryItemExtras implements LibraryItemExtras {
|
|||
String get heroTagSuffix;
|
||||
@override
|
||||
Uint8List? get coverImage;
|
||||
|
||||
/// Create a copy of LibraryItemExtras
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,12 @@ mixin _$ApiSettings {
|
|||
AuthenticatedUser? get activeUser => throw _privateConstructorUsedError;
|
||||
String? get activeLibraryId => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ApiSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ApiSettingsCopyWith<ApiSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -55,6 +59,8 @@ class _$ApiSettingsCopyWithImpl<$Res, $Val extends ApiSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -78,6 +84,8 @@ class _$ApiSettingsCopyWithImpl<$Res, $Val extends ApiSettings>
|
|||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AudiobookShelfServerCopyWith<$Res>? get activeServer {
|
||||
|
@ -90,6 +98,8 @@ class _$ApiSettingsCopyWithImpl<$Res, $Val extends ApiSettings>
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AuthenticatedUserCopyWith<$Res>? get activeUser {
|
||||
|
@ -130,6 +140,8 @@ class __$$ApiSettingsImplCopyWithImpl<$Res>
|
|||
_$ApiSettingsImpl _value, $Res Function(_$ApiSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -188,12 +200,14 @@ class _$ApiSettingsImpl implements _ApiSettings {
|
|||
other.activeLibraryId == activeLibraryId));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, activeServer, activeUser, activeLibraryId);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ApiSettingsImplCopyWith<_$ApiSettingsImpl> get copyWith =>
|
||||
|
@ -222,8 +236,11 @@ abstract class _ApiSettings implements ApiSettings {
|
|||
AuthenticatedUser? get activeUser;
|
||||
@override
|
||||
String? get activeLibraryId;
|
||||
|
||||
/// Create a copy of ApiSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ApiSettingsImplCopyWith<_$ApiSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,12 @@ mixin _$AppSettings {
|
|||
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
|
||||
DownloadSettings get downloadSettings => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AppSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$AppSettingsCopyWith<AppSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -56,6 +60,8 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -79,6 +85,8 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
|||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ThemeSettingsCopyWith<$Res> get themeSettings {
|
||||
|
@ -87,6 +95,8 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$PlayerSettingsCopyWith<$Res> get playerSettings {
|
||||
|
@ -95,6 +105,8 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$DownloadSettingsCopyWith<$Res> get downloadSettings {
|
||||
|
@ -133,6 +145,8 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
|||
_$AppSettingsImpl _value, $Res Function(_$AppSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -196,12 +210,14 @@ class _$AppSettingsImpl implements _AppSettings {
|
|||
other.downloadSettings == downloadSettings));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, themeSettings, playerSettings, downloadSettings);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith =>
|
||||
|
@ -230,8 +246,11 @@ abstract class _AppSettings implements AppSettings {
|
|||
PlayerSettings get playerSettings;
|
||||
@override
|
||||
DownloadSettings get downloadSettings;
|
||||
|
||||
/// Create a copy of AppSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -247,8 +266,12 @@ mixin _$ThemeSettings {
|
|||
bool get useCurrentPlayerThemeThroughoutApp =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ThemeSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of ThemeSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ThemeSettingsCopyWith<ThemeSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -275,6 +298,8 @@ class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ThemeSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -322,6 +347,8 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res>
|
|||
_$ThemeSettingsImpl _value, $Res Function(_$ThemeSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ThemeSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -390,12 +417,14 @@ class _$ThemeSettingsImpl implements _ThemeSettings {
|
|||
useCurrentPlayerThemeThroughoutApp));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isDarkMode,
|
||||
useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of ThemeSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith =>
|
||||
|
@ -424,8 +453,11 @@ abstract class _ThemeSettings implements ThemeSettings {
|
|||
bool get useMaterialThemeOnItemPage;
|
||||
@override
|
||||
bool get useCurrentPlayerThemeThroughoutApp;
|
||||
|
||||
/// Create a copy of ThemeSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -447,8 +479,12 @@ mixin _$PlayerSettings {
|
|||
throw _privateConstructorUsedError;
|
||||
Duration get playbackReportInterval => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this PlayerSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$PlayerSettingsCopyWith<PlayerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -483,6 +519,8 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -526,6 +564,8 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings {
|
||||
|
@ -535,6 +575,8 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings {
|
||||
|
@ -544,6 +586,8 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SleepTimerSettingsCopyWith<$Res> get sleepTimerSettings {
|
||||
|
@ -587,6 +631,8 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
_$PlayerSettingsImpl _value, $Res Function(_$PlayerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -701,7 +747,7 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
other.playbackReportInterval == playbackReportInterval));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
|
@ -713,7 +759,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
sleepTimerSettings,
|
||||
playbackReportInterval);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||
|
@ -755,8 +803,11 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
SleepTimerSettings get sleepTimerSettings;
|
||||
@override
|
||||
Duration get playbackReportInterval;
|
||||
|
||||
/// Create a copy of PlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -771,8 +822,12 @@ mixin _$ExpandedPlayerSettings {
|
|||
bool get showTotalProgress => throw _privateConstructorUsedError;
|
||||
bool get showChapterProgress => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ExpandedPlayerSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of ExpandedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ExpandedPlayerSettingsCopyWith<ExpandedPlayerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -797,6 +852,8 @@ class _$ExpandedPlayerSettingsCopyWithImpl<$Res,
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ExpandedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -838,6 +895,8 @@ class __$$ExpandedPlayerSettingsImplCopyWithImpl<$Res>
|
|||
$Res Function(_$ExpandedPlayerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ExpandedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -889,12 +948,14 @@ class _$ExpandedPlayerSettingsImpl implements _ExpandedPlayerSettings {
|
|||
other.showChapterProgress == showChapterProgress));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, showTotalProgress, showChapterProgress);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of ExpandedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ExpandedPlayerSettingsImplCopyWith<_$ExpandedPlayerSettingsImpl>
|
||||
|
@ -921,8 +982,11 @@ abstract class _ExpandedPlayerSettings implements ExpandedPlayerSettings {
|
|||
bool get showTotalProgress;
|
||||
@override
|
||||
bool get showChapterProgress;
|
||||
|
||||
/// Create a copy of ExpandedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ExpandedPlayerSettingsImplCopyWith<_$ExpandedPlayerSettingsImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -936,8 +1000,12 @@ MinimizedPlayerSettings _$MinimizedPlayerSettingsFromJson(
|
|||
mixin _$MinimizedPlayerSettings {
|
||||
bool get useChapterInfo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this MinimizedPlayerSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of MinimizedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$MinimizedPlayerSettingsCopyWith<MinimizedPlayerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -962,6 +1030,8 @@ class _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of MinimizedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -998,6 +1068,8 @@ class __$$MinimizedPlayerSettingsImplCopyWithImpl<$Res>
|
|||
$Res Function(_$MinimizedPlayerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of MinimizedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -1038,11 +1110,13 @@ class _$MinimizedPlayerSettingsImpl implements _MinimizedPlayerSettings {
|
|||
other.useChapterInfo == useChapterInfo));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, useChapterInfo);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of MinimizedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||
|
@ -1066,8 +1140,11 @@ abstract class _MinimizedPlayerSettings implements MinimizedPlayerSettings {
|
|||
|
||||
@override
|
||||
bool get useChapterInfo;
|
||||
|
||||
/// Create a copy of MinimizedPlayerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$MinimizedPlayerSettingsImplCopyWith<_$MinimizedPlayerSettingsImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -1109,8 +1186,12 @@ mixin _$SleepTimerSettings {
|
|||
Duration get autoTurnOnTime => throw _privateConstructorUsedError;
|
||||
Duration get autoTurnOffTime => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SleepTimerSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of SleepTimerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$SleepTimerSettingsCopyWith<SleepTimerSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -1147,6 +1228,8 @@ class _$SleepTimerSettingsCopyWithImpl<$Res, $Val extends SleepTimerSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of SleepTimerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -1253,6 +1336,8 @@ class __$$SleepTimerSettingsImplCopyWithImpl<$Res>
|
|||
$Res Function(_$SleepTimerSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of SleepTimerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -1456,7 +1541,7 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
|||
other.autoTurnOffTime == autoTurnOffTime));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
|
@ -1474,7 +1559,9 @@ class _$SleepTimerSettingsImpl implements _SleepTimerSettings {
|
|||
autoTurnOnTime,
|
||||
autoTurnOffTime);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of SleepTimerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SleepTimerSettingsImplCopyWith<_$SleepTimerSettingsImpl> get copyWith =>
|
||||
|
@ -1512,10 +1599,10 @@ abstract class _SleepTimerSettings implements SleepTimerSettings {
|
|||
Duration get defaultDuration;
|
||||
@override
|
||||
SleepTimerShakeSenseMode get shakeSenseMode;
|
||||
@override
|
||||
|
||||
/// the duration in which the shake is detected before the end of the timer and after the timer ends
|
||||
/// only used if [shakeSenseMode] is [SleepTimerShakeSenseMode.nearEnds]
|
||||
@override
|
||||
Duration get shakeSenseDuration;
|
||||
@override
|
||||
bool get vibrateWhenReset;
|
||||
|
@ -1525,32 +1612,35 @@ abstract class _SleepTimerSettings implements SleepTimerSettings {
|
|||
bool get fadeOutAudio;
|
||||
@override
|
||||
double get shakeDetectThreshold;
|
||||
@override
|
||||
|
||||
/// if true, the player will automatically rewind the audio when the sleep timer is stopped
|
||||
bool get autoRewindWhenStopped;
|
||||
@override
|
||||
bool get autoRewindWhenStopped;
|
||||
|
||||
/// the key is the duration in minutes
|
||||
Map<int, Duration> get autoRewindDurations;
|
||||
@override
|
||||
Map<int, Duration> get autoRewindDurations;
|
||||
|
||||
/// auto turn on timer settings
|
||||
bool get autoTurnOnTimer;
|
||||
@override
|
||||
bool get autoTurnOnTimer;
|
||||
|
||||
/// always auto turn on timer settings or during specific times
|
||||
bool get alwaysAutoTurnOnTimer;
|
||||
@override
|
||||
bool get alwaysAutoTurnOnTimer;
|
||||
|
||||
/// auto timer settings, only used if [alwaysAutoTurnOnTimer] is false
|
||||
///
|
||||
/// duration is the time from 00:00
|
||||
@override
|
||||
Duration get autoTurnOnTime;
|
||||
@override
|
||||
Duration get autoTurnOffTime;
|
||||
|
||||
/// Create a copy of SleepTimerSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$SleepTimerSettingsImplCopyWith<_$SleepTimerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -1568,8 +1658,12 @@ mixin _$DownloadSettings {
|
|||
int get maxConcurrentByHost => throw _privateConstructorUsedError;
|
||||
int get maxConcurrentByGroup => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this DownloadSettings to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of DownloadSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$DownloadSettingsCopyWith<DownloadSettings> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -1599,6 +1693,8 @@ class _$DownloadSettingsCopyWithImpl<$Res, $Val extends DownloadSettings>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of DownloadSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -1663,6 +1759,8 @@ class __$$DownloadSettingsImplCopyWithImpl<$Res>
|
|||
$Res Function(_$DownloadSettingsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of DownloadSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -1758,12 +1856,14 @@ class _$DownloadSettingsImpl implements _DownloadSettings {
|
|||
other.maxConcurrentByGroup == maxConcurrentByGroup));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, requiresWiFi, retries,
|
||||
allowPause, maxConcurrent, maxConcurrentByHost, maxConcurrentByGroup);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of DownloadSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$DownloadSettingsImplCopyWith<_$DownloadSettingsImpl> get copyWith =>
|
||||
|
@ -1802,8 +1902,11 @@ abstract class _DownloadSettings implements DownloadSettings {
|
|||
int get maxConcurrentByHost;
|
||||
@override
|
||||
int get maxConcurrentByGroup;
|
||||
|
||||
/// Create a copy of DownloadSettings
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$DownloadSettingsImplCopyWith<_$DownloadSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,12 @@ AudiobookShelfServer _$AudiobookShelfServerFromJson(Map<String, dynamic> json) {
|
|||
mixin _$AudiobookShelfServer {
|
||||
Uri get serverUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AudiobookShelfServer to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of AudiobookShelfServer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$AudiobookShelfServerCopyWith<AudiobookShelfServer> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -48,6 +52,8 @@ class _$AudiobookShelfServerCopyWithImpl<$Res,
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of AudiobookShelfServer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -81,6 +87,8 @@ class __$$AudiobookShelfServerImplCopyWithImpl<$Res>
|
|||
$Res Function(_$AudiobookShelfServerImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of AudiobookShelfServer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -120,11 +128,13 @@ class _$AudiobookShelfServerImpl implements _AudiobookShelfServer {
|
|||
other.serverUrl == serverUrl));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, serverUrl);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of AudiobookShelfServer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AudiobookShelfServerImplCopyWith<_$AudiobookShelfServerImpl>
|
||||
|
@ -149,8 +159,11 @@ abstract class _AudiobookShelfServer implements AudiobookShelfServer {
|
|||
|
||||
@override
|
||||
Uri get serverUrl;
|
||||
|
||||
/// Create a copy of AudiobookShelfServer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$AudiobookShelfServerImplCopyWith<_$AudiobookShelfServerImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
@ -26,8 +26,12 @@ mixin _$AuthenticatedUser {
|
|||
String? get username => throw _privateConstructorUsedError;
|
||||
String? get password => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AuthenticatedUser to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$AuthenticatedUserCopyWith<AuthenticatedUser> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
@ -58,6 +62,8 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser>
|
|||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -91,6 +97,8 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser>
|
|||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AudiobookShelfServerCopyWith<$Res> get server {
|
||||
|
@ -127,6 +135,8 @@ class __$$AuthenticatedUserImplCopyWithImpl<$Res>
|
|||
$Res Function(_$AuthenticatedUserImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
|
@ -205,12 +215,14 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser {
|
|||
other.password == password));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, server, authToken, id, username, password);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AuthenticatedUserImplCopyWith<_$AuthenticatedUserImpl> get copyWith =>
|
||||
|
@ -246,8 +258,11 @@ abstract class _AuthenticatedUser implements AuthenticatedUser {
|
|||
String? get username;
|
||||
@override
|
||||
String? get password;
|
||||
|
||||
/// Create a copy of AuthenticatedUser
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$AuthenticatedUserImplCopyWith<_$AuthenticatedUserImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
|
@ -47,39 +47,7 @@ class AddNewServer extends HookConsumerWidget {
|
|||
),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixText: 'https://',
|
||||
prefixIcon: Tooltip(
|
||||
message: newServerURI.text.isEmpty
|
||||
? 'Server Status'
|
||||
: isServerAliveValue
|
||||
? 'Server connected'
|
||||
: 'Cannot connect to server',
|
||||
child: newServerURI.text.isEmpty
|
||||
? Icon(
|
||||
Icons.cloud_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
: isServerAlive.when(
|
||||
data: (value) {
|
||||
return value
|
||||
? Icon(
|
||||
Icons.cloud_done_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
);
|
||||
},
|
||||
loading: () => Transform.scale(
|
||||
scale: 0.5,
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
error: (error, _) => Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
prefixIcon: ServerAliveIcon(server: Uri.parse(newServerURI.text)),
|
||||
|
||||
// add server button
|
||||
suffixIcon: onPressed == null
|
||||
|
@ -105,3 +73,56 @@ class AddNewServer extends HookConsumerWidget {
|
|||
// add to add to existing servers
|
||||
}
|
||||
}
|
||||
|
||||
class ServerAliveIcon extends HookConsumerWidget {
|
||||
const ServerAliveIcon({
|
||||
super.key,
|
||||
required this.server,
|
||||
});
|
||||
|
||||
final Uri server;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isServerAlive = ref.watch(isServerAliveProvider(server.toString()));
|
||||
bool isServerAliveValue = isServerAlive.when(
|
||||
data: (value) => value,
|
||||
loading: () => false,
|
||||
error: (error, _) => false,
|
||||
);
|
||||
|
||||
return Tooltip(
|
||||
message: server.toString().isEmpty
|
||||
? 'Server Status'
|
||||
: isServerAliveValue
|
||||
? 'Server connected'
|
||||
: 'Cannot connect to server',
|
||||
child: server.toString().isEmpty
|
||||
? Icon(
|
||||
Icons.cloud_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
: isServerAlive.when(
|
||||
data: (value) {
|
||||
return value
|
||||
? Icon(
|
||||
Icons.cloud_done_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
);
|
||||
},
|
||||
loading: () => Transform.scale(
|
||||
scale: 0.5,
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
error: (error, _) => Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
38
pubspec.lock
38
pubspec.lock
|
@ -202,10 +202,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819"
|
||||
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
version: "3.4.1"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -218,10 +218,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996"
|
||||
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -410,10 +410,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3"
|
||||
sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.7"
|
||||
version: "8.1.2"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -479,10 +479,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
|
||||
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.21"
|
||||
version: "2.0.22"
|
||||
flutter_riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -561,10 +561,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "48d03a1e4887b00fe622695139246e3c778ac814eeb32421467b56d23fa64034"
|
||||
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.6"
|
||||
version: "14.2.7"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -697,10 +697,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: just_audio
|
||||
sha256: ee50602364ba83fa6308f5512dd560c713ec3e1f2bc75f0db43618f0d82ef71a
|
||||
sha256: d8e8aaf417d33e345299c17f6457f72bd4ba0c549dc34607abb5183a354edc4d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.39"
|
||||
version: "0.9.40"
|
||||
just_audio_background:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -729,10 +729,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: just_audio_web
|
||||
sha256: "0edb481ad4aa1ff38f8c40f1a3576013c3420bf6669b686fe661627d49bc606c"
|
||||
sha256: b163878529d9b028c53a6972fcd58cae2405bcd11cbfcea620b6fb9f151429d6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.11"
|
||||
version: "0.4.12"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -849,10 +849,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
miniplayer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1107,7 +1107,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: de1ca8c4b1ec83ceafa285558244922959fe447a
|
||||
resolved-ref: f5e7db74c2aa367179f5b369407f88848a020c24
|
||||
url: "https://github.com/Dr-Blank/shelfsdk"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
|
@ -1384,10 +1384,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "1.0.0"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -92,7 +92,7 @@ dev_dependencies:
|
|||
sdk: flutter
|
||||
freezed: ^2.5.2
|
||||
json_serializable: ^6.8.0
|
||||
riverpod_generator: ^2.4.0
|
||||
riverpod_generator: ^2.4.2
|
||||
riverpod_lint: ^2.3.10
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue