security_section_widget.dart 11 KB

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