home.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. // Transfer codes
  113. ListTile(
  114. title: Text(AppLocalizations.of(context)!.transferCodesTitle ?? 'Transfer codes'),
  115. leading: const Icon(Icons.sync_alt),
  116. dense: true,
  117. onTap: () {
  118. Navigator.pop(context);
  119. Navigator.pushNamed(context, AppRoutes.transferCodes);
  120. },
  121. ),
  122. // How it works
  123. ListTile(
  124. title: Text(AppLocalizations.of(context)!.howItWorksTitle ?? 'How it works'),
  125. leading: const Icon(Icons.info_outline),
  126. dense: true,
  127. onTap: () {
  128. Navigator.pop(context);
  129. Navigator.pushNamed(context, AppRoutes.howItWorks);
  130. },
  131. ),
  132. // Settings
  133. ListTile(
  134. title: Text(AppLocalizations.of(context)!.settingsTitle),
  135. leading: const Icon(Icons.settings),
  136. dense: true,
  137. onTap: () {
  138. Navigator.pop(context);
  139. Navigator.pushNamed(context, AppRoutes.settings);
  140. }
  141. ),
  142. const Divider(height: 10),
  143. // Source code (in-app browser link)
  144. ListTile(
  145. title: Text(AppLocalizations.of(context)!.source),
  146. leading: const Icon(Icons.code),
  147. dense: true,
  148. onTap: () {
  149. Navigator.pop(context);
  150. launchURL(Constants.repoUrl);
  151. }),
  152. // Help
  153. ListTile(
  154. title: Text(AppLocalizations.of(context)!.helpTitle ?? 'Help'),
  155. leading: const Icon(Icons.help_outline),
  156. dense: true,
  157. onTap: () {
  158. Navigator.pop(context);
  159. Navigator.pushNamed(context, AppRoutes.help);
  160. },
  161. ),
  162. ],
  163. ),
  164. ),
  165. appBar: AppBar(
  166. title: Text(AppLocalizations.of(context)!.appName),
  167. actions: <Widget>[
  168. // Edit
  169. IconButton(
  170. icon: const Icon(Icons.edit),
  171. tooltip: AppLocalizations.of(context)!.edit,
  172. onPressed: () {
  173. Navigator.pushNamed(context, AppRoutes.edit);
  174. },
  175. ),
  176. // Add
  177. IconButton(
  178. icon: const Icon(Icons.add),
  179. tooltip: AppLocalizations.of(context)!.add,
  180. onPressed: () {
  181. showAddModal(context);
  182. },
  183. )
  184. ],
  185. ),
  186. body: _buildList(),
  187. );
  188. }
  189. }