2024-05-08 05:03:49 -04:00
|
|
|
import 'package:flutter/material.dart';
|
2025-04-23 15:00:01 +05:30
|
|
|
import 'package:flutter_animate/flutter_animate.dart';
|
2024-05-08 05:03:49 -04:00
|
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2025-04-23 15:00:01 +05:30
|
|
|
import 'package:shelfsdk/audiobookshelf_api.dart' show AuthMethod;
|
|
|
|
|
import 'package:vaani/api/api_provider.dart' show serverStatusProvider;
|
|
|
|
|
import 'package:vaani/api/server_provider.dart'
|
|
|
|
|
show ServerAlreadyExistsException, audiobookShelfServerProvider;
|
|
|
|
|
import 'package:vaani/features/onboarding/view/onboarding_single_page.dart'
|
|
|
|
|
show fadeSlideTransitionBuilder;
|
|
|
|
|
import 'package:vaani/features/onboarding/view/user_login_with_open_id.dart'
|
|
|
|
|
show UserLoginWithOpenID;
|
|
|
|
|
import 'package:vaani/features/onboarding/view/user_login_with_password.dart'
|
|
|
|
|
show UserLoginWithPassword;
|
|
|
|
|
import 'package:vaani/features/onboarding/view/user_login_with_token.dart'
|
|
|
|
|
show UserLoginWithToken;
|
|
|
|
|
import 'package:vaani/hacks/fix_autofill_losing_focus.dart'
|
|
|
|
|
show InactiveFocusScopeObserver;
|
|
|
|
|
import 'package:vaani/models/error_response.dart' show ErrorResponseHandler;
|
|
|
|
|
import 'package:vaani/settings/api_settings_provider.dart'
|
|
|
|
|
show apiSettingsProvider;
|
2024-09-06 15:10:00 -04:00
|
|
|
import 'package:vaani/settings/models/models.dart' as model;
|
2024-05-08 05:03:49 -04:00
|
|
|
|
2024-09-06 15:10:00 -04:00
|
|
|
class UserLoginWidget extends HookConsumerWidget {
|
2025-04-23 16:23:57 +05:30
|
|
|
const UserLoginWidget({
|
2024-05-08 05:03:49 -04:00
|
|
|
super.key,
|
2024-09-06 15:10:00 -04:00
|
|
|
required this.server,
|
2025-04-23 15:00:01 +05:30
|
|
|
this.onSuccess,
|
2024-09-06 15:10:00 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final Uri server;
|
2025-04-23 15:00:01 +05:30
|
|
|
final Function(model.AuthenticatedUser)? onSuccess;
|
2024-09-06 15:10:00 -04:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2025-04-23 15:00:01 +05:30
|
|
|
final serverStatusError = useMemoized(() => ErrorResponseHandler(), []);
|
2024-09-06 15:10:00 -04:00
|
|
|
final serverStatus =
|
|
|
|
|
ref.watch(serverStatusProvider(server, serverStatusError.storeError));
|
|
|
|
|
|
|
|
|
|
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,
|
2024-09-16 23:51:50 -04:00
|
|
|
openIDAvailable:
|
2024-09-06 15:10:00 -04:00
|
|
|
value.authMethods?.contains(AuthMethod.openid) ?? false,
|
2024-09-16 23:51:50 -04:00
|
|
|
openIDButtonText: value.authFormData?.authOpenIDButtonText,
|
2025-04-23 15:00:01 +05:30
|
|
|
onSuccess: onSuccess,
|
2024-09-06 15:10:00 -04:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
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,
|
2024-09-16 23:51:50 -04:00
|
|
|
this.openIDAvailable = false,
|
2024-05-08 05:03:49 -04:00
|
|
|
this.onPressed,
|
2024-09-16 23:51:50 -04:00
|
|
|
this.openIDButtonText,
|
2025-04-23 15:00:01 +05:30
|
|
|
this.onSuccess,
|
2024-05-08 05:03:49 -04:00
|
|
|
});
|
|
|
|
|
|
2024-09-06 15:10:00 -04:00
|
|
|
final Uri server;
|
|
|
|
|
final bool localAvailable;
|
2024-09-16 23:51:50 -04:00
|
|
|
final bool openIDAvailable;
|
2024-05-08 05:03:49 -04:00
|
|
|
final void Function()? onPressed;
|
2024-09-16 23:51:50 -04:00
|
|
|
final String? openIDButtonText;
|
2025-04-23 15:00:01 +05:30
|
|
|
final Function(model.AuthenticatedUser)? onSuccess;
|
2024-05-08 05:03:49 -04:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2024-09-06 15:10:00 -04:00
|
|
|
// will show choice chips for the available authentication methods
|
|
|
|
|
// authToken method is always available
|
|
|
|
|
final methodChoice = useState<AuthMethodChoice>(
|
2024-09-16 23:54:30 -04:00
|
|
|
localAvailable ? AuthMethodChoice.local : AuthMethodChoice.authToken,
|
2024-09-06 15:10:00 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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(
|
2025-04-23 15:00:01 +05:30
|
|
|
ref.read(apiSettingsProvider).copyWith(
|
|
|
|
|
activeServer: newServer,
|
|
|
|
|
),
|
2024-09-06 15:10:00 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return newServer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Center(
|
|
|
|
|
child: InactiveFocusScopeObserver(
|
|
|
|
|
child: AutofillGroup(
|
2025-04-10 19:12:20 +05:30
|
|
|
child: Column(
|
|
|
|
|
children: [
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
|
|
|
|
child: Wrap(
|
2024-09-06 15:10:00 -04:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-09-16 23:51:50 -04:00
|
|
|
if (openIDAvailable)
|
2024-09-06 15:10:00 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-04-23 15:00:01 +05:30
|
|
|
].animate(interval: 100.ms).fadeIn(
|
|
|
|
|
duration: 150.ms,
|
|
|
|
|
curve: Curves.easeIn,
|
|
|
|
|
),
|
2024-09-06 15:10:00 -04:00
|
|
|
),
|
2025-04-10 19:12:20 +05:30
|
|
|
),
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
2025-04-23 15:00:01 +05:30
|
|
|
child: AnimatedSwitcher(
|
|
|
|
|
duration: 200.ms,
|
|
|
|
|
transitionBuilder: fadeSlideTransitionBuilder,
|
|
|
|
|
child: switch (methodChoice.value) {
|
|
|
|
|
AuthMethodChoice.authToken => UserLoginWithToken(
|
|
|
|
|
server: server,
|
|
|
|
|
addServer: addServer,
|
|
|
|
|
onSuccess: onSuccess,
|
|
|
|
|
),
|
|
|
|
|
AuthMethodChoice.local => UserLoginWithPassword(
|
|
|
|
|
server: server,
|
|
|
|
|
addServer: addServer,
|
|
|
|
|
onSuccess: onSuccess,
|
|
|
|
|
),
|
|
|
|
|
AuthMethodChoice.openid => UserLoginWithOpenID(
|
|
|
|
|
server: server,
|
|
|
|
|
addServer: addServer,
|
|
|
|
|
openIDButtonText: openIDButtonText,
|
|
|
|
|
onSuccess: onSuccess,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-04-10 19:12:20 +05:30
|
|
|
),
|
|
|
|
|
],
|
2024-09-06 15:10:00 -04:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|