security_section_widget.dart 10 KB

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