follow request reject and block works

This commit is contained in:
otsmr 2025-01-25 13:17:10 +01:00
parent 20c20eb1e1
commit 0da87f1712
18 changed files with 332 additions and 178 deletions

View file

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:twonly/src/providers/db_provider.dart'; import 'package:twonly/src/providers/db_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/src/providers/notify_provider.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'src/app.dart'; import 'src/app.dart';
import 'src/settings/settings_controller.dart'; import 'src/settings/settings_controller.dart';
@ -49,5 +51,12 @@ void main() async {
// return true; // return true;
// }); // });
runApp(MyApp(settingsController: settingsController)); runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => NotifyProvider()),
],
child: MyApp(settingsController: settingsController),
),
);
} }

View file

@ -1,4 +1,6 @@
import 'package:provider/provider.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:twonly/src/providers/notify_provider.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/onboarding_view.dart'; import 'package:twonly/src/views/onboarding_view.dart';
import 'package:twonly/src/views/home_view.dart'; import 'package:twonly/src/views/home_view.dart';
@ -30,13 +32,18 @@ class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Start the color animation
_startColorAnimation(); _startColorAnimation();
apiProvider.setConnectionStateCallback((isConnected) { apiProvider.setConnectionStateCallback((isConnected) {
setState(() { setState(() {
_isConnected = isConnected; _isConnected = isConnected;
}); });
}); });
apiProvider.setUpdatedContacts(() {
context.read<NotifyProvider>().update();
});
context.read<NotifyProvider>().update();
apiProvider.connect(); apiProvider.connect();
} }

View file

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
enum MessageSendState {
opened,
received,
send,
sending,
}
class MessageSendStateIcon extends StatelessWidget {
final MessageSendState state;
const MessageSendStateIcon({super.key, required this.state});
@override
Widget build(BuildContext context) {
Widget icon = Placeholder();
String text = "";
switch (state) {
case MessageSendState.opened:
icon = Icon(
Icons.crop_square,
size: 14,
color: Theme.of(context).colorScheme.primary,
);
text = "Opened";
break;
case MessageSendState.received:
icon = Icon(
Icons.square_rounded,
size: 14,
color: Theme.of(context).colorScheme.primary,
);
text = "Received";
break;
case MessageSendState.send:
icon = Icon(
Icons.send,
size: 14,
);
text = "Send";
break;
case MessageSendState.sending:
icon = Row(
children: [
SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(
strokeWidth: 1,
),
),
SizedBox(width: 2),
],
);
text = "Sending";
break;
}
return Row(
children: [
icon,
const SizedBox(width: 3),
Text(text, style: TextStyle(fontSize: 12)),
const SizedBox(width: 5),
],
);
}
}

View file

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class NotificationBadge extends StatelessWidget {
final int count;
final Widget child;
const NotificationBadge(
{super.key, required this.count, required this.child});
@override
Widget build(BuildContext context) {
if (count == 0) return child;
return Stack(
children: [
child,
Positioned(
right: 5,
top: 0,
child: Container(
padding: EdgeInsets.all(5.0), // Add some padding
decoration: BoxDecoration(
color: Colors.red, // Background color
shape: BoxShape.circle, // Make it circular
),
child: Center(
child: Text(
count.toString(),
style: TextStyle(
color: Colors.white, // Text color
fontSize: 10,
),
),
),
),
)
],
);
}
}

View file

@ -16,6 +16,8 @@
"searchUsernameInput": "Username", "searchUsernameInput": "Username",
"searchUsernameTitle": "Search username", "searchUsernameTitle": "Search username",
"searchUsernameNotFound": "Username not found", "searchUsernameNotFound": "Username not found",
"searchUsernameNewFollowerTitle": "Follow requests",
"searchUsernameQrCodeBtn": "Scan QR code",
"searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.", "searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
"errorUnknown": "An unexpected error has occurred. Please try again later.", "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.", "errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",

View file

@ -30,6 +30,9 @@ class DbContacts extends CvModelBase {
static const columnRequested = "requested"; static const columnRequested = "requested";
final requested = CvField<int>(columnRequested); final requested = CvField<int>(columnRequested);
static const columnBlocked = "blocked";
final blocked = CvField<int>(columnBlocked);
static const columnCreatedAt = "created_at"; static const columnCreatedAt = "created_at";
final createdAt = CvField<DateTime>(columnCreatedAt); final createdAt = CvField<DateTime>(columnCreatedAt);
@ -40,6 +43,7 @@ class DbContacts extends CvModelBase {
$columnDisplayName TEXT, $columnDisplayName TEXT,
$columnAccepted INT NOT NULL DEFAULT 0, $columnAccepted INT NOT NULL DEFAULT 0,
$columnRequested INT NOT NULL DEFAULT 0, $columnRequested INT NOT NULL DEFAULT 0,
$columnBlocked INT NOT NULL DEFAULT 0,
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP $columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
) )
"""; """;
@ -47,22 +51,23 @@ class DbContacts extends CvModelBase {
@override @override
List<CvField> get fields => List<CvField> get fields =>
[userId, displayName, accepted, requested, createdAt]; [userId, displayName, accepted, requested, blocked, createdAt];
static Future<List<Contact>> getUsers() async { static Future<List<Contact>> getUsers() async {
try { try {
var users = await dbProvider.db!.query(tableName, columns: [ var users = await dbProvider.db!.query(tableName,
columnUserId, columns: [
columnDisplayName, columnUserId,
columnAccepted, columnDisplayName,
columnRequested, columnAccepted,
columnCreatedAt columnRequested,
]); columnCreatedAt
],
where: "$columnBlocked = 0");
if (users.isEmpty) return []; if (users.isEmpty) return [];
List<Contact> parsedUsers = []; List<Contact> parsedUsers = [];
for (int i = 0; i < users.length; i++) { for (int i = 0; i < users.length; i++) {
print(users[i]);
parsedUsers.add( parsedUsers.add(
Contact( Contact(
userId: Int64(users.cast()[i][columnUserId]), userId: Int64(users.cast()[i][columnUserId]),
@ -79,6 +84,26 @@ class DbContacts extends CvModelBase {
} }
} }
static Future blockUser(int userId) async {
Map<String, dynamic> valuesToUpdate = {
columnBlocked: 1,
};
await dbProvider.db!.update(
tableName,
valuesToUpdate,
where: "$columnUserId = ?",
whereArgs: [userId],
);
}
static Future deleteUser(int userId) async {
await dbProvider.db!.delete(
tableName,
where: "$columnUserId = ?",
whereArgs: [userId],
);
}
static Future<bool> insertNewContact( static Future<bool> insertNewContact(
String username, int userId, bool requested) async { String username, int userId, bool requested) async {
try { try {

View file

@ -3,7 +3,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:twonly/src/utils/json.dart'; import 'package:twonly/src/utils/json.dart';
part 'message.g.dart'; part 'message.g.dart';
enum MessageKind { textMessage, image, video, contactRequest } enum MessageKind { textMessage, image, video, contactRequest, rejectRequest }
// so _$MessageKindEnumMap gets generated // so _$MessageKindEnumMap gets generated
@JsonSerializable() @JsonSerializable()

View file

@ -19,6 +19,7 @@ const _$MessageKindEnumMap = {
MessageKind.image: 'image', MessageKind.image: 'image',
MessageKind.video: 'video', MessageKind.video: 'video',
MessageKind.contactRequest: 'contactRequest', MessageKind.contactRequest: 'contactRequest',
MessageKind.rejectRequest: 'rejectRequest',
}; };
Message _$MessageFromJson(Map<String, dynamic> json) => Message( Message _$MessageFromJson(Map<String, dynamic> json) => Message(

View file

@ -42,6 +42,7 @@ class ApiProvider {
bool _tryingToConnect = false; bool _tryingToConnect = false;
final log = Logger("api_provider"); final log = Logger("api_provider");
Function(bool)? _connectionStateCallback; Function(bool)? _connectionStateCallback;
Function? _updatedContacts;
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap(); final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
@ -109,6 +110,10 @@ class ApiProvider {
_connectionStateCallback = callBack; _connectionStateCallback = callBack;
} }
void setUpdatedContacts(Function callBack) {
_updatedContacts = callBack;
}
void tryToReconnect() { void tryToReconnect() {
if (_tryingToConnect) return; if (_tryingToConnect) return;
_tryingToConnect = true; _tryingToConnect = true;
@ -157,13 +162,22 @@ class ApiProvider {
Int64 fromUserId = msg.v0.newMessage.fromUserId; Int64 fromUserId = msg.v0.newMessage.fromUserId;
Message? message = await SignalHelper.getDecryptedText(fromUserId, body); Message? message = await SignalHelper.getDecryptedText(fromUserId, body);
if (message != null) { if (message != null) {
Result username = await getUsername(fromUserId); switch (message.kind) {
if (username.isSuccess) { case MessageKind.contactRequest:
print(username.value); Result username = await getUsername(fromUserId);
Uint8List name = username.value.userdata.username; if (username.isSuccess) {
DbContacts.insertNewContact( Uint8List name = username.value.userdata.username;
utf8.decode(name), fromUserId.toInt(), true); DbContacts.insertNewContact(
print(message); utf8.decode(name), fromUserId.toInt(), true);
updateNotifier();
}
break;
case MessageKind.rejectRequest:
DbContacts.deleteUser(fromUserId.toInt());
updateNotifier();
break;
default:
log.shout("Got unknown MessageKind $message");
} }
} }
var ok = client.Response_Ok()..none = true; var ok = client.Response_Ok()..none = true;
@ -182,6 +196,12 @@ class ApiProvider {
_channel!.sink.add(resBytes); _channel!.sink.add(resBytes);
} }
Future updateNotifier() async {
if (_updatedContacts != null) {
_updatedContacts!();
}
}
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async { Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
final startTime = DateTime.now(); final startTime = DateTime.now();

View file

@ -0,0 +1,29 @@
import 'package:flutter/foundation.dart';
import 'package:twonly/src/model/contacts_model.dart';
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class NotifyProvider with ChangeNotifier, DiagnosticableTreeMixin {
int _newContactRequests = 0;
List<Contact> _allContacts = [];
int get newContactRequests => _newContactRequests;
List<Contact> get allContacts => _allContacts;
void update() async {
_allContacts = await DbContacts.getUsers();
_newContactRequests = _allContacts
.where((contact) => !contact.accepted && contact.requested)
.length;
print(_newContactRequests);
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', newContactRequests));
}
}

View file

@ -38,7 +38,6 @@ class ConnectPreKeyStore extends PreKeyStore {
@override @override
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async { Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
if (!await containsPreKey(preKeyId)) { if (!await containsPreKey(preKeyId)) {
print(preKeyId);
await dbProvider.db!.insert(DB.tableName, await dbProvider.db!.insert(DB.tableName,
{DB.columnPreKeyId: preKeyId, DB.columnPreKey: record.serialize()}); {DB.columnPreKeyId: preKeyId, DB.columnPreKey: record.serialize()});
} else { } else {

View file

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
@ -44,6 +45,25 @@ Future<bool> addNewContact(String username) async {
return res.isSuccess; return res.isSuccess;
} }
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
if (bytes == null) {
Logger("utils/api").shout("Error encryption message!");
return Result.error(ErrorCode.InternalError);
}
Result resp = await apiProvider.sendTextMessage(userId, bytes);
return resp;
}
Future<Result> rejectUserRequest(Int64 userId) async {
Message msg =
Message(kind: MessageKind.rejectRequest, timestamp: DateTime.now());
return encryptAndSendMessage(userId, msg);
}
Future<Result> createNewUser(String username, String inviteCode) async { Future<Result> createNewUser(String username, String inviteCode) async {
final storage = getSecureStorage(); final storage = getSecureStorage();

View file

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';

View file

@ -1,5 +1,8 @@
import 'package:twonly/src/components/initialsavatar_component.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/providers/notify_provider.dart';
import 'package:twonly/src/views/search_username_view.dart'; import 'package:twonly/src/views/search_username_view.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'new_message_view.dart'; import 'new_message_view.dart';
@ -7,8 +10,6 @@ import 'package:flutter/material.dart';
import 'chat_item_details_view.dart'; import 'chat_item_details_view.dart';
import 'dart:async'; import 'dart:async';
enum MessageSendState { sending, send, opened, received }
class ChatItem { class ChatItem {
const ChatItem( const ChatItem(
{required this.username, {required this.username,
@ -57,19 +58,12 @@ class ChatListView extends StatefulWidget {
class _ChatListViewState extends State<ChatListView> { class _ChatListViewState extends State<ChatListView> {
int _secondsSinceOpen = 0; int _secondsSinceOpen = 0;
int _newContactRequests = 0;
late Timer _timer; late Timer _timer;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_startTimer(); _startTimer();
_checkNewContactRequests();
}
Future _checkNewContactRequests() async {
_newContactRequests = (await DbContacts.getUsers()).length;
setState(() {});
} }
void _startTimer() { void _startTimer() {
@ -101,57 +95,12 @@ class _ChatListViewState extends State<ChatListView> {
} }
} }
Widget getMessageSateIcon(MessageSendState state) {
List<Widget> children = [];
Widget icon = Placeholder();
String text = "";
switch (state) {
case MessageSendState.opened:
icon = Icon(
Icons.crop_square,
size: 14,
color: Theme.of(context).colorScheme.primary,
);
text = "Opened";
break;
case MessageSendState.received:
icon = Icon(Icons.square_rounded,
size: 14, color: Theme.of(context).colorScheme.primary);
text = "Received";
break;
case MessageSendState.send:
icon = Icon(Icons.send, size: 14);
text = "Send";
break;
case MessageSendState.sending:
icon = Row(children: [
SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(
strokeWidth: 1,
)),
SizedBox(width: 2)
]);
text = "Sending";
break;
}
children.add(const SizedBox(width: 5));
return Row(
children: [
icon,
const SizedBox(width: 3),
Text(text, style: TextStyle(fontSize: 12)),
const SizedBox(width: 5)
],
);
}
Widget getSubtitle(ChatItem item) { Widget getSubtitle(ChatItem item) {
return Row( return Row(
children: [ children: [
getMessageSateIcon(item.state), MessageSendStateIcon(
state: item.state,
),
Text(""), Text(""),
const SizedBox(width: 5), const SizedBox(width: 5),
Text(formatDuration(item.lastMessageInSeconds + _secondsSinceOpen), Text(formatDuration(item.lastMessageInSeconds + _secondsSinceOpen),
@ -180,62 +129,42 @@ class _ChatListViewState extends State<ChatListView> {
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.chatsTitle), title: Text(AppLocalizations.of(context)!.chatsTitle),
actions: [ actions: [
Stack( NotificationBadge(
children: [ count: context.watch<NotifyProvider>().newContactRequests,
IconButton( child: IconButton(
icon: Icon(Icons.person_add), // User with add icon icon: Icon(Icons.person_add),
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => SearchUsernameView(), builder: (context) => SearchUsernameView(),
),
);
},
),
if (_newContactRequests > 0)
Positioned(
right: 5,
top: 0,
child: Container(
padding: EdgeInsets.all(5.0), // Add some padding
decoration: BoxDecoration(
color: Colors.red, // Background color
shape: BoxShape.circle, // Make it circular
),
child: Center(
child: Text(
_newContactRequests.toString(),
style: TextStyle(
color: Colors.white, // Text color
fontSize: 10),
),
),
), ),
), );
], },
),
) )
], ],
), ),
body: ListView.builder( body: ListView.builder(
restorationId: 'sampleItemListView', restorationId: 'chat_list_view',
itemCount: widget.items.length, itemCount: widget.items.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = widget.items[index]; final item = widget.items[index];
return ListTile( return ListTile(
title: Text(item.username), title: Text(item.username),
subtitle: getSubtitle(item), subtitle: getSubtitle(item),
leading: InitialsAvatar(displayName: item.username), leading: InitialsAvatar(displayName: item.username),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => SampleItemDetailsView( builder: (context) => SampleItemDetailsView(
userId: item.userId, userId: item.userId,
),
), ),
); ),
}); );
},
);
}, },
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:twonly/src/components/initialsavatar_component.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/views/search_username_view.dart'; import 'package:twonly/src/views/search_username_view.dart';

View file

@ -2,7 +2,10 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/providers/notify_provider.dart';
import 'package:twonly/src/utils/api.dart'; import 'package:twonly/src/utils/api.dart';
import 'package:twonly/src/views/register_view.dart'; import 'package:twonly/src/views/register_view.dart';
@ -28,11 +31,9 @@ class _SearchUsernameView extends State<SearchUsernameView> {
_isLoading = false; _isLoading = false;
}); });
Logger("search_user_name").warning("Replace instead of pop");
if (context.mounted) { if (context.mounted) {
if (status) { if (status) {
// Navigator.pop(context); context.read<NotifyProvider>().update();
} else if (context.mounted) { } else if (context.mounted) {
showAlertDialog( showAlertDialog(
context, context,
@ -88,17 +89,19 @@ class _SearchUsernameView extends State<SearchUsernameView> {
showAlertDialog(context, "Coming soon", showAlertDialog(context, "Coming soon",
"This feature is not yet implemented!"); "This feature is not yet implemented!");
}, },
label: Text("QR-Code scannen"), label:
Text(AppLocalizations.of(context)!.searchUsernameQrCodeBtn),
), ),
SizedBox(height: 30), SizedBox(height: 30),
Container( if (context.read<NotifyProvider>().allContacts.isNotEmpty)
alignment: Alignment.centerLeft, Container(
padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 10), alignment: Alignment.centerLeft,
child: Text( padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 10),
"Neue Followanfragen", child: Text(
style: TextStyle(fontSize: 20), AppLocalizations.of(context)!.searchUsernameNewFollowerTitle,
style: TextStyle(fontSize: 20),
),
), ),
),
Expanded( Expanded(
child: ContactsListView(), child: ContactsListView(),
) )
@ -126,53 +129,55 @@ class ContactsListView extends StatefulWidget {
} }
class _ContactsListViewState extends State<ContactsListView> { class _ContactsListViewState extends State<ContactsListView> {
List<Contact> _allContacts = [];
@override
void initState() {
super.initState();
_loadContacts();
}
Future _loadContacts() async {
List<Contact> allContacts = await DbContacts.getUsers();
_allContacts = allContacts.where((contact) => !contact.accepted).toList();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Contact> contacts = context.read<NotifyProvider>().allContacts;
return ListView.builder( return ListView.builder(
itemCount: _allContacts.length, itemCount: contacts.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final contact = _allContacts[index]; final contact = contacts[index];
if (!contact.requested) {
return ListTile(
title: Text(contact.displayName),
subtitle: Text('Pending'),
);
}
return ListTile( return ListTile(
title: Text(contact.displayName), title: Text(contact.displayName),
leading: InitialsAvatar(displayName: contact.displayName),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: (!contact.requested)
IconButton( ? [Text('Pending')]
icon: Icon(Icons.close, color: Colors.red), : [
onPressed: () { Tooltip(
// Handle reject action message: "Block the user without informing.",
print('Rejected ${contact.displayName}'); child: IconButton(
}, icon: Icon(Icons.person_off_rounded,
), color: const Color.fromARGB(164, 244, 67, 54)),
IconButton( onPressed: () async {
icon: Icon(Icons.check, color: Colors.green), await DbContacts.blockUser(contact.userId.toInt());
onPressed: () { if (context.mounted) {
// Handle accept action context.read<NotifyProvider>().update();
print('Accepted ${contact.displayName}'); }
}, },
), ),
], ),
Tooltip(
message: "Reject the request and let the requester know.",
child: IconButton(
icon: Icon(Icons.close, color: Colors.red),
onPressed: () async {
await DbContacts.deleteUser(contact.userId.toInt());
if (context.mounted) {
context.read<NotifyProvider>().update();
}
rejectUserRequest(contact.userId);
},
),
),
IconButton(
icon: Icon(Icons.check, color: Colors.green),
onPressed: () {
// Handle accept action
print('Accepted ${contact.displayName}');
},
),
],
), ),
); );
}, },

View file

@ -3,7 +3,7 @@ import 'dart:collection';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:twonly/src/components/initialsavatar_component.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
class ShareImageView extends StatefulWidget { class ShareImageView extends StatefulWidget {