mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-06 02:59:28 +00:00
refactor: update AuthenticatedUser model to require id and remove password, enhance server URI handling in AddNewServer widget
This commit is contained in:
parent
eda45efbce
commit
fa815ae206
10 changed files with 297 additions and 72 deletions
|
|
@ -7,7 +7,9 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
import 'package:vaani/db/cache_manager.dart';
|
import 'package:vaani/db/cache_manager.dart';
|
||||||
|
import 'package:vaani/models/error_response.dart';
|
||||||
import 'package:vaani/settings/api_settings_provider.dart';
|
import 'package:vaani/settings/api_settings_provider.dart';
|
||||||
|
import 'package:vaani/settings/models/authenticated_user.dart';
|
||||||
import 'package:vaani/shared/extensions/obfuscation.dart';
|
import 'package:vaani/shared/extensions/obfuscation.dart';
|
||||||
|
|
||||||
part 'api_provider.g.dart';
|
part 'api_provider.g.dart';
|
||||||
|
|
@ -49,6 +51,7 @@ AudiobookshelfApi authenticatedApi(AuthenticatedApiRef ref) {
|
||||||
final apiSettings = ref.watch(apiSettingsProvider);
|
final apiSettings = ref.watch(apiSettingsProvider);
|
||||||
final user = apiSettings.activeUser;
|
final user = apiSettings.activeUser;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
_logger.severe('No active user can not provide authenticated api');
|
||||||
throw StateError('No active user');
|
throw StateError('No active user');
|
||||||
}
|
}
|
||||||
return AudiobookshelfApi(
|
return AudiobookshelfApi(
|
||||||
|
|
@ -97,17 +100,26 @@ class PersonalizedView extends _$PersonalizedView {
|
||||||
final api = ref.watch(authenticatedApiProvider);
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
final apiSettings = ref.watch(apiSettingsProvider);
|
final apiSettings = ref.watch(apiSettingsProvider);
|
||||||
final user = apiSettings.activeUser;
|
final user = apiSettings.activeUser;
|
||||||
|
if (user == null) {
|
||||||
|
_logger.warning('no active user');
|
||||||
|
yield [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (apiSettings.activeLibraryId == null) {
|
if (apiSettings.activeLibraryId == null) {
|
||||||
// set it to default user library by logging in and getting the library id
|
// set it to default user library by logging in and getting the library id
|
||||||
final login =
|
final login = await ref.read(loginProvider().future);
|
||||||
await api.login(username: user!.username!, password: user.password!);
|
if (login == null) {
|
||||||
|
_logger.shout('failed to login, not building personalized view');
|
||||||
|
yield [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
ref.read(apiSettingsProvider.notifier).updateState(
|
ref.read(apiSettingsProvider.notifier).updateState(
|
||||||
apiSettings.copyWith(activeLibraryId: login!.userDefaultLibraryId),
|
apiSettings.copyWith(activeLibraryId: login.userDefaultLibraryId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// try to find in cache
|
// try to find in cache
|
||||||
// final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}';
|
// final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}';
|
||||||
var key = 'personalizedView:${apiSettings.activeLibraryId! + user!.id!}';
|
final key = 'personalizedView:${apiSettings.activeLibraryId! + user.id}';
|
||||||
final cachedRes = await apiResponseCacheManager.getFileFromMemory(
|
final cachedRes = await apiResponseCacheManager.getFileFromMemory(
|
||||||
key,
|
key,
|
||||||
) ??
|
) ??
|
||||||
|
|
@ -127,7 +139,7 @@ class PersonalizedView extends _$PersonalizedView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! exagerated delay
|
// ! exaggerated delay
|
||||||
// await Future.delayed(const Duration(seconds: 2));
|
// await Future.delayed(const Duration(seconds: 2));
|
||||||
final res = await api.libraries
|
final res = await api.libraries
|
||||||
.getPersonalized(libraryId: apiSettings.activeLibraryId!);
|
.getPersonalized(libraryId: apiSettings.activeLibraryId!);
|
||||||
|
|
@ -151,6 +163,7 @@ class PersonalizedView extends _$PersonalizedView {
|
||||||
// method to force refresh the view and ignore the cache
|
// method to force refresh the view and ignore the cache
|
||||||
Future<void> forceRefresh() async {
|
Future<void> forceRefresh() async {
|
||||||
// clear the cache
|
// clear the cache
|
||||||
|
// TODO: find a better way to clear the cache for only personalized view key
|
||||||
return apiResponseCacheManager.emptyCache();
|
return apiResponseCacheManager.emptyCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +186,47 @@ FutureOr<User> me(
|
||||||
MeRef ref,
|
MeRef ref,
|
||||||
) async {
|
) async {
|
||||||
final api = ref.watch(authenticatedApiProvider);
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
final res = await api.me.getUser();
|
final errorResponseHandler = ErrorResponseHandler();
|
||||||
return res!;
|
final res = await api.me.getUser(
|
||||||
|
responseErrorHandler: errorResponseHandler.storeError,
|
||||||
|
);
|
||||||
|
if (res == null) {
|
||||||
|
_logger.severe(
|
||||||
|
'me failed, got response: ${errorResponseHandler.response.obfuscate()}',
|
||||||
|
);
|
||||||
|
throw StateError('me failed');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
FutureOr<LoginResponse?> login(
|
||||||
|
LoginRef ref, {
|
||||||
|
AuthenticatedUser? user,
|
||||||
|
}) async {
|
||||||
|
if (user == null) {
|
||||||
|
// try to get the user from settings
|
||||||
|
final apiSettings = ref.watch(apiSettingsProvider);
|
||||||
|
user = apiSettings.activeUser;
|
||||||
|
if (user == null) {
|
||||||
|
_logger.severe('no active user to login');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_logger.fine('no user provided, using active user: ${user.obfuscate()}');
|
||||||
|
}
|
||||||
|
final api = ref.watch(audiobookshelfApiProvider(user.server.serverUrl));
|
||||||
|
api.token = user.authToken;
|
||||||
|
var errorResponseHandler = ErrorResponseHandler();
|
||||||
|
_logger.fine('logging in with authenticated api');
|
||||||
|
final res = await api.misc.authorize(
|
||||||
|
responseErrorHandler: errorResponseHandler.storeError,
|
||||||
|
);
|
||||||
|
if (res == null) {
|
||||||
|
_logger.severe(
|
||||||
|
'login failed, got response: ${errorResponseHandler.response.obfuscate()}',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_logger.fine('login response: ${res.obfuscate()}');
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ class _AudiobookshelfApiProviderElement
|
||||||
Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl;
|
Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$authenticatedApiHash() => r'f555efb6eede590b5a8d60cad2e6bfc2847e2d14';
|
String _$authenticatedApiHash() => r'e662465f01ab1a6384db4738a3ae49b5fab48a4f';
|
||||||
|
|
||||||
/// get the api instance for the authenticated user
|
/// get the api instance for the authenticated user
|
||||||
///
|
///
|
||||||
|
|
@ -507,7 +507,7 @@ final fetchContinueListeningProvider =
|
||||||
|
|
||||||
typedef FetchContinueListeningRef
|
typedef FetchContinueListeningRef
|
||||||
= AutoDisposeFutureProviderRef<GetUserSessionsResponse>;
|
= AutoDisposeFutureProviderRef<GetUserSessionsResponse>;
|
||||||
String _$meHash() => r'bdc664c4fd867ad13018fa769ce7a6913248c44f';
|
String _$meHash() => r'da5f40b8063b0c0a6651fdcc4ac2d192d0dc7df6';
|
||||||
|
|
||||||
/// See also [me].
|
/// See also [me].
|
||||||
@ProviderFor(me)
|
@ProviderFor(me)
|
||||||
|
|
@ -521,7 +521,134 @@ final meProvider = AutoDisposeFutureProvider<User>.internal(
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef MeRef = AutoDisposeFutureProviderRef<User>;
|
typedef MeRef = AutoDisposeFutureProviderRef<User>;
|
||||||
String _$personalizedViewHash() => r'4c392ece4650bdc36d7195a0ddb8810e8fe4caa9';
|
String _$loginHash() => r'eb1c4fcef1818dce994846c1adb8eca8f6ec9259';
|
||||||
|
|
||||||
|
/// See also [login].
|
||||||
|
@ProviderFor(login)
|
||||||
|
const loginProvider = LoginFamily();
|
||||||
|
|
||||||
|
/// See also [login].
|
||||||
|
class LoginFamily extends Family<AsyncValue<LoginResponse?>> {
|
||||||
|
/// See also [login].
|
||||||
|
const LoginFamily();
|
||||||
|
|
||||||
|
/// See also [login].
|
||||||
|
LoginProvider call({
|
||||||
|
AuthenticatedUser? user,
|
||||||
|
}) {
|
||||||
|
return LoginProvider(
|
||||||
|
user: user,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LoginProvider getProviderOverride(
|
||||||
|
covariant LoginProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
user: provider.user,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'loginProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [login].
|
||||||
|
class LoginProvider extends AutoDisposeFutureProvider<LoginResponse?> {
|
||||||
|
/// See also [login].
|
||||||
|
LoginProvider({
|
||||||
|
AuthenticatedUser? user,
|
||||||
|
}) : this._internal(
|
||||||
|
(ref) => login(
|
||||||
|
ref as LoginRef,
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
from: loginProvider,
|
||||||
|
name: r'loginProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$loginHash,
|
||||||
|
dependencies: LoginFamily._dependencies,
|
||||||
|
allTransitiveDependencies: LoginFamily._allTransitiveDependencies,
|
||||||
|
user: user,
|
||||||
|
);
|
||||||
|
|
||||||
|
LoginProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.user,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final AuthenticatedUser? user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<LoginResponse?> Function(LoginRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: LoginProvider._internal(
|
||||||
|
(ref) => create(ref as LoginRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<LoginResponse?> createElement() {
|
||||||
|
return _LoginProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is LoginProvider && other.user == user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, user.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin LoginRef on AutoDisposeFutureProviderRef<LoginResponse?> {
|
||||||
|
/// The parameter `user` of this provider.
|
||||||
|
AuthenticatedUser? get user;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<LoginResponse?> with LoginRef {
|
||||||
|
_LoginProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
AuthenticatedUser? get user => (origin as LoginProvider).user;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$personalizedViewHash() => r'65c0bc60e312d290498ab488496495114d407ccb';
|
||||||
|
|
||||||
/// fetch the personalized view
|
/// fetch the personalized view
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class OnboardingSinglePage extends HookConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final apiSettings = ref.watch(apiSettingsProvider);
|
final apiSettings = ref.watch(apiSettingsProvider);
|
||||||
final serverUriController = useTextEditingController(
|
final serverUriController = useTextEditingController(
|
||||||
text: apiSettings.activeServer?.serverUrl.toString() ?? '',
|
text: apiSettings.activeServer?.serverUrl.toString() ?? 'https://',
|
||||||
);
|
);
|
||||||
var audiobookshelfUri = makeBaseUrl(serverUriController.text);
|
var audiobookshelfUri = makeBaseUrl(serverUriController.text);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@ class UserLoginWithPassword extends HookConsumerWidget {
|
||||||
final authenticatedUser = model.AuthenticatedUser(
|
final authenticatedUser = model.AuthenticatedUser(
|
||||||
server: addServer(),
|
server: addServer(),
|
||||||
id: success.user.id,
|
id: success.user.id,
|
||||||
password: password,
|
|
||||||
username: username,
|
username: username,
|
||||||
authToken: api.token!,
|
authToken: api.token!,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,18 @@ final _logger = Logger('ErrorResponse');
|
||||||
class ErrorResponseHandler {
|
class ErrorResponseHandler {
|
||||||
String? name;
|
String? name;
|
||||||
http.Response _response;
|
http.Response _response;
|
||||||
|
bool logRawResponse;
|
||||||
|
|
||||||
ErrorResponseHandler({
|
ErrorResponseHandler({
|
||||||
this.name,
|
this.name,
|
||||||
http.Response? response,
|
http.Response? response,
|
||||||
|
this.logRawResponse = false,
|
||||||
}) : _response = response ?? http.Response('', 418);
|
}) : _response = response ?? http.Response('', 418);
|
||||||
|
|
||||||
void storeError(http.Response response, [Object? error]) {
|
void storeError(http.Response response, [Object? error]) {
|
||||||
_logger.fine('for $name got response: ${response.obfuscate()}');
|
if (logRawResponse) {
|
||||||
|
_logger.fine('for $name got response: ${response.obfuscate()}');
|
||||||
|
}
|
||||||
_response = response;
|
_response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ class AuthenticatedUser with _$AuthenticatedUser {
|
||||||
const factory AuthenticatedUser({
|
const factory AuthenticatedUser({
|
||||||
required AudiobookShelfServer server,
|
required AudiobookShelfServer server,
|
||||||
required String authToken,
|
required String authToken,
|
||||||
String? id,
|
required String id,
|
||||||
String? username,
|
String? username,
|
||||||
String? password,
|
|
||||||
}) = _AuthenticatedUser;
|
}) = _AuthenticatedUser;
|
||||||
|
|
||||||
factory AuthenticatedUser.fromJson(Map<String, dynamic> json) =>
|
factory AuthenticatedUser.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ AuthenticatedUser _$AuthenticatedUserFromJson(Map<String, dynamic> json) {
|
||||||
mixin _$AuthenticatedUser {
|
mixin _$AuthenticatedUser {
|
||||||
AudiobookShelfServer get server => throw _privateConstructorUsedError;
|
AudiobookShelfServer get server => throw _privateConstructorUsedError;
|
||||||
String get authToken => throw _privateConstructorUsedError;
|
String get authToken => throw _privateConstructorUsedError;
|
||||||
String? get id => throw _privateConstructorUsedError;
|
String get id => throw _privateConstructorUsedError;
|
||||||
String? get username => throw _privateConstructorUsedError;
|
String? get username => throw _privateConstructorUsedError;
|
||||||
String? get password => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Serializes this AuthenticatedUser to a JSON map.
|
/// Serializes this AuthenticatedUser to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
@ -45,9 +44,8 @@ abstract class $AuthenticatedUserCopyWith<$Res> {
|
||||||
$Res call(
|
$Res call(
|
||||||
{AudiobookShelfServer server,
|
{AudiobookShelfServer server,
|
||||||
String authToken,
|
String authToken,
|
||||||
String? id,
|
String id,
|
||||||
String? username,
|
String? username});
|
||||||
String? password});
|
|
||||||
|
|
||||||
$AudiobookShelfServerCopyWith<$Res> get server;
|
$AudiobookShelfServerCopyWith<$Res> get server;
|
||||||
}
|
}
|
||||||
|
|
@ -69,9 +67,8 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser>
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? server = null,
|
Object? server = null,
|
||||||
Object? authToken = null,
|
Object? authToken = null,
|
||||||
Object? id = freezed,
|
Object? id = null,
|
||||||
Object? username = freezed,
|
Object? username = freezed,
|
||||||
Object? password = freezed,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
server: null == server
|
server: null == server
|
||||||
|
|
@ -82,18 +79,14 @@ class _$AuthenticatedUserCopyWithImpl<$Res, $Val extends AuthenticatedUser>
|
||||||
? _value.authToken
|
? _value.authToken
|
||||||
: authToken // ignore: cast_nullable_to_non_nullable
|
: authToken // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
id: freezed == id
|
id: null == id
|
||||||
? _value.id
|
? _value.id
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String,
|
||||||
username: freezed == username
|
username: freezed == username
|
||||||
? _value.username
|
? _value.username
|
||||||
: username // ignore: cast_nullable_to_non_nullable
|
: username // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
password: freezed == password
|
|
||||||
? _value.password
|
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,9 +112,8 @@ abstract class _$$AuthenticatedUserImplCopyWith<$Res>
|
||||||
$Res call(
|
$Res call(
|
||||||
{AudiobookShelfServer server,
|
{AudiobookShelfServer server,
|
||||||
String authToken,
|
String authToken,
|
||||||
String? id,
|
String id,
|
||||||
String? username,
|
String? username});
|
||||||
String? password});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$AudiobookShelfServerCopyWith<$Res> get server;
|
$AudiobookShelfServerCopyWith<$Res> get server;
|
||||||
|
|
@ -142,9 +134,8 @@ class __$$AuthenticatedUserImplCopyWithImpl<$Res>
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? server = null,
|
Object? server = null,
|
||||||
Object? authToken = null,
|
Object? authToken = null,
|
||||||
Object? id = freezed,
|
Object? id = null,
|
||||||
Object? username = freezed,
|
Object? username = freezed,
|
||||||
Object? password = freezed,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$AuthenticatedUserImpl(
|
return _then(_$AuthenticatedUserImpl(
|
||||||
server: null == server
|
server: null == server
|
||||||
|
|
@ -155,18 +146,14 @@ class __$$AuthenticatedUserImplCopyWithImpl<$Res>
|
||||||
? _value.authToken
|
? _value.authToken
|
||||||
: authToken // ignore: cast_nullable_to_non_nullable
|
: authToken // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
id: freezed == id
|
id: null == id
|
||||||
? _value.id
|
? _value.id
|
||||||
: id // ignore: cast_nullable_to_non_nullable
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String,
|
||||||
username: freezed == username
|
username: freezed == username
|
||||||
? _value.username
|
? _value.username
|
||||||
: username // ignore: cast_nullable_to_non_nullable
|
: username // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
password: freezed == password
|
|
||||||
? _value.password
|
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +164,8 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser {
|
||||||
const _$AuthenticatedUserImpl(
|
const _$AuthenticatedUserImpl(
|
||||||
{required this.server,
|
{required this.server,
|
||||||
required this.authToken,
|
required this.authToken,
|
||||||
this.id,
|
required this.id,
|
||||||
this.username,
|
this.username});
|
||||||
this.password});
|
|
||||||
|
|
||||||
factory _$AuthenticatedUserImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$AuthenticatedUserImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$AuthenticatedUserImplFromJson(json);
|
_$$AuthenticatedUserImplFromJson(json);
|
||||||
|
|
@ -189,15 +175,13 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser {
|
||||||
@override
|
@override
|
||||||
final String authToken;
|
final String authToken;
|
||||||
@override
|
@override
|
||||||
final String? id;
|
final String id;
|
||||||
@override
|
@override
|
||||||
final String? username;
|
final String? username;
|
||||||
@override
|
|
||||||
final String? password;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AuthenticatedUser(server: $server, authToken: $authToken, id: $id, username: $username, password: $password)';
|
return 'AuthenticatedUser(server: $server, authToken: $authToken, id: $id, username: $username)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -210,15 +194,12 @@ class _$AuthenticatedUserImpl implements _AuthenticatedUser {
|
||||||
other.authToken == authToken) &&
|
other.authToken == authToken) &&
|
||||||
(identical(other.id, id) || other.id == id) &&
|
(identical(other.id, id) || other.id == id) &&
|
||||||
(identical(other.username, username) ||
|
(identical(other.username, username) ||
|
||||||
other.username == username) &&
|
other.username == username));
|
||||||
(identical(other.password, password) ||
|
|
||||||
other.password == password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(runtimeType, server, authToken, id, username);
|
||||||
Object.hash(runtimeType, server, authToken, id, username, password);
|
|
||||||
|
|
||||||
/// Create a copy of AuthenticatedUser
|
/// Create a copy of AuthenticatedUser
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -241,9 +222,8 @@ abstract class _AuthenticatedUser implements AuthenticatedUser {
|
||||||
const factory _AuthenticatedUser(
|
const factory _AuthenticatedUser(
|
||||||
{required final AudiobookShelfServer server,
|
{required final AudiobookShelfServer server,
|
||||||
required final String authToken,
|
required final String authToken,
|
||||||
final String? id,
|
required final String id,
|
||||||
final String? username,
|
final String? username}) = _$AuthenticatedUserImpl;
|
||||||
final String? password}) = _$AuthenticatedUserImpl;
|
|
||||||
|
|
||||||
factory _AuthenticatedUser.fromJson(Map<String, dynamic> json) =
|
factory _AuthenticatedUser.fromJson(Map<String, dynamic> json) =
|
||||||
_$AuthenticatedUserImpl.fromJson;
|
_$AuthenticatedUserImpl.fromJson;
|
||||||
|
|
@ -253,11 +233,9 @@ abstract class _AuthenticatedUser implements AuthenticatedUser {
|
||||||
@override
|
@override
|
||||||
String get authToken;
|
String get authToken;
|
||||||
@override
|
@override
|
||||||
String? get id;
|
String get id;
|
||||||
@override
|
@override
|
||||||
String? get username;
|
String? get username;
|
||||||
@override
|
|
||||||
String? get password;
|
|
||||||
|
|
||||||
/// Create a copy of AuthenticatedUser
|
/// Create a copy of AuthenticatedUser
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ _$AuthenticatedUserImpl _$$AuthenticatedUserImplFromJson(
|
||||||
server:
|
server:
|
||||||
AudiobookShelfServer.fromJson(json['server'] as Map<String, dynamic>),
|
AudiobookShelfServer.fromJson(json['server'] as Map<String, dynamic>),
|
||||||
authToken: json['authToken'] as String,
|
authToken: json['authToken'] as String,
|
||||||
id: json['id'] as String?,
|
id: json['id'] as String,
|
||||||
username: json['username'] as String?,
|
username: json['username'] as String?,
|
||||||
password: json['password'] as String?,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$AuthenticatedUserImplToJson(
|
Map<String, dynamic> _$$AuthenticatedUserImplToJson(
|
||||||
|
|
@ -24,5 +23,4 @@ Map<String, dynamic> _$$AuthenticatedUserImplToJson(
|
||||||
'authToken': instance.authToken,
|
'authToken': instance.authToken,
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'username': instance.username,
|
'username': instance.username,
|
||||||
'password': instance.password,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk;
|
||||||
import 'package:vaani/settings/models/api_settings.dart';
|
import 'package:vaani/settings/models/api_settings.dart';
|
||||||
import 'package:vaani/settings/models/audiobookshelf_server.dart';
|
import 'package:vaani/settings/models/audiobookshelf_server.dart';
|
||||||
import 'package:vaani/settings/models/authenticated_user.dart';
|
import 'package:vaani/settings/models/authenticated_user.dart';
|
||||||
|
|
@ -67,7 +68,6 @@ extension ObfuscateAuthenticatedUser on AuthenticatedUser {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return copyWith(
|
return copyWith(
|
||||||
password: password == null ? null : 'passwordObfuscated',
|
|
||||||
username: username == null ? null : 'usernameObfuscated',
|
username: username == null ? null : 'usernameObfuscated',
|
||||||
authToken: 'authTokenObfuscated',
|
authToken: 'authTokenObfuscated',
|
||||||
server: server.obfuscate(),
|
server: server.obfuscate(),
|
||||||
|
|
@ -116,10 +116,54 @@ extension ObfuscateResponse on http.Response {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return http.Response(
|
return http.Response(
|
||||||
body,
|
obfuscateBody(),
|
||||||
statusCode,
|
statusCode,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
request: request?.obfuscate(),
|
request: request?.obfuscate(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String obfuscateBody() {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
// replace any email addresses with emailObfuscated
|
||||||
|
// replace any phone numbers with phoneObfuscated
|
||||||
|
// replace any urls with urlObfuscated
|
||||||
|
// replace any tokens with tokenObfuscated
|
||||||
|
// token regex is `"token": "..."`
|
||||||
|
return body
|
||||||
|
.replaceAll(
|
||||||
|
RegExp(r'(\b\w+@\w+\.\w+\b)|'
|
||||||
|
r'(\b\d{3}-\d{3}-\d{4}\b)|'
|
||||||
|
r'(\bhttps?://\S+\b)'),
|
||||||
|
'obfuscated',
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
|
RegExp(r'"?token"?:?\s*"[^"]+"'),
|
||||||
|
'"token": "tokenObfuscated"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ObfuscateLoginResponse on shelfsdk.LoginResponse {
|
||||||
|
shelfsdk.LoginResponse obfuscate() {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return copyWith(
|
||||||
|
user: user.obfuscate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ObfuscateUser on shelfsdk.User {
|
||||||
|
shelfsdk.User obfuscate() {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return shelfsdk.User.fromJson(
|
||||||
|
toJson()..['token'] = 'tokenObfuscated',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:vaani/api/api_provider.dart';
|
import 'package:vaani/api/api_provider.dart';
|
||||||
|
import 'package:vaani/main.dart';
|
||||||
|
|
||||||
|
final httpUrlRegExp = RegExp('https?://');
|
||||||
|
|
||||||
class AddNewServer extends HookConsumerWidget {
|
class AddNewServer extends HookConsumerWidget {
|
||||||
const AddNewServer({
|
const AddNewServer({
|
||||||
|
|
@ -25,7 +28,8 @@ class AddNewServer extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final myController = controller ?? useTextEditingController();
|
final myController =
|
||||||
|
controller ?? useTextEditingController(text: 'https://');
|
||||||
var newServerURI = useValueListenable(myController);
|
var newServerURI = useValueListenable(myController);
|
||||||
final isServerAlive = ref.watch(isServerAliveProvider(newServerURI.text));
|
final isServerAlive = ref.watch(isServerAliveProvider(newServerURI.text));
|
||||||
bool isServerAliveValue = isServerAlive.when(
|
bool isServerAliveValue = isServerAlive.when(
|
||||||
|
|
@ -34,15 +38,33 @@ class AddNewServer extends HookConsumerWidget {
|
||||||
error: (error, _) => false,
|
error: (error, _) => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Uri parsedUri = Uri.parse('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsedUri = Uri.parse(newServerURI.text);
|
||||||
|
} on FormatException {
|
||||||
|
// prepend https:// if not present
|
||||||
|
if (!newServerURI.text.startsWith(httpUrlRegExp)) {
|
||||||
|
myController.text = 'https://${newServerURI.text}';
|
||||||
|
parsedUri = Uri.parse(myController.text);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
appLogger.severe('Error parsing URI: $e');
|
||||||
|
}
|
||||||
|
final canSubmit = !readOnly &&
|
||||||
|
(isServerAliveValue || (allowEmpty && newServerURI.text.isEmpty));
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
autofillHints: const [AutofillHints.url],
|
autofillHints: const [AutofillHints.url],
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
onFieldSubmitted: (_) {
|
onFieldSubmitted: canSubmit
|
||||||
onPressed?.call();
|
? (_) {
|
||||||
},
|
onPressed?.call();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Server URI',
|
labelText: 'Server URI',
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
|
|
@ -50,8 +72,8 @@ class AddNewServer extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixText:
|
prefixText:
|
||||||
myController.text.startsWith(RegExp('https?://')) ? '' : 'https://',
|
myController.text.startsWith(httpUrlRegExp) ? '' : 'https://',
|
||||||
prefixIcon: ServerAliveIcon(server: Uri.parse(newServerURI.text)),
|
prefixIcon: ServerAliveIcon(server: parsedUri),
|
||||||
|
|
||||||
// add server button
|
// add server button
|
||||||
suffixIcon: onPressed == null
|
suffixIcon: onPressed == null
|
||||||
|
|
@ -65,10 +87,10 @@ class AddNewServer extends HookConsumerWidget {
|
||||||
focusColor: Theme.of(context).colorScheme.onSurface,
|
focusColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
|
||||||
// should be enabled when
|
// should be enabled when
|
||||||
onPressed: !readOnly &&
|
onPressed: canSubmit
|
||||||
(isServerAliveValue ||
|
? () {
|
||||||
(allowEmpty && newServerURI.text.isEmpty))
|
onPressed?.call();
|
||||||
? onPressed
|
}
|
||||||
: null, // disable button if server is not alive
|
: null, // disable button if server is not alive
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue