something

This commit is contained in:
Dr-Blank 2024-05-08 05:03:49 -04:00
parent dbf4ce1959
commit a720c977c2
No known key found for this signature in database
GPG key ID: 7452CC63F210A266
115 changed files with 8819 additions and 1 deletions

View file

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/api/authenticated_user_provider.dart';
import 'package:whispering_pages/api/server_provider.dart';
import 'package:whispering_pages/settings/app_settings_provider.dart';
class AppSettingsPage extends HookConsumerWidget {
const AppSettingsPage({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appSettings = ref.watch(appSettingsProvider);
final registeredServers = ref.watch(audiobookShelfServerProvider);
final registeredServersAsList = registeredServers.toList();
final availableUsers = ref.watch(authenticatedUserProvider);
final serverURIController = useTextEditingController();
final formKey = GlobalKey<FormState>();
return Scaffold(
appBar: AppBar(
title: const Text('App Settings'),
),
body: SettingsList(
sections: [
SettingsSection(
title: const Text('Appearance'),
tiles: [
SettingsTile.switchTile(
initialValue: appSettings.isDarkMode,
title: const Text('Dark Mode'),
leading: appSettings.isDarkMode
? const Icon(Icons.dark_mode)
: const Icon(Icons.light_mode),
onToggle: (value) {
ref.read(appSettingsProvider.notifier).toggleDarkMode();
},
),
],
),
],
),
);
}
}

76
lib/pages/home_page.dart Normal file
View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/api/api_provider.dart';
import 'package:whispering_pages/settings/app_settings_provider.dart';
import '../widgets/drawer.dart';
import '../widgets/shelves/home_shelf.dart';
class HomePage extends HookConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// hooks for the dark mode
final settings = ref.watch(appSettingsProvider);
final api = ref.watch(authenticatedApiProvider);
final views = ref.watch(personalizedViewProvider);
final scrollController = useScrollController();
return Scaffold(
appBar: AppBar(
title: GestureDetector(
child: const Text('Whispering Pages'),
onTap: () {
// scroll to the top of the page
scrollController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
// refresh the view
ref.invalidate(personalizedViewProvider);
},
),
),
drawer: const MyDrawer(),
body: Container(
child: views.when(
data: (data) {
final shelvesToDisplay = data
.where((element) => !element.id.contains('discover'))
.map(
(shelf) => HomeShelf(
title: Text(shelf.label),
shelf: shelf,
),
)
.toList();
return RefreshIndicator(
onRefresh: () async {
// await ref
// .read(personalizedViewProvider.notifier)
// .forceRefresh();
return ref.refresh(personalizedViewProvider);
},
child: ListView.separated(
itemBuilder: (context, index) => shelvesToDisplay[index],
separatorBuilder: (context, index) => Divider(
color: Theme.of(context).dividerColor.withOpacity(0.1),
indent: 16,
endIndent: 16,
),
itemCount: shelvesToDisplay.length,
controller: scrollController,
),
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stack) {
return Text('Error: $error');
},
),
),
);
}
}

View file

@ -0,0 +1,89 @@
import 'package:coast/coast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/pages/onboarding/server_setup.dart';
import 'package:whispering_pages/pages/onboarding/user_login.dart';
import 'package:whispering_pages/settings/api_settings_provider.dart';
const _serverTag = 'server';
class OnboardingPage extends StatefulHookConsumerWidget {
const OnboardingPage({super.key});
@override
OnboardingPageState createState() => OnboardingPageState();
}
class OnboardingPageState extends ConsumerState<OnboardingPage> {
final coastController = CoastController();
@override
void dispose() {
coastController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final apiSettings = ref.watch(apiSettingsProvider);
final serverUriController = useTextEditingController(
text: apiSettings.activeServer?.serverUrl.toString(),
);
bool isUserLoginAvailable() {
return apiSettings.activeServer != null;
}
// ignore: invalid_use_of_protected_member
if (isUserLoginAvailable()) {
try {
coastController.animateTo(
beach: 1,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
} catch (e) {
debugPrint('Error: $e');
}
}
final beaches = [
Beach(
builder: (context) => FirstTimeServerSetupPage(
controller: serverUriController,
heroServerTag: _serverTag,
),
),
isUserLoginAvailable()
? Beach(
builder: (context) => FirstTimeUserLoginPage(
serverUriController: serverUriController,
heroServerTag: _serverTag,
),
)
: null,
].nonNulls.toList();
const activeStep = 0;
return Scaffold(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Coast(
beaches: beaches,
controller: coastController,
observers: [
CrabController(),
],
),
),
Positioned(
child: Container(),
),
],
),
);
}
}

View file

@ -0,0 +1,66 @@
import 'package:coast/coast.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/api/server_provider.dart';
import 'package:whispering_pages/settings/models/audiobookshelf_server.dart' as model;
import 'package:whispering_pages/settings/api_settings_provider.dart';
import 'package:whispering_pages/widgets/add_new_server.dart';
class FirstTimeServerSetupPage extends HookConsumerWidget {
const FirstTimeServerSetupPage({
super.key,
required this.controller,
required this.heroServerTag,
});
final TextEditingController controller;
final String heroServerTag;
@override
Widget build(BuildContext context, WidgetRef ref) {
final apiSettings = ref.watch(apiSettingsProvider);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Welcome to Whispering Pages'),
Crab(
tag: heroServerTag,
child: AddNewServer(
controller: controller,
allowEmpty: true,
onPressed: () {
var newServer = controller.text.isEmpty
? null
: model.AudiobookShelfServer(
serverUrl: Uri.parse(controller.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,
),
);
}
},
),
),
],
),
);
}
}

View file

@ -0,0 +1,94 @@
import 'package:coast/coast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/api/api_provider.dart';
import 'package:whispering_pages/api/authenticated_user_provider.dart'
show authenticatedUserProvider;
import 'package:whispering_pages/settings/models/audiobookshelf_server.dart';
import 'package:whispering_pages/settings/models/authenticated_user.dart';
import 'package:whispering_pages/settings/api_settings_provider.dart';
import 'package:whispering_pages/widgets/add_new_server.dart';
import 'package:whispering_pages/widgets/user_login.dart';
/// Once the user has selected a server, they can login to it.
class FirstTimeUserLoginPage extends HookConsumerWidget {
const FirstTimeUserLoginPage({
super.key,
required this.serverUriController,
required this.heroServerTag,
});
final TextEditingController serverUriController;
final String heroServerTag;
@override
Widget build(BuildContext context, WidgetRef ref) {
final usernameController = useTextEditingController();
final passwordController = useTextEditingController();
final apiSettings = ref.watch(apiSettingsProvider);
final api = ref
.watch(audiobookshelfApiProvider(Uri.https(serverUriController.text)));
/// Login to the server and save the user
void loginAndSave() async {
final username = usernameController.text;
final password = passwordController.text;
final success = await api.login(username: username, password: password);
// debugPrint('Login success: $success');
if (success != null) {
var authenticatedUser = AuthenticatedUser(
server: 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,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login failed'),
),
);
// give focus back to the username field
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Crab(
tag: heroServerTag,
child: AddNewServer(
controller: serverUriController,
onPressed: () {},
readOnly: true,
),
),
const SizedBox(height: 30),
const Text('Login to server'),
const SizedBox(height: 30),
UserLogin(
usernameController: usernameController,
passwordController: passwordController,
onPressed: loginAndSave,
),
],
),
);
}
}

2
lib/pages/pages.dart Normal file
View file

@ -0,0 +1,2 @@
export 'home_page.dart';
export 'server_manager.dart';

View file

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/api/authenticated_user_provider.dart';
import 'package:whispering_pages/api/server_provider.dart';
import 'package:whispering_pages/settings/models/audiobookshelf_server.dart' as model;
import 'package:whispering_pages/settings/api_settings_provider.dart';
import 'package:whispering_pages/widgets/add_new_server.dart';
class ServerManagerPage extends HookConsumerWidget {
const ServerManagerPage({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final apiSettings = ref.watch(apiSettingsProvider);
final registeredServers = ref.watch(audiobookShelfServerProvider);
final registeredServersAsList = registeredServers.toList();
final availableUsers = ref.watch(authenticatedUserProvider);
final serverURIController = useTextEditingController();
final formKey = GlobalKey<FormState>();
debugPrint('registered servers: $registeredServers');
debugPrint('available users: $availableUsers');
return Scaffold(
appBar: AppBar(
title: const Text('Setup Servers'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Text(
'Registered Servers',
),
Expanded(
child: ListView.builder(
itemCount: registeredServers.length,
reverse: true,
itemBuilder: (context, index) {
var registeredServer = registeredServersAsList[index];
return ExpansionTile(
title: Text(registeredServer.serverUrl.toString()),
subtitle: Text(
'Users: ${availableUsers.where((element) => element.server == registeredServer).length}',
),
trailing: IconButton(
icon: const Icon(Icons.delete),
// delete the server from the list of servers
onPressed: () {
ref
.read(audiobookShelfServerProvider.notifier)
.removeServer(registeredServer);
},
),
// children are list of users of this server
children: availableUsers
.where(
(element) => element.server == registeredServer,
)
.map(
(e) => ListTile(
title: Text(e.username ?? 'Anonymous'),
subtitle: Text(e.authToken),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
ref
.read(authenticatedUserProvider.notifier)
.removeUser(e);
},
),
),
)
.nonNulls
.toList(),
);
},
),
),
const SizedBox(height: 20),
Form(
key: formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: AddNewServer(
controller: serverURIController,
onPressed: () {
if (formKey.currentState!.validate()) {
try {
final newServer = model.AudiobookShelfServer(
serverUrl: Uri.parse(serverURIController.text),
);
ref
.read(audiobookShelfServerProvider.notifier)
.addServer(
newServer,
);
ref.read(apiSettingsProvider.notifier).updateState(
apiSettings.copyWith(
activeServer: newServer,
),
);
serverURIController.clear();
} on ServerAlreadyExistsException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid URL'),
),
);
}
},
),
),
],
),
),
),
);
}
}