hide users from search user name view

This commit is contained in:
otsmr 2025-04-08 16:42:42 +02:00
parent db8e4d4419
commit 42330594bb
8 changed files with 206 additions and 159 deletions

View file

@ -56,7 +56,7 @@ class _UserContextMenuState extends State<UserContextMenu> {
onSelect: () { onSelect: () {
Navigator.push(context, MaterialPageRoute( Navigator.push(context, MaterialPageRoute(
builder: (context) { builder: (context) {
return ChatItemDetailsView(widget.contact.userId); return ChatItemDetailsView(widget.contact);
}, },
)); ));
}, },

View file

@ -108,7 +108,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
} }
Stream<List<Contact>> watchNotAcceptedContacts() { Stream<List<Contact>> watchNotAcceptedContacts() {
return (select(contacts)..where((t) => t.accepted.equals(false))).watch(); return (select(contacts)
..where((t) => t.accepted.equals(false) & t.archived.equals(false)))
.watch();
// return (select(contacts)).watch(); // return (select(contacts)).watch();
} }

View file

@ -39,6 +39,14 @@
"searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.", "searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.",
"searchUsernameNewFollowerTitle": "Folgeanfragen", "searchUsernameNewFollowerTitle": "Folgeanfragen",
"searchUsernameQrCodeBtn": "QR-Code scannen", "searchUsernameQrCodeBtn": "QR-Code scannen",
"searchUserNamePending": "Ausstehend",
"@searchUserNamePending": {},
"searchUserNameBlockUserTooltip": "Benutzer ohne Benachrichtigung blockieren.",
"@searchUserNameBlockUserTooltip": {},
"searchUserNameRejectUserTooltip": "Die Anfrage ablehnen und den Anfragenden informieren.",
"@searchUserNameRejectUserTooltip": {},
"searchUserNameArchiveUserTooltip": "Benutzer archivieren. Du wirst informiert sobald er deine Anfrage akzeptiert.",
"@searchUserNameArchiveUserTooltip": {},
"chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!", "chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!",
"chatListViewSendFirstTwonly": "Sende dein erstes twonly!", "chatListViewSendFirstTwonly": "Sende dein erstes twonly!",
"chatListDetailInput": "Nachricht eingeben", "chatListDetailInput": "Nachricht eingeben",

View file

@ -68,6 +68,14 @@
"@searchUsernameInput": {}, "@searchUsernameInput": {},
"searchUsernameTitle": "Search username", "searchUsernameTitle": "Search username",
"@searchUsernameTitle": {}, "@searchUsernameTitle": {},
"searchUserNamePending": "Pending",
"@searchUserNamePending": {},
"searchUserNameBlockUserTooltip": "Block the user without informing.",
"@searchUserNameBlockUserTooltip": {},
"searchUserNameRejectUserTooltip": "Reject the request and let the requester know.",
"@searchUserNameRejectUserTooltip": {},
"searchUserNameArchiveUserTooltip": "Archive the user. He will appear again as soon as he accepts your request.",
"@searchUserNameArchiveUserTooltip": {},
"searchUsernameNotFound": "Username not found", "searchUsernameNotFound": "Username not found",
"@searchUsernameNotFound": {}, "@searchUsernameNotFound": {},
"searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered", "searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered",

View file

@ -209,9 +209,9 @@ class ChatListEntry extends StatelessWidget {
/// Displays detailed information about a SampleItem. /// Displays detailed information about a SampleItem.
class ChatItemDetailsView extends StatefulWidget { class ChatItemDetailsView extends StatefulWidget {
const ChatItemDetailsView(this.userid, {super.key}); const ChatItemDetailsView(this.contact, {super.key});
final int userid; final Contact contact;
@override @override
State<ChatItemDetailsView> createState() => _ChatItemDetailsViewState(); State<ChatItemDetailsView> createState() => _ChatItemDetailsViewState();
@ -220,7 +220,7 @@ class ChatItemDetailsView extends StatefulWidget {
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> { class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
TextEditingController newMessageController = TextEditingController(); TextEditingController newMessageController = TextEditingController();
HashSet<int> alreadyReportedOpened = HashSet<int>(); HashSet<int> alreadyReportedOpened = HashSet<int>();
Contact? user; late Contact user;
String currentInputText = ""; String currentInputText = "";
late StreamSubscription<Contact> userSub; late StreamSubscription<Contact> userSub;
late StreamSubscription<List<Message>> messageSub; late StreamSubscription<List<Message>> messageSub;
@ -231,6 +231,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
user = widget.contact;
initStreams(); initStreams();
} }
@ -244,7 +245,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
Future initStreams() async { Future initStreams() async {
await twonlyDatabase.messagesDao.removeOldMessages(); await twonlyDatabase.messagesDao.removeOldMessages();
Stream<Contact> contact = Stream<Contact> contact =
twonlyDatabase.contactsDao.watchContact(widget.userid); twonlyDatabase.contactsDao.watchContact(widget.contact.userId);
userSub = contact.listen((contact) { userSub = contact.listen((contact) {
setState(() { setState(() {
user = contact; user = contact;
@ -252,15 +253,14 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
}); });
Stream<List<Message>> msgStream = Stream<List<Message>> msgStream =
twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.userid); twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.contact.userId);
messageSub = msgStream.listen((msgs) { messageSub = msgStream.listen((msgs) {
if (!context.mounted) return; if (!context.mounted) return;
if (Platform.isAndroid) { if (Platform.isAndroid) {
flutterLocalNotificationsPlugin.cancel(widget.userid); flutterLocalNotificationsPlugin.cancel(widget.contact.userId);
} else { } else {
flutterLocalNotificationsPlugin.cancelAll(); flutterLocalNotificationsPlugin.cancelAll();
} }
var updated = false;
List<Message> displayedMessages = []; List<Message> displayedMessages = [];
// should be cleared // should be cleared
Map<int, List<Message>> tmpReactionsToMyMessages = {}; Map<int, List<Message>> tmpReactionsToMyMessages = {};
@ -271,7 +271,6 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
if (msg.kind == MessageKind.textMessage && if (msg.kind == MessageKind.textMessage &&
msg.messageOtherId != null && msg.messageOtherId != null &&
msg.openedAt == null) { msg.openedAt == null) {
updated = true;
openedMessageOtherIds.add(msg.messageOtherId!); openedMessageOtherIds.add(msg.messageOtherId!);
} }
@ -294,25 +293,27 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
} }
} }
if (openedMessageOtherIds.isNotEmpty) { if (openedMessageOtherIds.isNotEmpty) {
notifyContactAboutOpeningMessage(widget.userid, openedMessageOtherIds); notifyContactAboutOpeningMessage(
widget.contact.userId, openedMessageOtherIds);
} }
twonlyDatabase.messagesDao.openedAllNonMediaMessages(widget.userid); twonlyDatabase.messagesDao
.openedAllNonMediaMessages(widget.contact.userId);
// should be fixed with that // should be fixed with that
if (!updated) { // if (!updated) {
// The stream should be get an update, so only update the UI when all are opened // // The stream should be get an update, so only update the UI when all are opened
setState(() { setState(() {
reactionsToMyMessages = tmpReactionsToMyMessages; reactionsToMyMessages = tmpReactionsToMyMessages;
reactionsToOtherMessages = tmpTeactionsToOtherMessages; reactionsToOtherMessages = tmpTeactionsToOtherMessages;
messages = displayedMessages; messages = displayedMessages;
}); });
} // }
}); });
} }
Future _sendMessage() async { Future _sendMessage() async {
if (newMessageController.text == "" || user == null) return; if (newMessageController.text == "") return;
await sendTextMessage( await sendTextMessage(
user!.userId, user.userId,
TextMessageContent( TextMessageContent(
text: newMessageController.text, text: newMessageController.text,
), ),
@ -330,15 +331,13 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
title: GestureDetector( title: GestureDetector(
onTap: () { onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) { Navigator.push(context, MaterialPageRoute(builder: (context) {
return ContactView(widget.userid); return ContactView(widget.contact.userId);
})); }));
}, },
child: (user == null) child: Row(
? Container()
: Row(
children: [ children: [
ContactAvatar( ContactAvatar(
contact: user!, contact: user,
fontSize: 19, fontSize: 19,
), ),
SizedBox(width: 10), SizedBox(width: 10),
@ -347,9 +346,9 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
color: Colors.transparent, color: Colors.transparent,
child: Row( child: Row(
children: [ children: [
Text(getContactDisplayName(user!)), Text(getContactDisplayName(user)),
SizedBox(width: 10), SizedBox(width: 10),
VerifiedShield(user!), VerifiedShield(user),
], ],
), ),
), ),
@ -361,7 +360,6 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
body: SafeArea( body: SafeArea(
child: Column( child: Column(
children: [ children: [
if (user != null)
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: messages.length, itemCount: messages.length,
@ -383,12 +381,11 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
if (msg.messageOtherId != null && if (msg.messageOtherId != null &&
reactionsToOtherMessages reactionsToOtherMessages
.containsKey(msg.messageOtherId!)) { .containsKey(msg.messageOtherId!)) {
reactions = reactions = reactionsToOtherMessages[msg.messageOtherId!]!;
reactionsToOtherMessages[msg.messageOtherId!]!;
} }
return ChatListEntry( return ChatListEntry(
msg, msg,
user!, user,
lastMessageFromSameUser, lastMessageFromSameUser,
reactions, reactions,
); );

View file

@ -173,6 +173,14 @@ class _UserListItem extends State<UserListItem> {
lastUpdateTime(); lastUpdateTime();
} }
@override
void dispose() {
updateTime?.cancel();
messagesNotOpenedStream.cancel();
lastMessageStream.cancel();
super.dispose();
}
void initStreams() { void initStreams() {
lastMessageStream = twonlyDatabase.messagesDao lastMessageStream = twonlyDatabase.messagesDao
.watchLastMessage(widget.user.userId) .watchLastMessage(widget.user.userId)
@ -235,14 +243,6 @@ class _UserListItem extends State<UserListItem> {
}); });
} }
@override
void dispose() {
updateTime?.cancel();
super.dispose();
messagesNotOpenedStream.cancel();
lastMessageStream.cancel();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int flameCounter = getFlameCounterFromContact(widget.user); int flameCounter = getFlameCounterFromContact(widget.user);
@ -306,7 +306,7 @@ class _UserListItem extends State<UserListItem> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return ChatItemDetailsView(widget.user.userId); return ChatItemDetailsView(widget.user);
}), }),
); );
}, },

View file

@ -476,7 +476,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return ChatItemDetailsView(widget.contact.userId); return ChatItemDetailsView(widget.contact);
}), }),
); );
}, },

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:drift/drift.dart' hide Column; import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
@ -29,6 +30,30 @@ class _SearchUsernameView extends State<SearchUsernameView> {
bool _isLoading = false; bool _isLoading = false;
bool hasRequestedUsers = false; bool hasRequestedUsers = false;
List<Contact> contacts = [];
late StreamSubscription<List<Contact>> contactsStream;
@override
void initState() {
super.initState();
initStreams();
}
@override
void dispose() {
contactsStream.cancel();
super.dispose();
}
void initStreams() {
contactsStream =
twonlyDatabase.contactsDao.watchNotAcceptedContacts().listen((update) {
setState(() {
contacts = update;
});
});
}
Future _addNewUser(BuildContext context) async { Future _addNewUser(BuildContext context) async {
final user = await getUser(); final user = await getUser();
if (user == null || user.username == searchUserName.text) { if (user == null || user.username == searchUserName.text) {
@ -105,9 +130,6 @@ class _SearchUsernameView extends State<SearchUsernameView> {
); );
} }
Stream<List<Contact>> contacts =
twonlyDatabase.contactsDao.watchNotAcceptedContacts();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.lang.searchUsernameTitle), title: Text(context.lang.searchUsernameTitle),
@ -148,22 +170,12 @@ class _SearchUsernameView extends State<SearchUsernameView> {
label: Text(context.lang.searchUsernameQrCodeBtn), label: Text(context.lang.searchUsernameQrCodeBtn),
), ),
SizedBox(height: 30), SizedBox(height: 30),
if (hasRequestedUsers) if (contacts.isNotEmpty)
HeadLineComponent( HeadLineComponent(
context.lang.searchUsernameNewFollowerTitle, context.lang.searchUsernameNewFollowerTitle,
), ),
StreamBuilder( Expanded(
stream: contacts, child: ContactsListView(contacts),
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.data == null ||
snapshot.data!.isEmpty) {
hasRequestedUsers = false;
return Container();
}
hasRequestedUsers = true;
return Expanded(child: ContactsListView(snapshot.data!));
},
) )
], ],
), ),
@ -194,23 +206,27 @@ class ContactsListView extends StatefulWidget {
} }
class _ContactsListViewState extends State<ContactsListView> { class _ContactsListViewState extends State<ContactsListView> {
@override List<Widget> sendRequestActions(Contact contact) {
Widget build(BuildContext context) { return [
return ListView.builder(
itemCount: widget.contacts.length,
itemBuilder: (context, index) {
final contact = widget.contacts[index];
final displayName = getContactDisplayName(contact);
return ListTile(
title: Text(displayName),
leading: ContactAvatar(contact: contact),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!contact.requested) Text('Pending'),
if (contact.requested) ...[
Tooltip( Tooltip(
message: "Block the user without informing.", message: context.lang.searchUserNameArchiveUserTooltip,
child: IconButton(
icon: FaIcon(FontAwesomeIcons.boxArchive, size: 15),
onPressed: () async {
final update = ContactsCompanion(archived: Value(true));
await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
},
),
),
Text(context.lang.searchUserNamePending),
];
}
List<Widget> requestedActions(Contact contact) {
return [
Tooltip(
message: context.lang.searchUserNameBlockUserTooltip,
child: IconButton( child: IconButton(
icon: Icon(Icons.person_off_rounded, icon: Icon(Icons.person_off_rounded,
color: const Color.fromARGB(164, 244, 67, 54)), color: const Color.fromARGB(164, 244, 67, 54)),
@ -222,7 +238,7 @@ class _ContactsListViewState extends State<ContactsListView> {
), ),
), ),
Tooltip( Tooltip(
message: "Reject the request and let the requester know.", message: context.lang.searchUserNameRejectUserTooltip,
child: IconButton( child: IconButton(
icon: Icon(Icons.close, color: Colors.red), icon: Icon(Icons.close, color: Colors.red),
onPressed: () async { onPressed: () async {
@ -246,7 +262,7 @@ class _ContactsListViewState extends State<ContactsListView> {
final update = ContactsCompanion(accepted: Value(true)); final update = ContactsCompanion(accepted: Value(true));
await twonlyDatabase.contactsDao await twonlyDatabase.contactsDao
.updateContact(contact.userId, update); .updateContact(contact.userId, update);
encryptAndSendMessage( await encryptAndSendMessage(
null, null,
contact.userId, contact.userId,
MessageJson( MessageJson(
@ -259,8 +275,24 @@ class _ContactsListViewState extends State<ContactsListView> {
notifyContactsAboutProfileChange(); notifyContactsAboutProfileChange();
}, },
), ),
], ];
], }
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.contacts.length,
itemBuilder: (context, index) {
final contact = widget.contacts[index];
final displayName = getContactDisplayName(contact);
return ListTile(
title: Text(displayName),
leading: ContactAvatar(contact: contact),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: contact.requested
? requestedActions(contact)
: sendRequestActions(contact),
), ),
); );
}, },