- import 'dart:convert';
- import 'dart:io';
- import 'dart:typed_data';
-
- import 'package:flutter/material.dart';
- import 'package:flutter_dotenv/flutter_dotenv.dart';
- import 'package:image_picker/image_picker.dart';
- import 'package:mime/mime.dart';
- import 'package:qr_flutter/qr_flutter.dart';
- import 'package:shared_preferences/shared_preferences.dart';
- import 'package:sliding_up_panel/sliding_up_panel.dart';
- import 'package:http/http.dart' as http;
-
- import '/components/file_picker.dart';
- import '/components/flash_message.dart';
- import '/components/select_message_ttl.dart';
- import '/components/custom_circle_avatar.dart';
- import '/components/custom_title_bar.dart';
- import '/database/models/my_profile.dart';
- import '/utils/encryption/crypto_utils.dart';
- import '/utils/storage/database.dart';
- import '/utils/encryption/aes_helper.dart';
- import '/utils/storage/session_cookie.dart';
- import '/utils/storage/write_file.dart';
- import '/views/main/profile/change_password.dart';
- import '/views/main/profile/change_server_url.dart';
-
- class Profile extends StatefulWidget {
- final MyProfile profile;
- const Profile({
- Key? key,
- required this.profile,
- }) : super(key: key);
-
- @override
- State<Profile> createState() => _ProfileState();
- }
-
- class _ProfileState extends State<Profile> {
- final PanelController _panelController = PanelController();
-
- bool showFileSelector = false;
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: const CustomTitleBar(
- title: Text(
- 'Profile',
- style: TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.bold
- )
- ),
- showBack: false,
- backgroundColor: Colors.transparent,
- ),
- body: SlidingUpPanel(
- controller: _panelController,
- slideDirection: SlideDirection.DOWN,
- defaultPanelState: PanelState.CLOSED,
- color: Theme.of(context).scaffoldBackgroundColor,
- backdropTapClosesPanel: true,
- backdropEnabled: true,
- backdropOpacity: 0.2,
- minHeight: 0,
- panel: Center(
- child: _profileQrCode(),
- ),
- body: Padding(
- padding: const EdgeInsets.only(top: 16,left: 16,right: 16),
- child: Column(
- children: <Widget>[
- usernameHeading(),
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 250),
- transitionBuilder: (Widget child, Animation<double> animation) {
- return SizeTransition(sizeFactor: animation, child: child);
- },
- child: fileSelector(),
- ),
- SizedBox(height: showFileSelector ? 10 : 30),
- settings(),
- const SizedBox(height: 30),
- logout(),
- ],
- )
- ),
- ),
- );
- }
-
- Widget usernameHeading() {
- return Row(
- children: <Widget> [
-
- CustomCircleAvatar(
- image: widget.profile.image,
- icon: const Icon(Icons.person, size: 40),
- radius: 30,
- editImageCallback: () {
- setState(() {
- showFileSelector = true;
- });
- },
- ),
-
- const SizedBox(width: 20),
-
- Expanded(
- flex: 1,
- child: Text(
- widget.profile.username,
- style: const TextStyle(
- fontSize: 25,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
-
- IconButton(
- onPressed: () => _panelController.open(),
- icon: const Icon(Icons.qr_code_2),
- ),
- ],
- );
- }
-
- Widget fileSelector() {
- if (!showFileSelector) {
- return const SizedBox.shrink();
- }
-
- return Padding(
- key: const Key('fileSelector'),
- padding: const EdgeInsets.only(top: 10),
- child: FilePicker(
- cameraHandle: _setProfileImage,
- galleryHandleSingle: _setProfileImage,
- )
- );
- }
-
- Future<void> _setProfileImage(XFile image) async {
- widget.profile.image = await writeImage(
- widget.profile.id,
- File(image.path).readAsBytesSync(),
- );
-
- setState(() {
- showFileSelector = false;
- });
-
- saveProfile();
-
- Map<String, dynamic> payload = {
- 'data': AesHelper.aesEncrypt(
- widget.profile.symmetricKey!,
- Uint8List.fromList(widget.profile.image!.readAsBytesSync())
- ),
- 'mimetype': lookupMimeType(widget.profile.image!.path),
- 'extension': getExtension(widget.profile.image!.path),
- };
-
- http.post(
- await MyProfile.getServerUrl('api/v1/auth/image'),
- headers: {
- 'cookie': await getSessionCookie(),
- },
- body: jsonEncode(payload),
- ).then((http.Response response) {
- if (response.statusCode == 204) {
- return;
- }
-
- showMessage(
- 'Could not add profile picture, please try again later.',
- context,
- );
- });
- }
-
- Widget logout() {
- bool isTesting = dotenv.env['ENVIRONMENT'] == 'development';
-
- return Align(
- alignment: Alignment.centerLeft,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- TextButton.icon(
- label: const Text(
- 'Logout',
- style: TextStyle(fontSize: 16)
- ),
- icon: const Icon(Icons.exit_to_app),
- style: ButtonStyle(
- foregroundColor: MaterialStateProperty.all<Color>(Theme.of(context).colorScheme.error),
- alignment: Alignment.centerLeft,
- ),
- onPressed: () {
- deleteDb();
- MyProfile.logout();
- Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing'));
- }
- ),
- isTesting ? TextButton.icon(
- label: const Text(
- 'Delete Database',
- style: TextStyle(fontSize: 16)
- ),
- icon: const Icon(Icons.delete_forever),
- style: ButtonStyle(
- foregroundColor: MaterialStateProperty.all<Color>(Theme.of(context).colorScheme.error),
- alignment: Alignment.centerLeft,
- ),
- onPressed: () {
- deleteDb();
- }
- ) : const SizedBox.shrink(),
- ],
- ),
- );
- }
-
- Widget settings() {
- return Align(
- alignment: Alignment.centerLeft,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
-
- const SizedBox(height: 5),
-
- TextButton.icon(
- label: const Text(
- 'Disappearing Messages',
- style: TextStyle(fontSize: 16)
- ),
- icon: const Icon(Icons.timer),
- style: ButtonStyle(
- alignment: Alignment.centerLeft,
- foregroundColor: MaterialStateProperty.resolveWith<Color>(
- (Set<MaterialState> states) {
- return Theme.of(context).colorScheme.onBackground;
- },
- )
- ),
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(builder: (context) => SelectMessageTTL(
- widgetTitle: 'Message Expiry',
- currentSelected: widget.profile.messageExpiryDefault,
- backCallback: (String messageExpiry) async {
- widget.profile.messageExpiryDefault = messageExpiry;
-
- http.post(
- await MyProfile.getServerUrl('api/v1/auth/message_expiry'),
- headers: {
- 'cookie': await getSessionCookie(),
- },
- body: jsonEncode({
- 'message_expiry': messageExpiry,
- }),
- ).then((http.Response response) {
- if (response.statusCode == 204) {
- return;
- }
-
- showMessage(
- 'Could not change your default message expiry, please try again later.',
- context,
- );
- });
-
- saveProfile();
- },
- ))
- );
- }
- ),
-
- const SizedBox(height: 5),
-
- TextButton.icon(
- label: const Text(
- 'Server URL',
- style: TextStyle(fontSize: 16)
- ),
- icon: const Icon(Icons.dataset_linked_outlined),
- style: ButtonStyle(
- alignment: Alignment.centerLeft,
- foregroundColor: MaterialStateProperty.resolveWith<Color>(
- (Set<MaterialState> states) {
- return Theme.of(context).colorScheme.onBackground;
- },
- )
- ),
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(builder: (context) => const ChangeServerUrl())
- );
- }
- ),
-
- const SizedBox(height: 5),
-
- TextButton.icon(
- label: const Text(
- 'Change Password',
- style: TextStyle(fontSize: 16)
- ),
- icon: const Icon(Icons.password),
- style: ButtonStyle(
- alignment: Alignment.centerLeft,
- foregroundColor: MaterialStateProperty.resolveWith<Color>(
- (Set<MaterialState> states) {
- return Theme.of(context).colorScheme.onBackground;
- },
- )
- ),
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(builder: (context) => ChangePassword(
- privateKey: widget.profile.privateKey!,
- ))
- );
- saveProfile();
- }
- ),
- ],
- ),
- );
- }
-
- Widget _profileQrCode() {
- String payload = jsonEncode({
- 'i': widget.profile.id,
- 'u': widget.profile.username,
- 'k': base64.encode(
- CryptoUtils.encodeRSAPublicKeyToPem(widget.profile.publicKey!).codeUnits
- ),
- });
-
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(20),
- child: QrImage(
- backgroundColor: Theme.of(context).colorScheme.tertiary,
- data: payload,
- version: QrVersions.auto,
- gapless: true,
- ),
- ),
- Align(
- alignment: Alignment.centerRight,
- child: Padding(
- padding: const EdgeInsets.only(right: 20),
- child: IconButton(
- onPressed: () => _panelController.close(),
- icon: const Icon(Icons.arrow_upward),
- ),
- ),
- ),
- ]
- );
- }
-
- Future<void> saveProfile() async {
- final preferences = await SharedPreferences.getInstance();
- preferences.setString('profile', widget.profile.toJson());
- }
- }
|