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: () {
Navigator.push(context, MaterialPageRoute(
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() {
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();
}

View file

@ -39,6 +39,14 @@
"searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.",
"searchUsernameNewFollowerTitle": "Folgeanfragen",
"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!",
"chatListViewSendFirstTwonly": "Sende dein erstes twonly!",
"chatListDetailInput": "Nachricht eingeben",

View file

@ -68,6 +68,14 @@
"@searchUsernameInput": {},
"searchUsernameTitle": "Search username",
"@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": {},
"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.
class ChatItemDetailsView extends StatefulWidget {
const ChatItemDetailsView(this.userid, {super.key});
const ChatItemDetailsView(this.contact, {super.key});
final int userid;
final Contact contact;
@override
State<ChatItemDetailsView> createState() => _ChatItemDetailsViewState();
@ -220,7 +220,7 @@ class ChatItemDetailsView extends StatefulWidget {
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
TextEditingController newMessageController = TextEditingController();
HashSet<int> alreadyReportedOpened = HashSet<int>();
Contact? user;
late Contact user;
String currentInputText = "";
late StreamSubscription<Contact> userSub;
late StreamSubscription<List<Message>> messageSub;
@ -231,6 +231,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
@override
void initState() {
super.initState();
user = widget.contact;
initStreams();
}
@ -244,7 +245,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
Future initStreams() async {
await twonlyDatabase.messagesDao.removeOldMessages();
Stream<Contact> contact =
twonlyDatabase.contactsDao.watchContact(widget.userid);
twonlyDatabase.contactsDao.watchContact(widget.contact.userId);
userSub = contact.listen((contact) {
setState(() {
user = contact;
@ -252,15 +253,14 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
});
Stream<List<Message>> msgStream =
twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.userid);
twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.contact.userId);
messageSub = msgStream.listen((msgs) {
if (!context.mounted) return;
if (Platform.isAndroid) {
flutterLocalNotificationsPlugin.cancel(widget.userid);
flutterLocalNotificationsPlugin.cancel(widget.contact.userId);
} else {
flutterLocalNotificationsPlugin.cancelAll();
}
var updated = false;
List<Message> displayedMessages = [];
// should be cleared
Map<int, List<Message>> tmpReactionsToMyMessages = {};
@ -271,7 +271,6 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
if (msg.kind == MessageKind.textMessage &&
msg.messageOtherId != null &&
msg.openedAt == null) {
updated = true;
openedMessageOtherIds.add(msg.messageOtherId!);
}
@ -294,25 +293,27 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
}
}
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
if (!updated) {
// The stream should be get an update, so only update the UI when all are opened
setState(() {
reactionsToMyMessages = tmpReactionsToMyMessages;
reactionsToOtherMessages = tmpTeactionsToOtherMessages;
messages = displayedMessages;
});
}
// if (!updated) {
// // The stream should be get an update, so only update the UI when all are opened
setState(() {
reactionsToMyMessages = tmpReactionsToMyMessages;
reactionsToOtherMessages = tmpTeactionsToOtherMessages;
messages = displayedMessages;
});
// }
});
}
Future _sendMessage() async {
if (newMessageController.text == "" || user == null) return;
if (newMessageController.text == "") return;
await sendTextMessage(
user!.userId,
user.userId,
TextMessageContent(
text: newMessageController.text,
),
@ -330,71 +331,67 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
title: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return ContactView(widget.userid);
return ContactView(widget.contact.userId);
}));
},
child: (user == null)
? Container()
: Row(
children: [
ContactAvatar(
contact: user!,
fontSize: 19,
),
SizedBox(width: 10),
Expanded(
child: Container(
color: Colors.transparent,
child: Row(
children: [
Text(getContactDisplayName(user!)),
SizedBox(width: 10),
VerifiedShield(user!),
],
),
),
),
],
child: Row(
children: [
ContactAvatar(
contact: user,
fontSize: 19,
),
SizedBox(width: 10),
Expanded(
child: Container(
color: Colors.transparent,
child: Row(
children: [
Text(getContactDisplayName(user)),
SizedBox(width: 10),
VerifiedShield(user),
],
),
),
),
],
),
),
),
body: SafeArea(
child: Column(
children: [
if (user != null)
Expanded(
child: ListView.builder(
itemCount: messages.length,
reverse: true,
itemBuilder: (context, i) {
bool lastMessageFromSameUser = false;
if (i > 0) {
lastMessageFromSameUser =
(messages[i - 1].messageOtherId == null &&
messages[i].messageOtherId == null) ||
(messages[i - 1].messageOtherId != null &&
messages[i].messageOtherId != null);
}
Message msg = messages[i];
List<Message> reactions = [];
if (reactionsToMyMessages.containsKey(msg.messageId)) {
reactions = reactionsToMyMessages[msg.messageId]!;
}
if (msg.messageOtherId != null &&
reactionsToOtherMessages
.containsKey(msg.messageOtherId!)) {
reactions =
reactionsToOtherMessages[msg.messageOtherId!]!;
}
return ChatListEntry(
msg,
user!,
lastMessageFromSameUser,
reactions,
);
},
),
Expanded(
child: ListView.builder(
itemCount: messages.length,
reverse: true,
itemBuilder: (context, i) {
bool lastMessageFromSameUser = false;
if (i > 0) {
lastMessageFromSameUser =
(messages[i - 1].messageOtherId == null &&
messages[i].messageOtherId == null) ||
(messages[i - 1].messageOtherId != null &&
messages[i].messageOtherId != null);
}
Message msg = messages[i];
List<Message> reactions = [];
if (reactionsToMyMessages.containsKey(msg.messageId)) {
reactions = reactionsToMyMessages[msg.messageId]!;
}
if (msg.messageOtherId != null &&
reactionsToOtherMessages
.containsKey(msg.messageOtherId!)) {
reactions = reactionsToOtherMessages[msg.messageOtherId!]!;
}
return ChatListEntry(
msg,
user,
lastMessageFromSameUser,
reactions,
);
},
),
),
Padding(
padding: const EdgeInsets.only(
bottom: 30, left: 20, right: 20, top: 10),

View file

@ -173,6 +173,14 @@ class _UserListItem extends State<UserListItem> {
lastUpdateTime();
}
@override
void dispose() {
updateTime?.cancel();
messagesNotOpenedStream.cancel();
lastMessageStream.cancel();
super.dispose();
}
void initStreams() {
lastMessageStream = twonlyDatabase.messagesDao
.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
Widget build(BuildContext context) {
int flameCounter = getFlameCounterFromContact(widget.user);
@ -306,7 +306,7 @@ class _UserListItem extends State<UserListItem> {
Navigator.push(
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(
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:flutter/material.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/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
@ -29,6 +30,30 @@ class _SearchUsernameView extends State<SearchUsernameView> {
bool _isLoading = 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 {
final user = await getUser();
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(
appBar: AppBar(
title: Text(context.lang.searchUsernameTitle),
@ -148,22 +170,12 @@ class _SearchUsernameView extends State<SearchUsernameView> {
label: Text(context.lang.searchUsernameQrCodeBtn),
),
SizedBox(height: 30),
if (hasRequestedUsers)
if (contacts.isNotEmpty)
HeadLineComponent(
context.lang.searchUsernameNewFollowerTitle,
),
StreamBuilder(
stream: 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!));
},
Expanded(
child: ContactsListView(contacts),
)
],
),
@ -194,6 +206,78 @@ class ContactsListView extends StatefulWidget {
}
class _ContactsListViewState extends State<ContactsListView> {
List<Widget> sendRequestActions(Contact contact) {
return [
Tooltip(
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(
icon: Icon(Icons.person_off_rounded,
color: const Color.fromARGB(164, 244, 67, 54)),
onPressed: () async {
final update = ContactsCompanion(blocked: Value(true));
await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
},
),
),
Tooltip(
message: context.lang.searchUserNameRejectUserTooltip,
child: IconButton(
icon: Icon(Icons.close, color: Colors.red),
onPressed: () async {
await twonlyDatabase.contactsDao
.deleteContactByUserId(contact.userId);
encryptAndSendMessage(
null,
contact.userId,
MessageJson(
kind: MessageKind.rejectRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
);
},
),
),
IconButton(
icon: Icon(Icons.check, color: Colors.green),
onPressed: () async {
final update = ContactsCompanion(accepted: Value(true));
await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
await encryptAndSendMessage(
null,
contact.userId,
MessageJson(
kind: MessageKind.acceptRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
pushKind: PushKind.acceptRequest,
);
notifyContactsAboutProfileChange();
},
),
];
}
@override
Widget build(BuildContext context) {
return ListView.builder(
@ -206,61 +290,9 @@ class _ContactsListViewState extends State<ContactsListView> {
leading: ContactAvatar(contact: contact),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!contact.requested) Text('Pending'),
if (contact.requested) ...[
Tooltip(
message: "Block the user without informing.",
child: IconButton(
icon: Icon(Icons.person_off_rounded,
color: const Color.fromARGB(164, 244, 67, 54)),
onPressed: () async {
final update = ContactsCompanion(blocked: Value(true));
await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
},
),
),
Tooltip(
message: "Reject the request and let the requester know.",
child: IconButton(
icon: Icon(Icons.close, color: Colors.red),
onPressed: () async {
await twonlyDatabase.contactsDao
.deleteContactByUserId(contact.userId);
encryptAndSendMessage(
null,
contact.userId,
MessageJson(
kind: MessageKind.rejectRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
);
},
),
),
IconButton(
icon: Icon(Icons.check, color: Colors.green),
onPressed: () async {
final update = ContactsCompanion(accepted: Value(true));
await twonlyDatabase.contactsDao
.updateContact(contact.userId, update);
encryptAndSendMessage(
null,
contact.userId,
MessageJson(
kind: MessageKind.acceptRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
pushKind: PushKind.acceptRequest,
);
notifyContactsAboutProfileChange();
},
),
],
],
children: contact.requested
? requestedActions(contact)
: sendRequestActions(contact),
),
);
},