home.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import '../../state/app_state.dart';
  2. import 'package:pb_authenticator_state/state.dart';
  3. import 'package:flutter/material.dart';
  4. import '../../helper/url.dart' show launchURL;
  5. import '/l10n/app_localizations.dart';
  6. import 'package:provider/provider.dart';
  7. import '../../config/routes.dart';
  8. import '../../l10n/constants.dart';
  9. import './list_item.dart' show HomeListItem;
  10. /// Android version of home page.
  11. class AndroidHomePage extends StatelessWidget {
  12. const AndroidHomePage({super.key});
  13. /// Builds list of items.
  14. Widget _buildList() {
  15. return Consumer<AppState>(
  16. builder: (context, state, child) {
  17. if (state.items == null) {
  18. // Items loading
  19. return Center(
  20. child: Padding(
  21. padding: const EdgeInsets.all(10),
  22. child: Text(AppLocalizations.of(context)!.loading)));
  23. } else if (state.items!.isEmpty) {
  24. // No Items
  25. return Center(
  26. child: Padding(
  27. padding: const EdgeInsets.all(10),
  28. child: Text(AppLocalizations.of(context)!.noAccounts,
  29. textAlign: TextAlign.center,
  30. style: const TextStyle(height: 1.2))));
  31. }
  32. return Container(
  33. margin: const EdgeInsets.all(10),
  34. child: ListView.builder(
  35. itemCount: state.items!.length,
  36. itemBuilder: (BuildContext context, int index) {
  37. var item = state.items![index];
  38. return HomeListItem(item, key: Key(item.id));
  39. },
  40. ),
  41. );
  42. },
  43. );
  44. }
  45. /// Shows add modal
  46. void showAddModal(BuildContext parentContext) {
  47. // Show bottom sheet
  48. showModalBottomSheet<void>(
  49. context: parentContext,
  50. builder: (BuildContext context) {
  51. return Container(
  52. padding: const EdgeInsets.all(8.0),
  53. child: ListView(
  54. shrinkWrap: true,
  55. children: <Widget>[
  56. // Scan QR
  57. ListTile(
  58. title: Text(AppLocalizations.of(context)!.addScanQR),
  59. leading: const Icon(Icons.camera_alt),
  60. onTap: () async {
  61. Navigator.pop(context);
  62. var result =
  63. await Navigator.pushNamed(context, AppRoutes.addScan);
  64. if (result == null) {
  65. return;
  66. }
  67. // TODO: Transition to new type
  68. var item = result as LegacyAuthenticatorItem;
  69. await Provider.of<AppState>(parentContext, listen: false)
  70. .addItem(item.totp);
  71. },
  72. ),
  73. // Add account manually
  74. ListTile(
  75. title: Text(AppLocalizations.of(context)!.addManualInput),
  76. leading: const Icon(Icons.keyboard_return),
  77. onTap: () {
  78. Navigator.pop(context);
  79. Navigator.pushNamed(context, AppRoutes.add);
  80. },
  81. ),
  82. ],
  83. ),
  84. );
  85. },
  86. );
  87. }
  88. @override
  89. Widget build(BuildContext context) {
  90. return Scaffold(
  91. drawer: Drawer(
  92. child: ListView(
  93. padding: EdgeInsets.zero,
  94. children: <Widget>[
  95. DrawerHeader(
  96. decoration: BoxDecoration(
  97. color: Theme.of(context).primaryColor,
  98. ),
  99. child: Row(
  100. crossAxisAlignment: CrossAxisAlignment.start,
  101. children: [
  102. Text(
  103. AppLocalizations.of(context)!.appName,
  104. style: TextStyle(
  105. color: Colors.white,
  106. fontSize:
  107. Theme.of(context).textTheme.titleLarge!.fontSize),
  108. ),
  109. ],
  110. ),
  111. ),
  112. // Settings
  113. ListTile(
  114. title: Text(AppLocalizations.of(context)!.settingsTitle),
  115. leading: const Icon(Icons.settings),
  116. dense: true,
  117. onTap: () {
  118. Navigator.pop(context);
  119. Navigator.pushNamed(context, AppRoutes.settings);
  120. }),
  121. // How it works
  122. ListTile(
  123. title: Text(AppLocalizations.of(context)!.howItWorksTitle ?? 'How it works'),
  124. leading: const Icon(Icons.info_outline),
  125. dense: true,
  126. onTap: () {
  127. Navigator.pop(context);
  128. Navigator.pushNamed(context, AppRoutes.howItWorks);
  129. },
  130. ),
  131. // Transfer codes
  132. ListTile(
  133. title: Text(AppLocalizations.of(context)!.transferCodesTitle ?? 'Transfer codes'),
  134. leading: const Icon(Icons.sync_alt),
  135. dense: true,
  136. onTap: () {
  137. Navigator.pop(context);
  138. Navigator.pushNamed(context, AppRoutes.transferCodes);
  139. },
  140. ),
  141. const Divider(height: 10),
  142. // Source code (in-app browser link)
  143. ListTile(
  144. title: Text(AppLocalizations.of(context)!.source),
  145. leading: const Icon(Icons.code),
  146. dense: true,
  147. onTap: () {
  148. Navigator.pop(context);
  149. launchURL(Constants.repoUrl);
  150. }),
  151. // Help
  152. ListTile(
  153. title: Text(AppLocalizations.of(context)!.helpTitle ?? 'Help'),
  154. leading: const Icon(Icons.help_outline),
  155. dense: true,
  156. onTap: () {
  157. Navigator.pop(context);
  158. Navigator.pushNamed(context, AppRoutes.help);
  159. },
  160. ),
  161. ],
  162. ),
  163. ),
  164. appBar: AppBar(
  165. title: Text(AppLocalizations.of(context)!.appName),
  166. actions: <Widget>[
  167. // Edit
  168. IconButton(
  169. icon: const Icon(Icons.edit),
  170. tooltip: AppLocalizations.of(context)!.edit,
  171. onPressed: () {
  172. Navigator.pushNamed(context, AppRoutes.edit);
  173. },
  174. ),
  175. // Add
  176. IconButton(
  177. icon: const Icon(Icons.add),
  178. tooltip: AppLocalizations.of(context)!.add,
  179. onPressed: () {
  180. showAddModal(context);
  181. },
  182. )
  183. ],
  184. ),
  185. body: _buildList(),
  186. );
  187. }
  188. }