security_section_widget.dart 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // @dart=2.9
  2. import 'dart:async';
  3. import 'dart:io';
  4. import 'package:expandable/expandable.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter_windowmanager/flutter_windowmanager.dart';
  7. import 'package:photos/core/configuration.dart';
  8. import 'package:photos/core/event_bus.dart';
  9. import 'package:photos/ente_theme_data.dart';
  10. import 'package:photos/events/two_factor_status_change_event.dart';
  11. import 'package:photos/services/local_authentication_service.dart';
  12. import 'package:photos/services/user_service.dart';
  13. import 'package:photos/ui/account/sessions_page.dart';
  14. import 'package:photos/ui/common/loading_widget.dart';
  15. import 'package:photos/ui/components/captioned_text_widget.dart';
  16. import 'package:photos/ui/components/menu_item_widget.dart';
  17. import 'package:photos/ui/settings/common_settings.dart';
  18. class SecuritySectionWidget extends StatefulWidget {
  19. const SecuritySectionWidget({Key key}) : super(key: key);
  20. @override
  21. State<SecuritySectionWidget> createState() => _SecuritySectionWidgetState();
  22. }
  23. class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
  24. final _config = Configuration.instance;
  25. StreamSubscription<TwoFactorStatusChangeEvent> _twoFactorStatusChangeEvent;
  26. final expandableController = ExpandableController(initialExpanded: false);
  27. @override
  28. void initState() {
  29. super.initState();
  30. _twoFactorStatusChangeEvent =
  31. Bus.instance.on<TwoFactorStatusChangeEvent>().listen((event) async {
  32. if (mounted) {
  33. setState(() {});
  34. }
  35. });
  36. }
  37. @override
  38. void dispose() {
  39. _twoFactorStatusChangeEvent.cancel();
  40. expandableController.dispose();
  41. super.dispose();
  42. }
  43. @override
  44. Widget build(BuildContext context) {
  45. return ExpandablePanel(
  46. header: MenuItemWidget(
  47. captionedTextWidget: const CaptionedTextWidget(
  48. text: "Security",
  49. makeTextBold: true,
  50. ),
  51. isHeaderOfExpansion: true,
  52. leadingIcon: Icons.local_police_outlined,
  53. trailingIcon: Icons.expand_more,
  54. menuItemColor:
  55. Theme.of(context).colorScheme.enteTheme.colorScheme.fillFaint,
  56. expandableController: expandableController,
  57. ),
  58. collapsed: const SizedBox.shrink(),
  59. expanded: _getSectionOptions(context),
  60. theme: getExpandableTheme(context),
  61. controller: expandableController,
  62. );
  63. }
  64. Widget _getSectionOptions(BuildContext context) {
  65. final List<Widget> children = [];
  66. if (_config.hasConfiguredAccount()) {
  67. children.addAll(
  68. [
  69. const Padding(padding: EdgeInsets.all(2)),
  70. FutureBuilder(
  71. future: UserService.instance.fetchTwoFactorStatus(),
  72. builder: (_, snapshot) {
  73. return MenuItemWidget(
  74. captionedTextWidget: const CaptionedTextWidget(
  75. text: "Two-factor",
  76. ),
  77. trailingSwitch: snapshot.hasData
  78. ? Switch.adaptive(
  79. value: snapshot.data,
  80. onChanged: (value) async {
  81. final hasAuthenticated =
  82. await LocalAuthenticationService.instance
  83. .requestLocalAuthentication(
  84. context,
  85. "Please authenticate to configure two-factor authentication",
  86. );
  87. if (hasAuthenticated) {
  88. if (value) {
  89. UserService.instance.setupTwoFactor(context);
  90. } else {
  91. _disableTwoFactor();
  92. }
  93. }
  94. },
  95. )
  96. : snapshot.hasError
  97. ? const Icon(Icons.error_outline_outlined)
  98. : const EnteLoadingWidget(),
  99. );
  100. },
  101. )
  102. ],
  103. );
  104. }
  105. children.addAll([
  106. MenuItemWidget(
  107. captionedTextWidget: const CaptionedTextWidget(
  108. text: "Lockscreen",
  109. ),
  110. trailingSwitch: Switch.adaptive(
  111. value: _config.shouldShowLockScreen(),
  112. onChanged: (value) async {
  113. final hasAuthenticated = await LocalAuthenticationService.instance
  114. .requestLocalAuthForLockScreen(
  115. context,
  116. value,
  117. "Please authenticate to change lockscreen setting",
  118. "To enable lockscreen, please setup device passcode or screen lock in your system settings.",
  119. );
  120. if (hasAuthenticated) {
  121. setState(() {});
  122. }
  123. },
  124. ),
  125. ),
  126. ]);
  127. if (Platform.isAndroid) {
  128. children.addAll(
  129. [
  130. MenuItemWidget(
  131. captionedTextWidget: const CaptionedTextWidget(
  132. text: "Hide from recents",
  133. ),
  134. trailingSwitch: Switch.adaptive(
  135. value: _config.shouldHideFromRecents(),
  136. onChanged: (value) async {
  137. if (value) {
  138. final AlertDialog alert = AlertDialog(
  139. title: const Text("Hide from recents?"),
  140. content: SingleChildScrollView(
  141. child: Column(
  142. mainAxisAlignment: MainAxisAlignment.start,
  143. crossAxisAlignment: CrossAxisAlignment.start,
  144. children: const [
  145. Text(
  146. "Hiding from the task switcher will prevent you from taking screenshots in this app.",
  147. style: TextStyle(
  148. height: 1.5,
  149. ),
  150. ),
  151. Padding(padding: EdgeInsets.all(8)),
  152. Text(
  153. "Are you sure?",
  154. style: TextStyle(
  155. height: 1.5,
  156. ),
  157. ),
  158. ],
  159. ),
  160. ),
  161. actions: [
  162. TextButton(
  163. child: Text(
  164. "No",
  165. style: TextStyle(
  166. color:
  167. Theme.of(context).colorScheme.defaultTextColor,
  168. ),
  169. ),
  170. onPressed: () {
  171. Navigator.of(context, rootNavigator: true)
  172. .pop('dialog');
  173. },
  174. ),
  175. TextButton(
  176. child: Text(
  177. "Yes",
  178. style: TextStyle(
  179. color:
  180. Theme.of(context).colorScheme.defaultTextColor,
  181. ),
  182. ),
  183. onPressed: () async {
  184. Navigator.of(context, rootNavigator: true)
  185. .pop('dialog');
  186. await _config.setShouldHideFromRecents(true);
  187. await FlutterWindowManager.addFlags(
  188. FlutterWindowManager.FLAG_SECURE,
  189. );
  190. setState(() {});
  191. },
  192. ),
  193. ],
  194. );
  195. showDialog(
  196. context: context,
  197. builder: (BuildContext context) {
  198. return alert;
  199. },
  200. );
  201. } else {
  202. await _config.setShouldHideFromRecents(false);
  203. await FlutterWindowManager.clearFlags(
  204. FlutterWindowManager.FLAG_SECURE,
  205. );
  206. setState(() {});
  207. }
  208. },
  209. ),
  210. ),
  211. ],
  212. );
  213. }
  214. children.addAll([
  215. MenuItemWidget(
  216. captionedTextWidget: const CaptionedTextWidget(
  217. text: "Active sessions",
  218. ),
  219. trailingIcon: Icons.chevron_right_outlined,
  220. trailingIconIsMuted: true,
  221. onTap: () async {
  222. final hasAuthenticated = await LocalAuthenticationService.instance
  223. .requestLocalAuthentication(
  224. context,
  225. "Please authenticate to view your active sessions",
  226. );
  227. if (hasAuthenticated) {
  228. Navigator.of(context).push(
  229. MaterialPageRoute(
  230. builder: (BuildContext context) {
  231. return const SessionsPage();
  232. },
  233. ),
  234. );
  235. }
  236. },
  237. ),
  238. ]);
  239. return Column(
  240. children: children,
  241. );
  242. }
  243. void _disableTwoFactor() {
  244. final AlertDialog alert = AlertDialog(
  245. title: const Text("Disable two-factor"),
  246. content: const Text(
  247. "Are you sure you want to disable two-factor authentication?",
  248. ),
  249. actions: [
  250. TextButton(
  251. child: Text(
  252. "No",
  253. style: TextStyle(
  254. color: Theme.of(context).colorScheme.greenAlternative,
  255. ),
  256. ),
  257. onPressed: () {
  258. Navigator.of(context, rootNavigator: true).pop('dialog');
  259. },
  260. ),
  261. TextButton(
  262. child: const Text(
  263. "Yes",
  264. style: TextStyle(
  265. color: Colors.red,
  266. ),
  267. ),
  268. onPressed: () async {
  269. await UserService.instance.disableTwoFactor(context);
  270. Navigator.of(context, rootNavigator: true).pop('dialog');
  271. },
  272. ),
  273. ],
  274. );
  275. showDialog(
  276. context: context,
  277. builder: (BuildContext context) {
  278. return alert;
  279. },
  280. );
  281. }
  282. }