Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
8.5 KiB

  1. import 'dart:convert';
  2. import 'package:Envelope/components/flash_message.dart';
  3. import 'package:Envelope/utils/storage/session_cookie.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_dotenv/flutter_dotenv.dart';
  6. import 'package:qr_flutter/qr_flutter.dart';
  7. import 'package:sliding_up_panel/sliding_up_panel.dart';
  8. import 'package:http/http.dart' as http;
  9. import '/components/select_message_ttl.dart';
  10. import '/components/custom_circle_avatar.dart';
  11. import '/components/custom_title_bar.dart';
  12. import '/models/my_profile.dart';
  13. import '/utils/encryption/crypto_utils.dart';
  14. import '/utils/storage/database.dart';
  15. import '/views/main/profile/change_password.dart';
  16. import '/views/main/profile/change_server_url.dart';
  17. class Profile extends StatefulWidget {
  18. final MyProfile profile;
  19. const Profile({
  20. Key? key,
  21. required this.profile,
  22. }) : super(key: key);
  23. @override
  24. State<Profile> createState() => _ProfileState();
  25. }
  26. class _ProfileState extends State<Profile> {
  27. final PanelController _panelController = PanelController();
  28. @override
  29. Widget build(BuildContext context) {
  30. return Scaffold(
  31. appBar: const CustomTitleBar(
  32. title: Text(
  33. 'Profile',
  34. style: TextStyle(
  35. fontSize: 32,
  36. fontWeight: FontWeight.bold
  37. )
  38. ),
  39. showBack: false,
  40. backgroundColor: Colors.transparent,
  41. ),
  42. body: SlidingUpPanel(
  43. controller: _panelController,
  44. slideDirection: SlideDirection.DOWN,
  45. defaultPanelState: PanelState.CLOSED,
  46. color: Theme.of(context).scaffoldBackgroundColor,
  47. backdropTapClosesPanel: true,
  48. backdropEnabled: true,
  49. backdropOpacity: 0.2,
  50. minHeight: 0,
  51. maxHeight: 450,
  52. panel: Center(
  53. child: _profileQrCode(),
  54. ),
  55. body: Padding(
  56. padding: const EdgeInsets.only(top: 16,left: 16,right: 16),
  57. child: Column(
  58. children: <Widget>[
  59. usernameHeading(),
  60. const SizedBox(height: 30),
  61. settings(),
  62. const SizedBox(height: 30),
  63. logout(),
  64. ],
  65. )
  66. ),
  67. ),
  68. );
  69. }
  70. Widget usernameHeading() {
  71. return Row(
  72. children: <Widget> [
  73. const CustomCircleAvatar(
  74. icon: Icon(Icons.person, size: 40),
  75. radius: 30,
  76. ),
  77. const SizedBox(width: 20),
  78. Expanded(
  79. flex: 1,
  80. child: Text(
  81. widget.profile.username,
  82. style: const TextStyle(
  83. fontSize: 25,
  84. fontWeight: FontWeight.w500,
  85. ),
  86. ),
  87. ),
  88. IconButton(
  89. onPressed: () => _panelController.open(),
  90. icon: const Icon(Icons.qr_code_2),
  91. ),
  92. ],
  93. );
  94. }
  95. Widget logout() {
  96. bool isTesting = dotenv.env['ENVIRONMENT'] == 'development';
  97. return Align(
  98. alignment: Alignment.centerLeft,
  99. child: Column(
  100. crossAxisAlignment: CrossAxisAlignment.stretch,
  101. children: [
  102. TextButton.icon(
  103. label: const Text(
  104. 'Logout',
  105. style: TextStyle(fontSize: 16)
  106. ),
  107. icon: const Icon(Icons.exit_to_app),
  108. style: const ButtonStyle(
  109. alignment: Alignment.centerLeft,
  110. ),
  111. onPressed: () {
  112. deleteDb();
  113. MyProfile.logout();
  114. Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
  115. }
  116. ),
  117. isTesting ? TextButton.icon(
  118. label: const Text(
  119. 'Delete Database',
  120. style: TextStyle(fontSize: 16)
  121. ),
  122. icon: const Icon(Icons.delete_forever),
  123. style: const ButtonStyle(
  124. alignment: Alignment.centerLeft,
  125. ),
  126. onPressed: () {
  127. deleteDb();
  128. }
  129. ) : const SizedBox.shrink(),
  130. ],
  131. ),
  132. );
  133. }
  134. Widget settings() {
  135. return Align(
  136. alignment: Alignment.centerLeft,
  137. child: Column(
  138. crossAxisAlignment: CrossAxisAlignment.stretch,
  139. children: [
  140. const SizedBox(height: 5),
  141. TextButton.icon(
  142. label: const Text(
  143. 'Disappearing Messages',
  144. style: TextStyle(fontSize: 16)
  145. ),
  146. icon: const Icon(Icons.timer),
  147. style: ButtonStyle(
  148. alignment: Alignment.centerLeft,
  149. foregroundColor: MaterialStateProperty.resolveWith<Color>(
  150. (Set<MaterialState> states) {
  151. return Theme.of(context).colorScheme.onBackground;
  152. },
  153. )
  154. ),
  155. onPressed: () {
  156. Navigator.of(context).push(
  157. MaterialPageRoute(builder: (context) => SelectMessageTTL(
  158. widgetTitle: 'Message Expiry',
  159. currentSelected: widget.profile.messageExpiryDefault,
  160. backCallback: (String messageExpiry) async {
  161. widget.profile.messageExpiryDefault = messageExpiry;
  162. http.post(
  163. await MyProfile.getServerUrl('api/v1/auth/message_expiry'),
  164. headers: {
  165. 'cookie': await getSessionCookie(),
  166. },
  167. body: jsonEncode({
  168. 'message_expiry': messageExpiry,
  169. }),
  170. ).then((http.Response response) {
  171. if (response.statusCode == 200) {
  172. return;
  173. }
  174. showMessage(
  175. 'Could not change your default message expiry, please try again later.',
  176. context,
  177. );
  178. });
  179. },
  180. ))
  181. );
  182. }
  183. ),
  184. const SizedBox(height: 5),
  185. TextButton.icon(
  186. label: const Text(
  187. 'Server URL',
  188. style: TextStyle(fontSize: 16)
  189. ),
  190. icon: const Icon(Icons.dataset_linked_outlined),
  191. style: ButtonStyle(
  192. alignment: Alignment.centerLeft,
  193. foregroundColor: MaterialStateProperty.resolveWith<Color>(
  194. (Set<MaterialState> states) {
  195. return Theme.of(context).colorScheme.onBackground;
  196. },
  197. )
  198. ),
  199. onPressed: () {
  200. Navigator.of(context).push(
  201. MaterialPageRoute(builder: (context) => const ChangeServerUrl())
  202. );
  203. }
  204. ),
  205. const SizedBox(height: 5),
  206. TextButton.icon(
  207. label: const Text(
  208. 'Change Password',
  209. style: TextStyle(fontSize: 16)
  210. ),
  211. icon: const Icon(Icons.password),
  212. style: ButtonStyle(
  213. alignment: Alignment.centerLeft,
  214. foregroundColor: MaterialStateProperty.resolveWith<Color>(
  215. (Set<MaterialState> states) {
  216. return Theme.of(context).colorScheme.onBackground;
  217. },
  218. )
  219. ),
  220. onPressed: () {
  221. Navigator.of(context).push(
  222. MaterialPageRoute(builder: (context) => ChangePassword(
  223. privateKey: widget.profile.privateKey!,
  224. ))
  225. );
  226. }
  227. ),
  228. ],
  229. ),
  230. );
  231. }
  232. Widget _profileQrCode() {
  233. String payload = jsonEncode({
  234. 'i': widget.profile.id,
  235. 'u': widget.profile.username,
  236. 'k': base64.encode(
  237. CryptoUtils.encodeRSAPublicKeyToPem(widget.profile.publicKey!).codeUnits
  238. ),
  239. });
  240. return Column(
  241. children: [
  242. Padding(
  243. padding: const EdgeInsets.all(20),
  244. child: QrImage(
  245. backgroundColor: Theme.of(context).colorScheme.primary,
  246. data: payload,
  247. version: QrVersions.auto,
  248. gapless: true,
  249. ),
  250. ),
  251. Align(
  252. alignment: Alignment.centerRight,
  253. child: Padding(
  254. padding: const EdgeInsets.only(right: 20),
  255. child: IconButton(
  256. onPressed: () => _panelController.close(),
  257. icon: const Icon(Icons.arrow_upward),
  258. ),
  259. ),
  260. ),
  261. ]
  262. );
  263. }
  264. }