From e7a4b59379493c445d593d4b65bb960bd0832280 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 8 Feb 2025 18:15:57 +0100 Subject: [PATCH] contact view page --- README.md | 6 +- lib/src/components/alert_dialog.dart | 44 ++++++ lib/src/components/better_list_title.dart | 39 +++++ lib/src/localization/app_en.arb | 1 - lib/src/model/contacts_model.dart | 54 +++---- lib/src/model/messages_model.dart | 7 +- .../camera_to_share/share_image_view.dart | 5 +- .../views/chats/chat_item_details_view.dart | 25 +++- lib/src/views/chats/search_username_view.dart | 2 +- .../views/contact/contact_verify_view.dart | 32 ++++ lib/src/views/contact/contact_view.dart | 140 ++++++++++++++++++ lib/src/views/onboarding/register_view.dart | 41 +---- lib/src/views/settings/account_view.dart | 2 +- .../views/settings/settings_main_view.dart | 44 +----- 14 files changed, 324 insertions(+), 118 deletions(-) create mode 100644 lib/src/components/alert_dialog.dart create mode 100644 lib/src/components/better_list_title.dart create mode 100644 lib/src/views/contact/contact_verify_view.dart create mode 100644 lib/src/views/contact/contact_view.dart diff --git a/README.md b/README.md index b091fb8..b1bf285 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ Don't be lonely, get twonly! Send pictures to a friend in real time and be sure ## TODOS bevor first beta -- Pro Invitation codes -- Push Notification (Android) -- Contact view page - Verify contact view - Context Menu + +- Pro Invitation codes +- Push Notification (Android) - Settings - Notification - Real deployment aufsetzen, direkt auf Netcup? diff --git a/lib/src/components/alert_dialog.dart b/lib/src/components/alert_dialog.dart new file mode 100644 index 0000000..5ab0098 --- /dev/null +++ b/lib/src/components/alert_dialog.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/misc.dart'; + +Future showAlertDialog( + BuildContext context, String title, String content) async { + Completer completer = Completer(); + + Widget okButton = TextButton( + child: Text(context.lang.ok), + onPressed: () { + completer.complete(true); + Navigator.pop(context); + }, + ); + + Widget cancelButton = TextButton( + child: Text(context.lang.cancel), + onPressed: () { + completer.complete(false); + Navigator.pop(context); + }, + ); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + cancelButton, + okButton, + ], + ); + + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + return completer.future; +} diff --git a/lib/src/components/better_list_title.dart b/lib/src/components/better_list_title.dart new file mode 100644 index 0000000..c79c88a --- /dev/null +++ b/lib/src/components/better_list_title.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class BetterListTile extends StatelessWidget { + final IconData icon; + final String text; + final Color? color; + final VoidCallback onTap; + + const BetterListTile({ + super.key, + required this.icon, + required this.text, + this.color, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Padding( + padding: const EdgeInsets.only( + right: 10, + left: 19, + ), + child: FaIcon( + icon, + size: 20, + color: color, + ), + ), + title: Text( + text, + style: TextStyle(color: color), + ), + onTap: onTap, + ); + } +} diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 0b3da00..960eae9 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -59,7 +59,6 @@ "addDrawing": "Drawing", "addEmoji": "Emoji", "toogleFlashLight": "Toggle the flash light", - "chatListDetailTitle": "Your chat with {username}", "searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.", "errorUnknown": "An unexpected error has occurred. Please try again later.", "errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.", diff --git a/lib/src/model/contacts_model.dart b/lib/src/model/contacts_model.dart index 4e7e2c6..7d77dfe 100644 --- a/lib/src/model/contacts_model.dart +++ b/lib/src/model/contacts_model.dart @@ -100,18 +100,6 @@ class DbContacts extends CvModelBase { } } - static Future _updateFlameCounter(int userId, int totalMediaCounter) async { - Map valuesToUpdate = { - columnTotalMediaCounter: totalMediaCounter - }; - await dbProvider.db!.update( - tableName, - valuesToUpdate, - where: "$columnUserId = ?", - whereArgs: [userId], - ); - } - static Future> _getAllUsers() async { try { var users = await dbProvider.db!.query(tableName, columns: [ @@ -146,31 +134,45 @@ class DbContacts extends CvModelBase { } } - static Future blockUser(int userId, {bool unblock = false}) async { - Map valuesToUpdate = { - columnBlocked: unblock ? 0 : 1, - }; + static Future _update(int userId, Map updates, + {bool notifyFlutter = true}) async { await dbProvider.db!.update( tableName, - valuesToUpdate, + updates, where: "$columnUserId = ?", whereArgs: [userId], ); - globalCallBackOnContactChange(); + if (notifyFlutter) { + globalCallBackOnContactChange(); + } + } + + static Future changeDisplayName(int userId, String newDisplayName) async { + if (newDisplayName == "") return; + Map updates = { + columnDisplayName: newDisplayName, + }; + await _update(userId, updates); + } + + static Future _updateFlameCounter(int userId, int totalMediaCounter) async { + Map updates = {columnTotalMediaCounter: totalMediaCounter}; + await _update(userId, updates, notifyFlutter: false); + } + + static Future blockUser(int userId, {bool unblock = false}) async { + Map updates = { + columnBlocked: unblock ? 0 : 1, + }; + await _update(userId, updates); } static Future acceptUser(int userId) async { - Map valuesToUpdate = { + Map updates = { columnAccepted: 1, columnRequested: 0, }; - await dbProvider.db!.update( - tableName, - valuesToUpdate, - where: "$columnUserId = ?", - whereArgs: [userId], - ); - globalCallBackOnContactChange(); + await _update(userId, updates); } static Future deleteUser(int userId) async { diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart index b3677c4..069cf75 100644 --- a/lib/src/model/messages_model.dart +++ b/lib/src/model/messages_model.dart @@ -40,8 +40,7 @@ class DbMessage { bool get messageReceived => messageOtherId != null; bool isMedia() { - return messageContent is TextMessageContent || - messageContent is MediaMessageContent; + return messageContent is MediaMessageContent; } MessageSendState getSendState() { @@ -378,9 +377,9 @@ class DbMessages extends CvModelBase { jsonDecode(fromDb[i][columnMessageContentJson])); var tmp = content; - if (tmp is TextMessageContent && messageOpenedAt != null) { + if (messageOpenedAt != null) { messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]); - if (messageOpenedAt != null) { + if (tmp is TextMessageContent && messageOpenedAt != null) { if ((DateTime.now()).difference(messageOpenedAt).inHours >= 24) { deleteTextContent(fromDb[i][columnMessageId], tmp); } diff --git a/lib/src/views/camera_to_share/share_image_view.dart b/lib/src/views/camera_to_share/share_image_view.dart index ad0a454..88e5f21 100644 --- a/lib/src/views/camera_to_share/share_image_view.dart +++ b/lib/src/views/camera_to_share/share_image_view.dart @@ -180,10 +180,7 @@ class _ShareImageView extends State { widget.isRealTwonly, widget.maxShowTime, ); - - // TODO: pop back to the HomeView page popUntil did not work. check later how to improve in case of pushing more then 2 - Navigator.pop(context); - Navigator.pop(context); + Navigator.popUntil(context, (route) => route.isFirst); globalUpdateOfHomeViewPageIndex(1); }, style: ButtonStyle( diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index f51dfd0..7a29738 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; +import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/json/message.dart'; @@ -11,6 +12,7 @@ import 'package:twonly/src/providers/download_change_provider.dart'; import 'package:twonly/src/providers/messages_change_provider.dart'; import 'package:twonly/src/views/chats/media_viewer_view.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/contact/contact_view.dart'; class ChatListEntry extends StatelessWidget { const ChatListEntry(this.message, this.user, this.lastMessageFromSameUser, @@ -187,7 +189,28 @@ class _ChatItemDetailsViewState extends State { } return Scaffold( appBar: AppBar( - title: Text(context.lang.chatListDetailTitle(widget.user.displayName)), + title: GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return ContactView(widget.user); + })); + }, + child: Row( + children: [ + InitialsAvatar( + displayName: widget.user.displayName, + fontSize: 19, + ), + SizedBox(width: 10), + Expanded( + child: Container( + color: Colors.transparent, + child: Text(widget.user.displayName), + ), + ), + ], + ), + ), ), body: Column( children: [ diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index 8edecb2..844f45e 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:provider/provider.dart'; import 'package:twonly/main.dart'; @@ -9,7 +10,6 @@ import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/providers/contacts_change_provider.dart'; import 'package:twonly/src/providers/api/api.dart'; -import 'package:twonly/src/views/onboarding/register_view.dart'; // ignore: library_prefixes import 'package:twonly/src/utils/signal.dart' as SignalHelper; diff --git a/lib/src/views/contact/contact_verify_view.dart b/lib/src/views/contact/contact_verify_view.dart new file mode 100644 index 0000000..8fa89ae --- /dev/null +++ b/lib/src/views/contact/contact_verify_view.dart @@ -0,0 +1,32 @@ +import 'package:twonly/src/model/contacts_model.dart'; +import 'package:flutter/material.dart'; + +class ContactVerifyView extends StatefulWidget { + const ContactVerifyView(this.contact, {super.key}); + + final Contact contact; + + @override + State createState() => _ContactVerifyViewState(); +} + +class _ContactVerifyViewState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Verify ${widget.contact.displayName}"), + ), + body: ListView( + children: [ + SizedBox(height: 50), + ], + ), + ); + } +} diff --git a/lib/src/views/contact/contact_view.dart b/lib/src/views/contact/contact_view.dart new file mode 100644 index 0000000..1af4aa1 --- /dev/null +++ b/lib/src/views/contact/contact_view.dart @@ -0,0 +1,140 @@ +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:twonly/src/components/alert_dialog.dart'; +import 'package:twonly/src/components/better_list_title.dart'; +import 'package:twonly/src/components/flame.dart'; +import 'package:twonly/src/components/initialsavatar.dart'; +import 'package:twonly/src/model/contacts_model.dart'; +import 'package:flutter/material.dart'; +import 'package:twonly/src/providers/messages_change_provider.dart'; +import 'package:twonly/src/views/contact/contact_verify_view.dart'; + +class ContactView extends StatefulWidget { + const ContactView(this.contact, {super.key}); + + final Contact contact; + + @override + State createState() => _ContactViewState(); +} + +class _ContactViewState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + int flameCounter = context + .watch() + .flamesCounter[widget.contact.userId.toInt()] ?? + 0; + + return Scaffold( + appBar: AppBar( + title: Text(""), + ), + body: ListView( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: InitialsAvatar( + displayName: widget.contact.displayName, + fontSize: 30, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.contact.displayName, + style: TextStyle(fontSize: 20), + ), + if (flameCounter > 0) + FlameCounterWidget(widget.contact, flameCounter, 110000000), + ], + ), + SizedBox(height: 50), + BetterListTile( + icon: FontAwesomeIcons.pencil, + text: "Nickname", + onTap: () async { + String? newUsername = + await showNicknameChangeDialog(context, widget.contact); + if (newUsername != null && newUsername != "") { + await DbContacts.changeDisplayName( + widget.contact.userId.toInt(), newUsername); + } + }, + ), + const Divider(), + BetterListTile( + icon: FontAwesomeIcons.shieldHeart, + text: "Verify safety number", + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) { + return ContactVerifyView(widget.contact); + }, + )); + }, + ), + BetterListTile( + icon: FontAwesomeIcons.ban, + color: Colors.red, + text: "Block", + onTap: () async { + bool block = await showAlertDialog( + context, + "Block ${widget.contact.displayName}", + "A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.."); + if (block) { + await DbContacts.blockUser(widget.contact.userId.toInt()); + // go back to the first + if (context.mounted) { + Navigator.popUntil(context, (route) => route.isFirst); + } + } + }, + ), + ], + ), + ); + } +} + +Future showNicknameChangeDialog( + BuildContext context, Contact contact) { + final TextEditingController controller = + TextEditingController(text: contact.displayName); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Change nickname'), + content: TextField( + controller: controller, + autofocus: true, + decoration: InputDecoration(hintText: "New nickname"), + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + ), + TextButton( + child: Text('Submit'), + onPressed: () { + String inputText = controller.text; + Navigator.of(context).pop(inputText); // Return the input text + }, + ), + ], + ); + }, + ); +} diff --git a/lib/src/views/onboarding/register_view.dart b/lib/src/views/onboarding/register_view.dart index 5d3f690..f6cf113 100644 --- a/lib/src/views/onboarding/register_view.dart +++ b/lib/src/views/onboarding/register_view.dart @@ -4,6 +4,7 @@ import 'package:logging/logging.dart'; import 'package:twonly/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/model/json/user_data.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/signal.dart'; @@ -186,43 +187,3 @@ class _RegisterViewState extends State { ); } } - -Future showAlertDialog( - BuildContext context, String title, String content) async { - Completer completer = Completer(); - // set up the button - Widget okButton = TextButton( - child: Text(context.lang.ok), - onPressed: () { - completer.complete(true); // Complete the future with true - Navigator.pop(context); - }, - ); - - Widget cancelButton = TextButton( - child: Text(context.lang.cancel), - onPressed: () { - completer.complete(false); // Complete the future with true - Navigator.pop(context); - }, - ); - - // set up the AlertDialog - AlertDialog alert = AlertDialog( - title: Text(title), - content: Text(content), - actions: [ - cancelButton, - okButton, - ], - ); - - // show the dialog - showDialog( - context: context, - builder: (BuildContext context) { - return alert; - }, - ); - return completer.future; -} diff --git a/lib/src/views/settings/account_view.dart b/lib/src/views/settings/account_view.dart index 3db0e7e..1675acd 100644 --- a/lib/src/views/settings/account_view.dart +++ b/lib/src/views/settings/account_view.dart @@ -1,8 +1,8 @@ import 'package:restart_app/restart_app.dart'; import 'package:flutter/material.dart'; +import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/onboarding/register_view.dart'; class AccountView extends StatelessWidget { const AccountView({super.key}); diff --git a/lib/src/views/settings/settings_main_view.dart b/lib/src/views/settings/settings_main_view.dart index d85500c..803b586 100644 --- a/lib/src/views/settings/settings_main_view.dart +++ b/lib/src/views/settings/settings_main_view.dart @@ -1,5 +1,6 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/components/better_list_title.dart'; import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/model/json/user_data.dart'; import 'package:flutter/material.dart'; @@ -79,7 +80,7 @@ class _ProfileViewState extends State { ], ), ), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.user, text: context.lang.settingsAccount, onTap: () { @@ -89,13 +90,13 @@ class _ProfileViewState extends State { })); }, ), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.shieldHeart, text: context.lang.settingsSubscription, onTap: () {}, ), const Divider(), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.sun, text: context.lang.settingsAppearance, onTap: () { @@ -105,7 +106,7 @@ class _ProfileViewState extends State { })); }, ), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.lock, text: context.lang.settingsPrivacy, onTap: () { @@ -115,7 +116,7 @@ class _ProfileViewState extends State { })); }, ), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.bell, text: context.lang.settingsNotification, onTap: () async { @@ -140,7 +141,7 @@ class _ProfileViewState extends State { }, ), const Divider(), - SettingsListTile( + BetterListTile( icon: FontAwesomeIcons.circleQuestion, text: context.lang.settingsHelp, onTap: () { @@ -156,34 +157,3 @@ class _ProfileViewState extends State { ); } } - -class SettingsListTile extends StatelessWidget { - final IconData icon; - final String text; - final VoidCallback onTap; - - const SettingsListTile({ - super.key, - required this.icon, - required this.text, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: Padding( - padding: const EdgeInsets.only( - right: 10, - left: 19, - ), - child: FaIcon( - icon, - size: 20, - ), - ), - title: Text(text), - onTap: onTap, - ); - } -}