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.

249 lines
7.2 KiB

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