diff --git a/lib/pages/accounts_page.dart b/lib/pages/accounts_page.dart new file mode 100644 index 0000000..1816c4e --- /dev/null +++ b/lib/pages/accounts_page.dart @@ -0,0 +1,251 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:venera/components/components.dart'; +import 'package:venera/foundation/app.dart'; +import 'package:venera/foundation/comic_source/comic_source.dart'; +import 'package:venera/foundation/state_controller.dart'; +import 'package:venera/utils/translations.dart'; + +class AccountsPageLogic extends StateController { + final _reLogin = {}; +} + +class AccountsPage extends StatelessWidget { + const AccountsPage({super.key}); + + AccountsPageLogic get logic => StateController.find(); + + @override + Widget build(BuildContext context) { + var body = StateBuilder( + init: AccountsPageLogic(), + builder: (logic) { + return CustomScrollView( + slivers: [ + SliverAppbar(title: Text("Accounts".tl)), + SliverList( + delegate: SliverChildListDelegate( + buildContent(context).toList(), + ), + ), + SliverPadding( + padding: EdgeInsets.only(bottom: context.padding.bottom), + ) + ], + ); + }, + ); + + return Scaffold( + body: body, + ); + } + + Iterable buildContent(BuildContext context) sync* { + var sources = ComicSource.all().where((element) => element.account != null); + if (sources.isEmpty) return; + + for (var element in sources) { + final bool logged = element.isLogged; + yield Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), + child: Text( + element.name, + style: const TextStyle(fontSize: 16), + ), + ); + if (!logged) { + yield ListTile( + title: Text("Log in".tl), + trailing: const Icon(Icons.arrow_right), + onTap: () async { + if (element.account!.onLogin != null) { + await element.account!.onLogin!(context); + } + if (element.account!.login != null && context.mounted) { + await context.to( + () => _LoginPage( + login: element.account!.login!, + registerWebsite: element.account!.registerWebsite, + ), + ); + element.saveData(); + } + logic.update(); + }, + ); + } + if (logged) { + for (var item in element.account!.infoItems) { + if (item.builder != null) { + yield item.builder!(context); + } else { + yield ListTile( + title: Text(item.title.tl), + subtitle: item.data == null ? null : Text(item.data!()), + onTap: item.onTap, + ); + } + } + if (element.account!.allowReLogin) { + bool loading = logic._reLogin[element.key] == true; + yield ListTile( + title: Text("Re-login".tl), + subtitle: Text("Click if login expired".tl), + onTap: () async { + if (element.data["account"] == null) { + context.showMessage(message: "No data".tl); + return; + } + logic._reLogin[element.key] = true; + logic.update(); + final List account = element.data["account"]; + var res = await element.account!.login!(account[0], account[1]); + if (res.error) { + context.showMessage(message: res.errorMessage!); + } else { + context.showMessage(message: "Success".tl); + } + logic._reLogin[element.key] = false; + logic.update(); + }, + trailing: loading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : const Icon(Icons.refresh), + ); + } + yield ListTile( + title: Text("Exit".tl), + onTap: () { + element.data["account"] = null; + element.account?.logout(); + element.saveData(); + logic.update(); + }, + trailing: const Icon(Icons.logout), + ); + } + yield const Divider(thickness: 0.6); + } + } + + void setClipboard(String text) { + Clipboard.setData(ClipboardData(text: text)); + showToast( + message: "Copied".tl, + icon: const Icon(Icons.check), + context: App.rootContext, + ); + } +} + +class _LoginPage extends StatefulWidget { + const _LoginPage({required this.login, this.registerWebsite}); + + final LoginFunction login; + + final String? registerWebsite; + + @override + State<_LoginPage> createState() => _LoginPageState(); +} + +class _LoginPageState extends State<_LoginPage> { + String username = ""; + String password = ""; + bool loading = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const Appbar( + title: Text(''), + ), + body: Center( + child: Container( + padding: const EdgeInsets.all(16), + constraints: const BoxConstraints(maxWidth: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Login".tl, style: const TextStyle(fontSize: 24)), + const SizedBox(height: 32), + TextField( + decoration: InputDecoration( + labelText: "Username".tl, + border: const OutlineInputBorder(), + ), + onChanged: (s) { + username = s; + }, + ), + const SizedBox(height: 16), + TextField( + decoration: InputDecoration( + labelText: "Password".tl, + border: const OutlineInputBorder(), + ), + obscureText: true, + onChanged: (s) { + password = s; + }, + onSubmitted: (s) => login(), + ), + const SizedBox(height: 32), + Button.filled( + isLoading: loading, + onPressed: login, + child: Text("Continue".tl), + ), + const SizedBox(height: 32), + if (widget.registerWebsite != null) + TextButton( + onPressed: () => launchUrlString(widget.registerWebsite!), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.link), + const SizedBox(width: 8), + Text("Create Account".tl), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void login() { + if (username.isEmpty || password.isEmpty) { + showToast( + message: "Cannot be empty".tl, + icon: const Icon(Icons.error_outline), + context: context, + ); + return; + } + setState(() { + loading = true; + }); + widget.login(username, password).then((value) { + if (value.error) { + context.showMessage(message: value.errorMessage!); + setState(() { + loading = false; + }); + } else { + if (mounted) { + context.pop(); + } + } + }); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 03d21ca..896bf65 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -9,6 +9,7 @@ import 'package:venera/foundation/history.dart'; import 'package:venera/foundation/image_provider/cached_image.dart'; import 'package:venera/foundation/local.dart'; import 'package:venera/foundation/log.dart'; +import 'package:venera/pages/accounts_page.dart'; import 'package:venera/pages/comic_page.dart'; import 'package:venera/pages/comic_source_page.dart'; import 'package:venera/pages/history_page.dart'; @@ -755,7 +756,9 @@ class _AccountsWidgetState extends State<_AccountsWidget> { ), child: InkWell( borderRadius: BorderRadius.circular(8), - onTap: () {}, + onTap: () { + context.to(() => const AccountsPage()); + }, child: Column( mainAxisSize: MainAxisSize.min, children: [