security_section_widget.dart 11 KB

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