security_section_widget.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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/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/settings/common_settings.dart';
  16. import 'package:photos/ui/settings/settings_section_title.dart';
  17. import 'package:photos/ui/settings/settings_text_item.dart';
  18. import 'package:photos/ui/tools/app_lock.dart';
  19. import 'package:photos/utils/auth_util.dart';
  20. import 'package:photos/utils/dialog_util.dart';
  21. class SecuritySectionWidget extends StatefulWidget {
  22. const SecuritySectionWidget({Key key}) : super(key: key);
  23. @override
  24. State<SecuritySectionWidget> createState() => _SecuritySectionWidgetState();
  25. }
  26. class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
  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. final hasAuthenticatedOrNoLocalAuth =
  76. await LocalAuthenticationService.instance
  77. .requestLocalAuthentication(
  78. context,
  79. "Please authenticate to configure two-factor authentication",
  80. );
  81. if (hasAuthenticatedOrNoLocalAuth) {
  82. if (value) {
  83. UserService.instance.setupTwoFactor(context);
  84. } else {
  85. _disableTwoFactor();
  86. }
  87. }
  88. },
  89. );
  90. } else if (snapshot.hasError) {
  91. return Icon(
  92. Icons.error_outline,
  93. color: Colors.white.withOpacity(0.8),
  94. );
  95. }
  96. return const EnteLoadingWidget();
  97. },
  98. ),
  99. ],
  100. ),
  101. ),
  102. ],
  103. );
  104. }
  105. children.addAll([
  106. sectionOptionDivider,
  107. SizedBox(
  108. height: 48,
  109. child: Row(
  110. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  111. children: [
  112. Text(
  113. "Lockscreen",
  114. style: Theme.of(context).textTheme.subtitle1,
  115. ),
  116. Switch.adaptive(
  117. value: _config.shouldShowLockScreen(),
  118. onChanged: (value) async {
  119. if (await LocalAuthentication().isDeviceSupported()) {
  120. AppLock.of(context).disable();
  121. final result = await requestAuthentication(
  122. "Please authenticate to change lockscreen setting",
  123. );
  124. if (result) {
  125. AppLock.of(context).setEnabled(value);
  126. _config.setShouldShowLockScreen(value);
  127. setState(() {});
  128. } else {
  129. AppLock.of(context)
  130. .setEnabled(_config.shouldShowLockScreen());
  131. }
  132. } else {
  133. showErrorDialog(
  134. context,
  135. "",
  136. "To enable the ente lockscreen, please setup the device passcode or screen lock in the system settings.",
  137. );
  138. }
  139. },
  140. ),
  141. ],
  142. ),
  143. ),
  144. ]);
  145. if (Platform.isAndroid) {
  146. children.addAll(
  147. [
  148. sectionOptionDivider,
  149. SizedBox(
  150. height: 48,
  151. child: Row(
  152. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  153. children: [
  154. Text(
  155. "Hide from recents",
  156. style: Theme.of(context).textTheme.subtitle1,
  157. ),
  158. Switch.adaptive(
  159. value: _config.shouldHideFromRecents(),
  160. onChanged: (value) async {
  161. if (value) {
  162. final AlertDialog alert = AlertDialog(
  163. title: const Text("Hide from recents?"),
  164. content: SingleChildScrollView(
  165. child: Column(
  166. mainAxisAlignment: MainAxisAlignment.start,
  167. crossAxisAlignment: CrossAxisAlignment.start,
  168. children: const [
  169. Text(
  170. "Hiding from the task switcher will prevent you from taking screenshots in this app.",
  171. style: TextStyle(
  172. height: 1.5,
  173. ),
  174. ),
  175. Padding(padding: EdgeInsets.all(8)),
  176. Text(
  177. "Are you sure?",
  178. style: TextStyle(
  179. height: 1.5,
  180. ),
  181. ),
  182. ],
  183. ),
  184. ),
  185. actions: [
  186. TextButton(
  187. child: Text(
  188. "No",
  189. style: TextStyle(
  190. color: Theme.of(context)
  191. .colorScheme
  192. .defaultTextColor,
  193. ),
  194. ),
  195. onPressed: () {
  196. Navigator.of(context, rootNavigator: true)
  197. .pop('dialog');
  198. },
  199. ),
  200. TextButton(
  201. child: Text(
  202. "Yes",
  203. style: TextStyle(
  204. color: Theme.of(context)
  205. .colorScheme
  206. .defaultTextColor,
  207. ),
  208. ),
  209. onPressed: () async {
  210. Navigator.of(context, rootNavigator: true)
  211. .pop('dialog');
  212. await _config.setShouldHideFromRecents(true);
  213. await FlutterWindowManager.addFlags(
  214. FlutterWindowManager.FLAG_SECURE,
  215. );
  216. setState(() {});
  217. },
  218. ),
  219. ],
  220. );
  221. showDialog(
  222. context: context,
  223. builder: (BuildContext context) {
  224. return alert;
  225. },
  226. );
  227. } else {
  228. await _config.setShouldHideFromRecents(false);
  229. await FlutterWindowManager.clearFlags(
  230. FlutterWindowManager.FLAG_SECURE,
  231. );
  232. setState(() {});
  233. }
  234. },
  235. ),
  236. ],
  237. ),
  238. ),
  239. ],
  240. );
  241. }
  242. children.addAll([
  243. sectionOptionDivider,
  244. GestureDetector(
  245. behavior: HitTestBehavior.translucent,
  246. onTap: () async {
  247. final hasAuthenticatedOrNoLocalAuth = await LocalAuthenticationService
  248. .instance
  249. .requestLocalAuthentication(
  250. context,
  251. "Please authenticate to view your active sessions",
  252. );
  253. if (hasAuthenticatedOrNoLocalAuth) {
  254. Navigator.of(context).push(
  255. MaterialPageRoute(
  256. builder: (BuildContext context) {
  257. return const SessionsPage();
  258. },
  259. ),
  260. );
  261. }
  262. },
  263. child: const SettingsTextItem(
  264. text: "Active sessions",
  265. icon: Icons.navigate_next,
  266. ),
  267. ),
  268. ]);
  269. return Column(
  270. children: children,
  271. );
  272. }
  273. void _disableTwoFactor() {
  274. final AlertDialog alert = AlertDialog(
  275. title: const Text("Disable two-factor"),
  276. content: const Text(
  277. "Are you sure you want to disable two-factor authentication?",
  278. ),
  279. actions: [
  280. TextButton(
  281. child: Text(
  282. "No",
  283. style: TextStyle(
  284. color: Theme.of(context).colorScheme.greenAlternative,
  285. ),
  286. ),
  287. onPressed: () {
  288. Navigator.of(context, rootNavigator: true).pop('dialog');
  289. },
  290. ),
  291. TextButton(
  292. child: const Text(
  293. "Yes",
  294. style: TextStyle(
  295. color: Colors.red,
  296. ),
  297. ),
  298. onPressed: () async {
  299. await UserService.instance.disableTwoFactor(context);
  300. Navigator.of(context, rootNavigator: true).pop('dialog');
  301. },
  302. ),
  303. ],
  304. );
  305. showDialog(
  306. context: context,
  307. builder: (BuildContext context) {
  308. return alert;
  309. },
  310. );
  311. }
  312. }