diff --git a/lib/config/app_url.dart b/lib/config/app_url.dart index f23b5cc..dd00ddd 100644 --- a/lib/config/app_url.dart +++ b/lib/config/app_url.dart @@ -14,6 +14,7 @@ class AppUrl { static const String topPreferences = apiUrl + "/top_preferences"; static const String updateProfilePic = apiUrl + "/update_profile_picture"; static const String getLanguages = apiUrl + "/get_languages"; + static const String deleteAccount = apiUrl + "/delete_account"; static const String suggestion = baseURL + "/suggestion"; diff --git a/lib/network/delete_account.dart b/lib/network/delete_account.dart new file mode 100644 index 0000000..22e213e --- /dev/null +++ b/lib/network/delete_account.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:jam/config/app_url.dart'; +import 'package:jam/providers/user_provider.dart'; +import 'package:jam/widgets/show_snackbar.dart'; + +import '../main.dart'; + +/// Delete Account Call, returns answer from server, +/// null if could not connect +Future deleteAccountCall(String userId, String password) async { + final Map dataToSend = { + "user_id": userId, + "password": password, + }; + try { + var response = await post( + Uri.parse(AppUrl.deleteAccount), + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + body: json.encode(dataToSend), + ); + if (response.body == "Wrong api token") { + print("wrong api token in delete account call"); + logout(); + showSnackBar(navigatorKey.currentContext!, "Wrong api token"); + } + return response.body; + } catch (err) { + print(err); + return null; + } +} diff --git a/lib/network/network_call.dart b/lib/network/network_call.dart index 3229564..b40d636 100644 --- a/lib/network/network_call.dart +++ b/lib/network/network_call.dart @@ -1,6 +1,10 @@ import 'dart:convert'; import 'package:http/http.dart'; +import 'package:jam/providers/user_provider.dart'; +import 'package:jam/widgets/show_snackbar.dart'; + +import '../main.dart'; /// Can be used with api calls that return OK on success. /// Returns true on success @@ -16,6 +20,8 @@ Future networkCall(Map dataToSend, String apiUrl) async { } if (response.body == "Wrong api token") { print("wrong api token in $apiUrl call"); + logout(); + showSnackBar(navigatorKey.currentContext!, "Wrong api token"); return false; } return false; diff --git a/lib/network/wake.dart b/lib/network/wake.dart index 3b24cd7..c9adc99 100644 --- a/lib/network/wake.dart +++ b/lib/network/wake.dart @@ -33,6 +33,12 @@ Future?> wakeRequest( return null; } Map decoded = jsonDecode(response.body); + var smallPic = decoded["small_profile_picture"]; + if (smallPic != null) { + Uint8List smallPicture = Uint8List.fromList(json.decode(smallPic).cast()); + saveSmallPicture(smallPicture, userId); + } + Map rawFriends = decoded["friends"]; result["refresh_token_expired"] = decoded["refresh_token_expired"]; print("raw friends length:"); diff --git a/lib/pages/dm.dart b/lib/pages/dm.dart index 226f43d..f8daa5e 100644 --- a/lib/pages/dm.dart +++ b/lib/pages/dm.dart @@ -119,8 +119,10 @@ class _DMState extends State with WidgetsBindingObserver { }, ); AlertDialog alertDialog = alert( - "Do you really want to block $otherUsername?", blockButton, - content: "You won't be able to receive messages from $otherUsername."); + "Do you really want to block $otherUsername?", + blockButton, + content: Text("You won't be able to receive messages from $otherUsername."), + ); void _handleThreeDotClick(String value) { switch (value) { diff --git a/lib/pages/explanation.dart b/lib/pages/explanation.dart new file mode 100644 index 0000000..f8f8515 --- /dev/null +++ b/lib/pages/explanation.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:jam/config/routes.dart'; + +class Explanation extends StatelessWidget { + @override + Widget build(BuildContext context) { + TextStyle style = TextStyle( + color: Colors.white, + fontSize: 20, + ); + return Scaffold( + backgroundColor: Color(0xFFFF66C4), + body: Padding( + padding: + const EdgeInsets.only(top: 70, bottom: 70, left: 50, right: 50), + child: Column( + children: [ + Container( + height: 200, + width: 200, + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.contain, + image: AssetImage('assets/icon/icon.png'), + ), + ), + ), + SizedBox(height: 20), + SingleChildScrollView( + child: Column( + children: [ + Text( + "\tWelcome to Jam! This is an app that helps people with same music taste get together via spotify. The matches are made daily at the same time for everyone so you won't get a match until midnight GMT. We will send a notification to remind you. Have fun!", + style: style, + ), + ], + ), + ), + Expanded( + child: Align( + alignment: Alignment.bottomRight, + child: RawMaterialButton( + splashColor: Colors.white, + shape: CircleBorder(), + fillColor: Colors.white, + child: Icon( + Icons.check, + color: Colors.grey, + size: 50, + ), + onPressed: () { + Navigator.pushNamedAndRemoveUntil( + context, + chatLanguages, + (Route route) => false, + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/forms/register.dart b/lib/pages/forms/register.dart index c6d4b6f..10742c3 100644 --- a/lib/pages/forms/register.dart +++ b/lib/pages/forms/register.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:jam/pages/explanation.dart'; import 'package:jam/widgets/app_bar.dart'; import 'package:jam/widgets/loading.dart'; import 'package:jam/widgets/show_snackbar.dart'; @@ -92,7 +93,12 @@ class _RegisterState extends State { User? user = response['user']; Provider.of(context, listen: false) .setUser(user, context); - Navigator.pushNamedAndRemoveUntil(context, routes.chatLanguages, (Route route) => false); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (BuildContext context) => Explanation()), + (route) => false, + ); } else { showSnackBar(context, response['message']); } diff --git a/lib/pages/homepage.dart b/lib/pages/homepage.dart index 390ae12..5071dfe 100644 --- a/lib/pages/homepage.dart +++ b/lib/pages/homepage.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:jam/pages/kebab_menu/read_log.dart'; import 'package:jam/providers/message_provider.dart'; import 'package:jam/providers/unread_message_counter.dart'; import 'package:jam/util/local_notification.dart'; @@ -34,14 +33,6 @@ class _HomepageState extends State { case "Contact Us": Navigator.pushNamed(context, routes.contactUs); break; - case "Logs": - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ReadLog(), - ), - ); - break; } } @@ -99,7 +90,7 @@ class _HomepageState extends State { PopupMenuButton( onSelected: _handleThreeDotClick, itemBuilder: (BuildContext context) { - return {"Contact Us", 'About', 'Logs'}.map((String choice) { + return {"Contact Us", 'About'}.map((String choice) { return PopupMenuItem( value: choice, child: Text(choice), @@ -150,22 +141,24 @@ class _HomepageState extends State { ), ), Divider(color: Colors.grey), - FutureBuilder( - future: Provider.of(context, listen: false).init( - Provider.of(context, listen: false), - user, - context, + Expanded( + child: FutureBuilder( + future: Provider.of(context, listen: false).init( + Provider.of(context, listen: false), + user, + context, + ), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + print("messages future builder waiting"); + return Center(child: CircularProgressIndicator()); + default: + return messagesList(user, context); + } + }, ), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - print("messages future builder waiting"); - return Center(child: CircularProgressIndicator()); - default: - return messagesList(user, context); - } - }, ), ], ), diff --git a/lib/pages/kebab_menu/about.dart b/lib/pages/kebab_menu/about.dart index f255d27..1265102 100644 --- a/lib/pages/kebab_menu/about.dart +++ b/lib/pages/kebab_menu/about.dart @@ -46,7 +46,7 @@ class About extends StatelessWidget { onTap: _launchEmail, child: FittedBox( child: Text( - "dabco5317@gmail.com", + "overlapco0@gmail.com", textAlign: TextAlign.center, style: TextStyle( fontSize: 30, @@ -64,7 +64,7 @@ class About extends StatelessWidget { } void _launchEmail() async { - var _url = "mailto:dabco5317@gmail.com?subject=Jam"; + var _url = "mailto:overlapco0@gmail.com?subject=Jam"; if (!await launch(_url)) throw 'Could not launch $_url'; } } diff --git a/lib/pages/kebab_menu/read_log.dart b/lib/pages/kebab_menu/read_log.dart deleted file mode 100644 index 3773bb1..0000000 --- a/lib/pages/kebab_menu/read_log.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jam/util/log_to_file.dart'; - -class ReadLog extends StatefulWidget { - @override - State createState() => _ReadLogState(); -} - -class _ReadLogState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Logs"), - ), - body: FutureBuilder( - future: readLog(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - return Center(child: CircularProgressIndicator()); - default: - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(snapshot.data as String), - ), - ); - } - }, - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - setState(() { - clearLog(); - }); - }, - child: Text("Clear"), - ), - ); - } -} diff --git a/lib/pages/profile/chat_language.dart b/lib/pages/profile/chat_language.dart index ae630d5..d2a7918 100644 --- a/lib/pages/profile/chat_language.dart +++ b/lib/pages/profile/chat_language.dart @@ -39,19 +39,20 @@ class _ChatLanguageState extends State { Provider.of(context, listen: false).addLanguage(iso); }); } else if (success == 2) { - showSnackBar( - context, "It looks like you have already selected your languages"); - List? langs = await getLanguagesCall(user.id!, user.token!, user.id!); + showSnackBar(context, + "It looks like you have already selected your languages"); + List? langs = + await getLanguagesCall(user.id!, user.token!, user.id!); if (langs != null) { setState(() { if (ModalRoute.of(context)!.isFirst) { okVisible = true; } - Provider.of(context, listen: false).overrideLanguages(langs); + Provider.of(context, listen: false) + .overrideLanguages(langs); }); } - } - else { + } else { showSnackBar( context, "Could not add language, check your connection"); } @@ -179,8 +180,12 @@ class _ChatLanguageState extends State { color: Colors.pinkAccent, ), SizedBox(height: 10), - const Text("You don't have any language preference." - "You have to select at least one language in order to match with other people."), + const Text( + "Choose the languages you can speak.\n\n" + "You have to select at least one language in order to match with other people.", + style: TextStyle(fontSize: 15), + textAlign: TextAlign.center, + ), ], ) : Container( diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index 336e0ac..d7cb7f7 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -5,11 +5,16 @@ import 'package:jam/config/routes.dart'; import 'package:jam/models/artist_model.dart'; import 'package:jam/models/track_model.dart'; import 'package:jam/models/user.dart'; +import 'package:jam/network/delete_account.dart'; import 'package:jam/network/top_preferences.dart'; import 'package:jam/providers/user_provider.dart'; +import 'package:jam/util/validators.dart'; import 'package:jam/widgets/alert.dart'; -import 'package:jam/widgets/profile_picture.dart'; +import 'package:jam/widgets/form_widgets.dart'; +import 'package:jam/widgets/loading.dart'; import 'package:jam/widgets/profile_lists.dart'; +import 'package:jam/widgets/profile_picture.dart'; +import 'package:jam/widgets/show_snackbar.dart'; import 'package:provider/provider.dart'; class Profile extends StatefulWidget { @@ -18,7 +23,6 @@ class Profile extends StatefulWidget { } class _ProfileState extends State { - Future openHiveBox(String boxName) async { if (!Hive.isBoxOpen(boxName)) { await Hive.openBox(boxName); @@ -36,18 +40,95 @@ class _ProfileState extends State { Hive.registerAdapter(trackAdapter); } - TextButton continueButton = TextButton( - child: const Text("Log out"), - onPressed: () { - Provider.of(context, listen: false).logout(); - }, - ); - AlertDialog alertDialog = alert("Attention!", continueButton, - content: "Are you sure you want to log out?"); - User user = Provider.of(context).user!; String userId = user.id!; + AlertDialog logoutDialog = alert( + "Attention!", + TextButton( + child: const Text("Log out"), + onPressed: () { + Provider.of(context, listen: false).logout(); + }, + ), + content: Text("Are you sure you want to log out?"), + ); + + bool deleting = false; + String? _password; + final formKey = new GlobalKey(); + AlertDialog deleteAccountDialog = alert( + "Attention!", + TextButton( + child: const Text("Delete Account"), + onPressed: () { + Navigator.pop(context); + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setState) { + return AlertDialog( + actions: deleting ? [] : [cancelButton], + content: Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.0), + Text("Password"), + SizedBox(height: 5.0), + TextFormField( + autofocus: false, + obscureText: true, + validator: (value) => validatePassword(value), + onSaved: (value) => _password = value, + decoration: buildInputDecoration( + "Enter your password", Icons.lock), + ), + SizedBox(height: 20.0), + deleting + ? loading("Please wait") + : longButtons( + "Delete Account", + () async { + setState(() { + deleting = true; + }); + final form = formKey.currentState!; + if (form.validate()) { + form.save(); + String? result = await deleteAccountCall(userId, _password!); + if (result == null) { + Navigator.pop(context); + showSnackBar(context, "Check your connection"); + } else if (result == "OK") { + Provider.of(context, listen: false).logout(); + } else { + Navigator.pop(context); + showSnackBar(context, result); + } + } + setState(() { + deleting = false; + }); + }, + color: Colors.red, + ), + ], + ), + ), + ), + ); + }); + }, + ); + }, + ), + content: Text( + "Are you sure you want to delete your account? This action is irreversible and you will lose all of your matches forever!"), + ); + topPreferencesCall(userId, user.token!, userId); // request from server String boxName = tracksArtistsBoxName(userId, userId); @@ -111,7 +192,23 @@ class _ProfileState extends State { showDialog( context: context, builder: (BuildContext context) { - return alertDialog; + return logoutDialog; + }, + ); + }, + ), + Divider(color: Colors.grey), + ListTile( + leading: Icon( + Icons.delete_forever_outlined, + color: Colors.red, + ), + title: const Text('Delete Account'), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return deleteAccountDialog; }, ); }, diff --git a/lib/providers/message_provider.dart b/lib/providers/message_provider.dart index 9d5ad5c..064aa43 100644 --- a/lib/providers/message_provider.dart +++ b/lib/providers/message_provider.dart @@ -10,7 +10,6 @@ import 'package:jam/network/wake.dart'; import 'package:jam/providers/unread_message_counter.dart'; import 'package:jam/providers/user_provider.dart'; import 'package:jam/util/local_notification.dart'; -import 'package:jam/util/log_to_file.dart'; import 'package:jam/util/util_functions.dart'; import 'package:jam/widgets/show_snackbar.dart'; import 'package:provider/provider.dart'; @@ -89,7 +88,6 @@ class MessageProvider extends ChangeNotifier { String key = "${message.timestamp}"; if (chat.get(key) != null) { print("double message: ${message.messageContent}"); - logToFile("double message: ${message.messageContent}\n"); return; } await chat.put(key, message); diff --git a/lib/providers/mqtt.dart b/lib/providers/mqtt.dart index 2eb5e72..c14bf6b 100644 --- a/lib/providers/mqtt.dart +++ b/lib/providers/mqtt.dart @@ -8,7 +8,6 @@ import 'package:jam/models/user.dart'; import 'package:jam/providers/message_provider.dart'; import 'package:jam/providers/unread_message_counter.dart'; import 'package:jam/providers/user_provider.dart'; -import 'package:jam/util/log_to_file.dart'; import 'package:jam/widgets/show_snackbar.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; @@ -87,10 +86,8 @@ Future connect(User _user, MessageProvider _msgProvider, try { await _client.connect(); - logToFile("client.connect() await done.\n"); } catch (e) { print('Exception: $e'); - logToFile('Exception: $e\n'); _client.disconnect(); } @@ -99,8 +96,6 @@ Future connect(User _user, MessageProvider _msgProvider, final payload = MqttEncoding().decoder.convert(byteMessage.payload.message); var topic = c[0].topic; - logToFile('Received message:$payload from topic: $topic\n'); - logToFile("c Length: ${c.length.toString()}\n"); var message = jsonDecode(payload); if (message == null) { @@ -194,7 +189,6 @@ void onConnected() { // every user subscribes to topic for their id client?.subscribe("/${user.id}/inbox", MqttQos.atLeastOnce); client?.subscribe("/${user.id}/devices/$clientId", MqttQos.atMostOnce); - logToFile("Connected, subscribe is called.\n"); } /// disconnected @@ -206,7 +200,6 @@ void onDisconnected() { /// subscribe to topic succeeded void onSubscribed(String topic) { print('Subscribed topic: $topic'); - logToFile("Subscribed topic: $topic\n"); } /// subscribe to topic failed diff --git a/lib/util/log_to_file.dart b/lib/util/log_to_file.dart deleted file mode 100644 index 3c7d1fd..0000000 --- a/lib/util/log_to_file.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:io'; - -import 'package:path_provider/path_provider.dart'; - -Future get _localPath async { - final directory = await getApplicationDocumentsDirectory(); - return directory.path; -} - -Future get _localFile async { - final path = await _localPath; - return File('$path/log.txt'); -} - -Future logToFile(String log) async { - final file = await _localFile; - // Write the file - return file.writeAsString('$log', mode: FileMode.append); -} - -Future readLog() async { - try { - final file = await _localFile; - // Read the file - final contents = await file.readAsString(); - return contents; - } catch (e) { - // If encountering an error, return 0 - return "An error occured: ${e.toString()}"; - } -} - -Future clearLog() async { - final file = await _localFile; - file.writeAsString(""); -} diff --git a/lib/widgets/alert.dart b/lib/widgets/alert.dart index edd8b82..a027aab 100644 --- a/lib/widgets/alert.dart +++ b/lib/widgets/alert.dart @@ -8,10 +8,10 @@ Widget cancelButton = TextButton( }, ); -AlertDialog alert(String title, TextButton continueButton, {content: String}) { +AlertDialog alert(String title, TextButton continueButton, {content: Widget}) { return AlertDialog( title: Text(title), - content: Text(content), + content: content, actions: [ cancelButton, continueButton, diff --git a/lib/widgets/messages_list.dart b/lib/widgets/messages_list.dart index 159b7c0..a813e5f 100644 --- a/lib/widgets/messages_list.dart +++ b/lib/widgets/messages_list.dart @@ -35,8 +35,6 @@ messagesList(user, context) { } chats.sort(); return ListView.separated( - shrinkWrap: true, - // needed for this scrollable widget inside another scrollable widget itemBuilder: (context, index) => !chats[index].isBlocked ? Padding( padding: EdgeInsets.only(top: 5, bottom: 5), diff --git a/pubspec.yaml b/pubspec.yaml index a2b6ab7..fb5effd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,6 +78,7 @@ flutter: assets: - assets/avatar.png - assets/ca/lets-encrypt-r3.pem + - assets/icon/icon.png # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in