security_section_widget.dart 11 KB

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