mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +00:00
block users via settings
This commit is contained in:
parent
1b47c11aaa
commit
ac91e954f7
9 changed files with 366 additions and 161 deletions
14
README.md
14
README.md
|
|
@ -4,18 +4,22 @@ Don't be lonely, get twonly! Send pictures to a friend in real time and be sure
|
||||||
|
|
||||||
|
|
||||||
## TODOS bevor first beta
|
## TODOS bevor first beta
|
||||||
- Settings
|
|
||||||
- Delete and Block active users
|
|
||||||
- MessageKind -> Ausbauen?
|
- MessageKind -> Ausbauen?
|
||||||
- Nachrichten nach 24h Stunden löschen
|
- Nachrichten nach 24h Stunden löschen
|
||||||
- Real deployment aufsetzen, direkt auf Netcup?
|
|
||||||
- Pro Invitation codes
|
- Pro Invitation codes
|
||||||
- Push Notification (Android)
|
- Push Notification (Android)
|
||||||
- FIX: Problem Bild falsch, wenn handy schräg...
|
- Settings
|
||||||
|
- Notification
|
||||||
|
- Real deployment aufsetzen, direkt auf Netcup?
|
||||||
- MediaView:
|
- MediaView:
|
||||||
- Bei weiteren geladenen Bildern -> Direkt anzeigen ohne zu popen
|
- Bei weiteren geladenen Bildern -> Direkt anzeigen ohne pop
|
||||||
|
|
||||||
|
## Not my issues
|
||||||
|
- FIX: Problem Bild falsch, wenn handy schräg... -> Issue already openend
|
||||||
|
|
||||||
## TODOS bevor first release
|
## TODOS bevor first release
|
||||||
|
- Settings
|
||||||
|
- Subscription
|
||||||
- Webpage
|
- Webpage
|
||||||
- Instagam & Marketing vorbereiten
|
- Instagam & Marketing vorbereiten
|
||||||
- IT-Startup der TU Darmstadt anschreiben
|
- IT-Startup der TU Darmstadt anschreiben
|
||||||
|
|
|
||||||
137
lib/main.dart
137
lib/main.dart
|
|
@ -1,8 +1,4 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
import 'package:twonly/src/providers/api_provider.dart';
|
||||||
|
|
@ -13,141 +9,13 @@ import 'package:twonly/src/providers/download_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/settings_change_provider.dart';
|
import 'package:twonly/src/providers/settings_change_provider.dart';
|
||||||
|
import 'package:twonly/src/services/notification_service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'src/app.dart';
|
import 'src/app.dart';
|
||||||
|
|
||||||
late DbProvider dbProvider;
|
late DbProvider dbProvider;
|
||||||
late ApiProvider apiProvider;
|
late ApiProvider apiProvider;
|
||||||
|
|
||||||
/// Streams are created so that app can respond to notification-related events
|
|
||||||
/// since the plugin is initialized in the `main` function
|
|
||||||
final StreamController<NotificationResponse> selectNotificationStream =
|
|
||||||
StreamController<NotificationResponse>.broadcast();
|
|
||||||
|
|
||||||
const MethodChannel platform =
|
|
||||||
MethodChannel('dexterx.dev/flutter_local_notifications_example');
|
|
||||||
|
|
||||||
const String portName = 'notification_send_port';
|
|
||||||
|
|
||||||
class ReceivedNotification {
|
|
||||||
ReceivedNotification({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.body,
|
|
||||||
required this.payload,
|
|
||||||
this.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
final int id;
|
|
||||||
final String? title;
|
|
||||||
final String? body;
|
|
||||||
final String? payload;
|
|
||||||
final Map<String, dynamic>? data;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? selectedNotificationPayload;
|
|
||||||
|
|
||||||
/// A notification action which triggers a url launch event
|
|
||||||
const String urlLaunchActionId = 'id_1';
|
|
||||||
|
|
||||||
/// A notification action which triggers a App navigation event
|
|
||||||
const String navigationActionId = 'id_3';
|
|
||||||
|
|
||||||
/// Defines a iOS/MacOS notification category for text input actions.
|
|
||||||
const String darwinNotificationCategoryText = 'textCategory';
|
|
||||||
|
|
||||||
/// Defines a iOS/MacOS notification category for plain actions.
|
|
||||||
const String darwinNotificationCategoryPlain = 'plainCategory';
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
|
||||||
// ignore: avoid_print
|
|
||||||
print('notification(${notificationResponse.id}) action tapped: '
|
|
||||||
'${notificationResponse.actionId} with'
|
|
||||||
' payload: ${notificationResponse.payload}');
|
|
||||||
if (notificationResponse.input?.isNotEmpty ?? false) {
|
|
||||||
// ignore: avoid_print
|
|
||||||
print(
|
|
||||||
'notification action tapped with input: ${notificationResponse.input}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
||||||
FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
int id = 0;
|
|
||||||
|
|
||||||
Future<void> setupPushNotification() async {
|
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
|
||||||
AndroidInitializationSettings("logo");
|
|
||||||
|
|
||||||
final List<DarwinNotificationCategory> darwinNotificationCategories =
|
|
||||||
<DarwinNotificationCategory>[
|
|
||||||
DarwinNotificationCategory(
|
|
||||||
darwinNotificationCategoryText,
|
|
||||||
actions: <DarwinNotificationAction>[
|
|
||||||
DarwinNotificationAction.text(
|
|
||||||
'text_1',
|
|
||||||
'Action 1',
|
|
||||||
buttonTitle: 'Send',
|
|
||||||
placeholder: 'Placeholder',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
DarwinNotificationCategory(
|
|
||||||
darwinNotificationCategoryPlain,
|
|
||||||
actions: <DarwinNotificationAction>[
|
|
||||||
DarwinNotificationAction.plain('id_1', 'Action 1'),
|
|
||||||
DarwinNotificationAction.plain(
|
|
||||||
'id_2',
|
|
||||||
'Action 2 (destructive)',
|
|
||||||
options: <DarwinNotificationActionOption>{
|
|
||||||
DarwinNotificationActionOption.destructive,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DarwinNotificationAction.plain(
|
|
||||||
navigationActionId,
|
|
||||||
'Action 3 (foreground)',
|
|
||||||
options: <DarwinNotificationActionOption>{
|
|
||||||
DarwinNotificationActionOption.foreground,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DarwinNotificationAction.plain(
|
|
||||||
'id_4',
|
|
||||||
'Action 4 (auth required)',
|
|
||||||
options: <DarwinNotificationActionOption>{
|
|
||||||
DarwinNotificationActionOption.authenticationRequired,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options: <DarwinNotificationCategoryOption>{
|
|
||||||
DarwinNotificationCategoryOption.hiddenPreviewShowTitle,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Note: permissions aren't requested here just to demonstrate that can be
|
|
||||||
/// done later
|
|
||||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
|
||||||
DarwinInitializationSettings(
|
|
||||||
requestAlertPermission: false,
|
|
||||||
requestBadgePermission: false,
|
|
||||||
requestSoundPermission: false,
|
|
||||||
notificationCategories: darwinNotificationCategories,
|
|
||||||
);
|
|
||||||
|
|
||||||
final InitializationSettings initializationSettings = InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsDarwin,
|
|
||||||
);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
|
||||||
initializationSettings,
|
|
||||||
onDidReceiveNotificationResponse: selectNotificationStream.add,
|
|
||||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final settingsController = SettingsChangeProvider();
|
final settingsController = SettingsChangeProvider();
|
||||||
|
|
||||||
|
|
@ -167,8 +35,7 @@ void main() async {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setupPushNotification();
|
await setupPushNotification();
|
||||||
|
|
||||||
await initMediaStorage();
|
await initMediaStorage();
|
||||||
|
|
||||||
dbProvider = DbProvider();
|
dbProvider = DbProvider();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@
|
||||||
"settingsSubscription": "Subscription",
|
"settingsSubscription": "Subscription",
|
||||||
"settingsAppearance": "Appearance",
|
"settingsAppearance": "Appearance",
|
||||||
"settingsPrivacy": "Privacy",
|
"settingsPrivacy": "Privacy",
|
||||||
|
"settingsPrivacyBlockUsers": "Block users",
|
||||||
|
"settingsPrivacyBlockUsersDesc": "Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.",
|
||||||
|
"settingsPrivacyBlockUsersCount": "{len} contact(s)",
|
||||||
"settingsNotification": "Notification",
|
"settingsNotification": "Notification",
|
||||||
"settingsHelp": "Help",
|
"settingsHelp": "Help",
|
||||||
"settingsHelpSupport": "Support Center",
|
"settingsHelpSupport": "Support Center",
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ class Contact {
|
||||||
{required this.userId,
|
{required this.userId,
|
||||||
required this.displayName,
|
required this.displayName,
|
||||||
required this.accepted,
|
required this.accepted,
|
||||||
|
required this.blocked,
|
||||||
required this.totalMediaCounter,
|
required this.totalMediaCounter,
|
||||||
required this.requested});
|
required this.requested});
|
||||||
final Int64 userId;
|
final Int64 userId;
|
||||||
final String displayName;
|
final String displayName;
|
||||||
final bool accepted;
|
final bool accepted;
|
||||||
final bool requested;
|
final bool requested;
|
||||||
|
final bool blocked;
|
||||||
final int totalMediaCounter;
|
final int totalMediaCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +66,21 @@ class DbContacts extends CvModelBase {
|
||||||
[userId, displayName, accepted, requested, blocked, createdAt];
|
[userId, displayName, accepted, requested, blocked, createdAt];
|
||||||
|
|
||||||
static Future<List<Contact>> getActiveUsers() async {
|
static Future<List<Contact>> getActiveUsers() async {
|
||||||
return (await getUsers()).where((u) => u.accepted).toList();
|
return (await _getAllUsers())
|
||||||
|
.where((u) => u.accepted && !u.blocked)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<Contact>> getBlockedUsers() async {
|
||||||
|
return (await _getAllUsers()).where((u) => u.blocked).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<Contact>> getUsers() async {
|
||||||
|
return (await _getAllUsers()).where((u) => !u.blocked).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<Contact>> getAllUsers() async {
|
||||||
|
return await _getAllUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future checkAndUpdateFlames(int userId, {DateTime? timestamp}) async {
|
static Future checkAndUpdateFlames(int userId, {DateTime? timestamp}) async {
|
||||||
|
|
@ -96,18 +112,17 @@ class DbContacts extends CvModelBase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Contact>> getUsers() async {
|
static Future<List<Contact>> _getAllUsers() async {
|
||||||
try {
|
try {
|
||||||
var users = await dbProvider.db!.query(tableName,
|
var users = await dbProvider.db!.query(tableName, columns: [
|
||||||
columns: [
|
columnUserId,
|
||||||
columnUserId,
|
columnDisplayName,
|
||||||
columnDisplayName,
|
columnAccepted,
|
||||||
columnAccepted,
|
columnRequested,
|
||||||
columnRequested,
|
columnBlocked,
|
||||||
columnTotalMediaCounter,
|
columnTotalMediaCounter,
|
||||||
columnCreatedAt
|
columnCreatedAt
|
||||||
],
|
]);
|
||||||
where: "$columnBlocked = 0");
|
|
||||||
if (users.isEmpty) return [];
|
if (users.isEmpty) return [];
|
||||||
|
|
||||||
List<Contact> parsedUsers = [];
|
List<Contact> parsedUsers = [];
|
||||||
|
|
@ -119,6 +134,7 @@ class DbContacts extends CvModelBase {
|
||||||
totalMediaCounter: users.cast()[i][columnTotalMediaCounter],
|
totalMediaCounter: users.cast()[i][columnTotalMediaCounter],
|
||||||
displayName: users.cast()[i][columnDisplayName],
|
displayName: users.cast()[i][columnDisplayName],
|
||||||
accepted: users[i][columnAccepted] == 1,
|
accepted: users[i][columnAccepted] == 1,
|
||||||
|
blocked: users[i][columnBlocked] == 1,
|
||||||
requested: users[i][columnRequested] == 1,
|
requested: users[i][columnRequested] == 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -130,9 +146,9 @@ class DbContacts extends CvModelBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future blockUser(int userId) async {
|
static Future blockUser(int userId, {bool unblock = false}) async {
|
||||||
Map<String, dynamic> valuesToUpdate = {
|
Map<String, dynamic> valuesToUpdate = {
|
||||||
columnBlocked: 1,
|
columnBlocked: unblock ? 0 : 1,
|
||||||
};
|
};
|
||||||
await dbProvider.db!.update(
|
await dbProvider.db!.update(
|
||||||
tableName,
|
tableName,
|
||||||
|
|
|
||||||
132
lib/src/services/notification_service.dart
Normal file
132
lib/src/services/notification_service.dart
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
|
||||||
|
/// Streams are created so that app can respond to notification-related events
|
||||||
|
/// since the plugin is initialized in the `main` function
|
||||||
|
final StreamController<NotificationResponse> selectNotificationStream =
|
||||||
|
StreamController<NotificationResponse>.broadcast();
|
||||||
|
|
||||||
|
const MethodChannel platform = MethodChannel('twonly.eu/notifications');
|
||||||
|
|
||||||
|
const String portName = 'notification_send_port';
|
||||||
|
|
||||||
|
class ReceivedNotification {
|
||||||
|
ReceivedNotification({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
required this.payload,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String? title;
|
||||||
|
final String? body;
|
||||||
|
final String? payload;
|
||||||
|
final Map<String, dynamic>? data;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? selectedNotificationPayload;
|
||||||
|
|
||||||
|
/// A notification action which triggers a url launch event
|
||||||
|
const String urlLaunchActionId = 'id_1';
|
||||||
|
|
||||||
|
/// A notification action which triggers a App navigation event
|
||||||
|
const String navigationActionId = 'id_3';
|
||||||
|
|
||||||
|
/// Defines a iOS/MacOS notification category for text input actions.
|
||||||
|
const String darwinNotificationCategoryText = 'textCategory';
|
||||||
|
|
||||||
|
/// Defines a iOS/MacOS notification category for plain actions.
|
||||||
|
const String darwinNotificationCategoryPlain = 'plainCategory';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print('notification(${notificationResponse.id}) action tapped: '
|
||||||
|
'${notificationResponse.actionId} with'
|
||||||
|
' payload: ${notificationResponse.payload}');
|
||||||
|
if (notificationResponse.input?.isNotEmpty ?? false) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(
|
||||||
|
'notification action tapped with input: ${notificationResponse.input}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
Future<void> setupPushNotification() async {
|
||||||
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||||
|
AndroidInitializationSettings("logo");
|
||||||
|
|
||||||
|
final List<DarwinNotificationCategory> darwinNotificationCategories =
|
||||||
|
<DarwinNotificationCategory>[
|
||||||
|
DarwinNotificationCategory(
|
||||||
|
darwinNotificationCategoryText,
|
||||||
|
actions: <DarwinNotificationAction>[
|
||||||
|
DarwinNotificationAction.text(
|
||||||
|
'text_1',
|
||||||
|
'Action 1',
|
||||||
|
buttonTitle: 'Send',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
DarwinNotificationCategory(
|
||||||
|
darwinNotificationCategoryPlain,
|
||||||
|
actions: <DarwinNotificationAction>[
|
||||||
|
DarwinNotificationAction.plain('id_1', 'Action 1'),
|
||||||
|
DarwinNotificationAction.plain(
|
||||||
|
'id_2',
|
||||||
|
'Action 2 (destructive)',
|
||||||
|
options: <DarwinNotificationActionOption>{
|
||||||
|
DarwinNotificationActionOption.destructive,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DarwinNotificationAction.plain(
|
||||||
|
navigationActionId,
|
||||||
|
'Action 3 (foreground)',
|
||||||
|
options: <DarwinNotificationActionOption>{
|
||||||
|
DarwinNotificationActionOption.foreground,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DarwinNotificationAction.plain(
|
||||||
|
'id_4',
|
||||||
|
'Action 4 (auth required)',
|
||||||
|
options: <DarwinNotificationActionOption>{
|
||||||
|
DarwinNotificationActionOption.authenticationRequired,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options: <DarwinNotificationCategoryOption>{
|
||||||
|
DarwinNotificationCategoryOption.hiddenPreviewShowTitle,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Note: permissions aren't requested here just to demonstrate that can be
|
||||||
|
/// done later
|
||||||
|
final DarwinInitializationSettings initializationSettingsDarwin =
|
||||||
|
DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: false,
|
||||||
|
requestBadgePermission: false,
|
||||||
|
requestSoundPermission: false,
|
||||||
|
notificationCategories: darwinNotificationCategories,
|
||||||
|
);
|
||||||
|
|
||||||
|
final InitializationSettings initializationSettings = InitializationSettings(
|
||||||
|
android: initializationSettingsAndroid,
|
||||||
|
iOS: initializationSettingsDarwin,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
|
initializationSettings,
|
||||||
|
onDidReceiveNotificationResponse: selectNotificationStream.add,
|
||||||
|
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -44,12 +44,10 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadAsync() async {
|
Future<void> _loadAsync() async {
|
||||||
final users = await DbContacts.getActiveUsers();
|
_users = await DbContacts.getActiveUsers();
|
||||||
setState(() {
|
_updateUsers(_users);
|
||||||
_users = users;
|
|
||||||
_updateUsers(_users);
|
|
||||||
});
|
|
||||||
imageBytes = await widget.imageBytesFuture;
|
imageBytes = await widget.imageBytesFuture;
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _updateUsers(List<Contact> users) async {
|
Future _updateUsers(List<Contact> users) async {
|
||||||
|
|
|
||||||
52
lib/src/views/settings/privacy_view.dart
Normal file
52
lib/src/views/settings/privacy_view.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/settings/privacy_view_block_users.dart';
|
||||||
|
|
||||||
|
class PrivacyView extends StatefulWidget {
|
||||||
|
const PrivacyView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PrivacyView> createState() => _PrivacyViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrivacyViewState extends State<PrivacyView> {
|
||||||
|
List<Contact> blockedUsers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
updateBlockedUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future updateBlockedUsers() async {
|
||||||
|
blockedUsers = await DbContacts.getBlockedUsers();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.settingsPrivacy),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.settingsPrivacyBlockUsers),
|
||||||
|
subtitle: Text(
|
||||||
|
context.lang.settingsPrivacyBlockUsersCount(blockedUsers.length),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return PrivacyViewBlockUsers();
|
||||||
|
}));
|
||||||
|
updateBlockedUsers();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
lib/src/views/settings/privacy_view_block_users.dart
Normal file
127
lib/src/views/settings/privacy_view_block_users.dart
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class PrivacyViewBlockUsers extends StatefulWidget {
|
||||||
|
const PrivacyViewBlockUsers({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PrivacyViewBlockUsers> createState() => _PrivacyViewBlockUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
|
List<Contact> allUsers = [];
|
||||||
|
List<Contact> filteredUsers = [];
|
||||||
|
String lastQuery = "";
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future loadAsync() async {
|
||||||
|
allUsers = await DbContacts.getAllUsers();
|
||||||
|
_filterUsers(lastQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _filterUsers(String query) async {
|
||||||
|
lastQuery = query;
|
||||||
|
if (query.isEmpty) {
|
||||||
|
filteredUsers = allUsers;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filteredUsers = allUsers
|
||||||
|
.where((user) =>
|
||||||
|
user.displayName.toLowerCase().contains(query.toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.settingsPrivacyBlockUsers),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: TextField(
|
||||||
|
onChanged: _filterUsers,
|
||||||
|
decoration: getInputDecoration(
|
||||||
|
context,
|
||||||
|
context.lang.searchUsernameInput,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
context.lang.settingsPrivacyBlockUsersDesc,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Expanded(
|
||||||
|
child: UserList(
|
||||||
|
List.from(filteredUsers),
|
||||||
|
updateStatus: () {
|
||||||
|
loadAsync();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserList extends StatelessWidget {
|
||||||
|
const UserList(this.users, {super.key, required this.updateStatus});
|
||||||
|
final List<Contact> users;
|
||||||
|
final Function updateStatus;
|
||||||
|
|
||||||
|
Future block(bool? value, int userId) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await DbContacts.blockUser(userId, unblock: !value);
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Step 1: Sort the users alphabetically
|
||||||
|
users.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
restorationId: 'new_message_users_list',
|
||||||
|
itemCount: users.length,
|
||||||
|
itemBuilder: (BuildContext context, int i) {
|
||||||
|
Contact user = users[i];
|
||||||
|
print(user.blocked);
|
||||||
|
return ListTile(
|
||||||
|
title: Row(children: [
|
||||||
|
Text(user.displayName),
|
||||||
|
]),
|
||||||
|
leading: InitialsAvatar(
|
||||||
|
displayName: user.displayName,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: user.blocked,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
print(value);
|
||||||
|
block(value, user.userId.toInt());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
block(!user.blocked, user.userId.toInt());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/main.dart';
|
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/model/json/user_data.dart';
|
import 'package:twonly/src/model/json/user_data.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/services/notification_service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/settings/account_view.dart';
|
import 'package:twonly/src/views/settings/account_view.dart';
|
||||||
import 'package:twonly/src/views/settings/appearance_view.dart';
|
import 'package:twonly/src/views/settings/appearance_view.dart';
|
||||||
import 'package:twonly/src/views/settings/help_view.dart';
|
import 'package:twonly/src/views/settings/help_view.dart';
|
||||||
|
import 'package:twonly/src/views/settings/privacy_view.dart';
|
||||||
|
|
||||||
class ProfileView extends StatefulWidget {
|
class ProfileView extends StatefulWidget {
|
||||||
const ProfileView({super.key});
|
const ProfileView({super.key});
|
||||||
|
|
@ -107,7 +108,12 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
icon: FontAwesomeIcons.lock,
|
icon: FontAwesomeIcons.lock,
|
||||||
text: context.lang.settingsPrivacy,
|
text: context.lang.settingsPrivacy,
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return PrivacyView();
|
||||||
|
}));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
icon: FontAwesomeIcons.bell,
|
icon: FontAwesomeIcons.bell,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue