mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:28:40 +00:00
start using drift
This commit is contained in:
parent
00ad654660
commit
f7306fe7db
48 changed files with 3138 additions and 807 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
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';
|
||||||
|
|
||||||
late DbProvider dbProvider;
|
|
||||||
late ApiProvider apiProvider;
|
late ApiProvider apiProvider;
|
||||||
|
late DbProvider dbProvider;
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/database/database.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';
|
||||||
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/download_change_provider.dart';
|
import 'package:twonly/src/providers/db_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/send_next_media_to.dart';
|
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||||
import 'package:twonly/src/providers/settings_change_provider.dart';
|
import 'package:twonly/src/providers/settings_change_provider.dart';
|
||||||
import 'package:twonly/src/services/fcm_service.dart';
|
import 'package:twonly/src/services/fcm_service.dart';
|
||||||
|
|
@ -49,9 +47,10 @@ void main() async {
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => MessagesChangeProvider()),
|
Provider<TwonlyDatabase>(
|
||||||
ChangeNotifierProvider(create: (_) => DownloadChangeProvider()),
|
create: (context) => TwonlyDatabase(),
|
||||||
ChangeNotifierProvider(create: (_) => ContactChangeProvider()),
|
dispose: (context, db) => db.close(),
|
||||||
|
),
|
||||||
ChangeNotifierProvider(create: (_) => SendNextMediaTo()),
|
ChangeNotifierProvider(create: (_) => SendNextMediaTo()),
|
||||||
ChangeNotifierProvider(create: (_) => settingsController),
|
ChangeNotifierProvider(create: (_) => settingsController),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/components/connection_state.dart';
|
import 'package:twonly/src/components/connection_state.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/download_change_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/messages_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/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/onboarding/onboarding_view.dart';
|
import 'package:twonly/src/views/onboarding/onboarding_view.dart';
|
||||||
|
|
@ -23,9 +20,6 @@ Function(bool) globalCallbackConnectionState = (a) {};
|
||||||
bool globalIsAppInBackground = true;
|
bool globalIsAppInBackground = true;
|
||||||
|
|
||||||
// these two callbacks are called on updated to the corresponding database
|
// these two callbacks are called on updated to the corresponding database
|
||||||
Function globalCallBackOnContactChange = () {};
|
|
||||||
Future Function(int, int?) globalCallBackOnMessageChange = (a, b) async {};
|
|
||||||
Function(List<int>, bool) globalCallBackOnDownloadChange = (a, b) {};
|
|
||||||
|
|
||||||
/// The Widget that configures your application.
|
/// The Widget that configures your application.
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatefulWidget {
|
||||||
|
|
@ -45,10 +39,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||||
globalIsAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
// init change provider to load data from the database
|
|
||||||
context.read<ContactChangeProvider>().update();
|
|
||||||
context.read<MessagesChangeProvider>().init();
|
|
||||||
|
|
||||||
// register global callbacks to the widget tree
|
// register global callbacks to the widget tree
|
||||||
globalCallbackConnectionState = (isConnected) {
|
globalCallbackConnectionState = (isConnected) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -56,20 +46,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
globalCallBackOnContactChange = () {
|
|
||||||
context.read<ContactChangeProvider>().update();
|
|
||||||
};
|
|
||||||
|
|
||||||
globalCallBackOnDownloadChange = (token, add) {
|
|
||||||
context.read<DownloadChangeProvider>().update(token, add);
|
|
||||||
};
|
|
||||||
|
|
||||||
globalCallBackOnMessageChange = (userId, messageId) async {
|
|
||||||
await context
|
|
||||||
.read<MessagesChangeProvider>()
|
|
||||||
.updateLastMessageFor(userId, messageId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// _requestPermissions();
|
// _requestPermissions();
|
||||||
// _initService();
|
// _initService();
|
||||||
|
|
@ -124,8 +100,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||||
if (wasPaused) {
|
if (wasPaused) {
|
||||||
globalIsAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
apiProvider.connect();
|
apiProvider.connect();
|
||||||
context.read<ContactChangeProvider>().update();
|
|
||||||
context.read<MessagesChangeProvider>().init();
|
|
||||||
// _stopService();
|
// _stopService();
|
||||||
}
|
}
|
||||||
} else if (state == AppLifecycleState.paused) {
|
} else if (state == AppLifecycleState.paused) {
|
||||||
|
|
@ -145,9 +119,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
// disable globalCallbacks to the flutter tree
|
// disable globalCallbacks to the flutter tree
|
||||||
globalCallbackConnectionState = (a) {};
|
globalCallbackConnectionState = (a) {};
|
||||||
globalCallBackOnDownloadChange = (a, b) {};
|
|
||||||
globalCallBackOnContactChange = () {};
|
|
||||||
globalCallBackOnMessageChange = (a, b) async {};
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:twonly/src/components/verified_shield.dart';
|
import 'package:twonly/src/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/components/flame.dart';
|
import 'package:twonly/src/components/flame.dart';
|
||||||
import 'package:twonly/src/components/headline.dart';
|
import 'package:twonly/src/components/headline.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
|
|
||||||
class BestFriendsSelector extends StatelessWidget {
|
class BestFriendsSelector extends StatelessWidget {
|
||||||
final List<Contact> users;
|
final List<Contact> users;
|
||||||
final Function(Int64, bool) updateStatus;
|
final Function(int, bool) updateStatus;
|
||||||
final HashSet<Int64> selectedUserIds;
|
final HashSet<int> selectedUserIds;
|
||||||
final int maxTotalMediaCounter;
|
final int maxTotalMediaCounter;
|
||||||
final bool isRealTwonly;
|
final bool isRealTwonly;
|
||||||
|
|
||||||
|
|
@ -80,7 +78,7 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
|
|
||||||
class UserCheckbox extends StatelessWidget {
|
class UserCheckbox extends StatelessWidget {
|
||||||
final Contact user;
|
final Contact user;
|
||||||
final Function(Int64, bool) onChanged;
|
final Function(int, bool) onChanged;
|
||||||
final bool isChecked;
|
final bool isChecked;
|
||||||
final bool isRealTwonly;
|
final bool isRealTwonly;
|
||||||
final int maxTotalMediaCounter;
|
final int maxTotalMediaCounter;
|
||||||
|
|
@ -96,10 +94,7 @@ class UserCheckbox extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int flameCounter = context
|
String displayName = getContactDisplayName(user);
|
||||||
.watch<MessagesChangeProvider>()
|
|
||||||
.flamesCounter[user.userId.toInt()] ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding:
|
padding:
|
||||||
|
|
@ -120,8 +115,8 @@ class UserCheckbox extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
InitialsAvatar(
|
InitialsAvatar(
|
||||||
|
displayName,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
displayName: user.displayName,
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -138,16 +133,23 @@ class UserCheckbox extends StatelessWidget {
|
||||||
size: 12,
|
size: 12,
|
||||||
)),
|
)),
|
||||||
Text(
|
Text(
|
||||||
user.displayName.length > 10
|
displayName.length > 10
|
||||||
? '${user.displayName.substring(0, 10)}...'
|
? '${displayName.substring(0, 10)}...'
|
||||||
: user.displayName,
|
: displayName,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (flameCounter > 0)
|
StreamBuilder(
|
||||||
FlameCounterWidget(
|
stream: context.db.watchFlameCounter(user.userId),
|
||||||
user, flameCounter, maxTotalMediaCounter),
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData && snapshot.data! != 0) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
return FlameCounterWidget(
|
||||||
|
user, snapshot.data!, maxTotalMediaCounter);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(child: Container()),
|
Expanded(child: Container()),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/animate_icon.dart';
|
import 'package:twonly/src/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
|
|
||||||
class FlameCounterWidget extends StatelessWidget {
|
class FlameCounterWidget extends StatelessWidget {
|
||||||
final Contact user;
|
final Contact user;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ class InitialsAvatar extends StatelessWidget {
|
||||||
final String displayName;
|
final String displayName;
|
||||||
final double? fontSize;
|
final double? fontSize;
|
||||||
|
|
||||||
const InitialsAvatar(
|
const InitialsAvatar(this.displayName, {super.key, this.fontSize = 20});
|
||||||
{super.key, required this.displayName, this.fontSize = 20});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
|
import 'package:twonly/src/database/messages_db.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
import 'package:twonly/src/providers/download_change_provider.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
enum MessageSendState {
|
enum MessageSendState {
|
||||||
|
|
@ -15,21 +15,75 @@ enum MessageSendState {
|
||||||
sending,
|
sending,
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageSendStateIcon extends StatelessWidget {
|
MessageSendState messageSendStateFromMessage(Message msg) {
|
||||||
final DbMessage message;
|
MessageSendState state;
|
||||||
|
|
||||||
|
if (!msg.acknowledgeByServer) {
|
||||||
|
state = MessageSendState.sending;
|
||||||
|
} else {
|
||||||
|
if (msg.messageOtherId == null) {
|
||||||
|
// message send
|
||||||
|
if (msg.openedAt == null) {
|
||||||
|
state = MessageSendState.send;
|
||||||
|
} else {
|
||||||
|
state = MessageSendState.sendOpened;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// message received
|
||||||
|
if (msg.openedAt == null) {
|
||||||
|
state = MessageSendState.received;
|
||||||
|
} else {
|
||||||
|
state = MessageSendState.receivedOpened;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageSendStateIcon extends StatefulWidget {
|
||||||
|
final List<Message> messages;
|
||||||
final MainAxisAlignment mainAxisAlignment;
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
|
|
||||||
const MessageSendStateIcon(this.message,
|
const MessageSendStateIcon(this.messages,
|
||||||
{super.key, this.mainAxisAlignment = MainAxisAlignment.end});
|
{super.key, this.mainAxisAlignment = MainAxisAlignment.end});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageSendStateIcon> createState() => _MessageSendStateIconState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
|
bool containsVideo = false;
|
||||||
|
bool containsText = false;
|
||||||
|
bool containsImage = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
for (Message msg in widget.messages) {
|
||||||
|
if (msg.kind == MessageKind.textMessage) {
|
||||||
|
containsText = true;
|
||||||
|
}
|
||||||
|
if (msg.kind == MessageKind.media) {
|
||||||
|
MessageJson message =
|
||||||
|
MessageJson.fromJson(jsonDecode(msg.contentJson!));
|
||||||
|
final content = message.content;
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.isVideo) {
|
||||||
|
containsVideo = true;
|
||||||
|
} else {
|
||||||
|
containsImage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget icon = Placeholder();
|
Widget icon = Placeholder();
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
Color color =
|
|
||||||
message.messageContent.getColor(Theme.of(context).colorScheme.primary);
|
|
||||||
|
|
||||||
Widget loaderIcon = Row(
|
Widget loaderIcon = Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -41,7 +95,9 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (message.getSendState()) {
|
MessageSendState state = messageSendStateFromMessage(message);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
icon = Icon(Icons.crop_square, size: 14, color: color);
|
icon = Icon(Icons.crop_square, size: 14, color: color);
|
||||||
text = context.lang.messageSendState_Received;
|
text = context.lang.messageSendState_Received;
|
||||||
|
|
@ -65,24 +121,16 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.isDownloaded) {
|
if (message.downloadState == DownloadState.pending) {
|
||||||
text = context.lang.messageSendState_TapToLoad;
|
text = context.lang.messageSendState_TapToLoad;
|
||||||
}
|
}
|
||||||
|
if (message.downloadState == DownloadState.downloaded) {
|
||||||
bool isDownloading = false;
|
|
||||||
final content = message.messageContent;
|
|
||||||
if (message.messageReceived && content is MediaMessageContent) {
|
|
||||||
final test = context.watch<DownloadChangeProvider>().currentlyDownloading;
|
|
||||||
isDownloading = test.contains(content.downloadToken.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDownloading) {
|
|
||||||
text = context.lang.messageSendState_Loading;
|
text = context.lang.messageSendState_Loading;
|
||||||
icon = loaderIcon;
|
icon = loaderIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: mainAxisAlignment,
|
mainAxisAlignment: widget.mainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:pie_menu/pie_menu.dart';
|
import 'package:pie_menu/pie_menu.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/providers/send_next_media_to.dart';
|
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
|
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
|
||||||
import 'package:twonly/src/views/contact/contact_verify_view.dart';
|
import 'package:twonly/src/views/contact/contact_verify_view.dart';
|
||||||
import 'package:twonly/src/views/home_view.dart';
|
import 'package:twonly/src/views/home_view.dart';
|
||||||
|
|
||||||
class UserContextMenu extends StatefulWidget {
|
class UserContextMenu extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Contact user;
|
final Contact contact;
|
||||||
|
|
||||||
const UserContextMenu({super.key, required this.user, required this.child});
|
const UserContextMenu(
|
||||||
|
{super.key, required this.contact, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UserContextMenu> createState() => _UserContextMenuState();
|
State<UserContextMenu> createState() => _UserContextMenuState();
|
||||||
|
|
@ -22,38 +24,38 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PieMenu(
|
return PieMenu(
|
||||||
onPressed: () => print('pressed'),
|
onPressed: () => (),
|
||||||
actions: [
|
actions: [
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: const Text('Verify user'),
|
tooltip: Text(context.lang.contextMenuVerifyUser),
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContactVerifyView(widget.user);
|
return ContactVerifyView(widget.contact);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
child: widget.user.verified
|
child: widget.contact.verified
|
||||||
? FaIcon(FontAwesomeIcons.shieldHeart)
|
? FaIcon(FontAwesomeIcons.shieldHeart)
|
||||||
: const Icon(Icons.gpp_maybe_rounded), // Can be any widget
|
: const Icon(Icons.gpp_maybe_rounded),
|
||||||
),
|
),
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: const Text('Open chat'),
|
tooltip: Text(context.lang.contextMenuOpenChat),
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ChatItemDetailsView(user: widget.user);
|
return ChatItemDetailsView(user: widget.contact);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.solidComments),
|
child: const FaIcon(FontAwesomeIcons.solidComments),
|
||||||
),
|
),
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: const Text('Send image'),
|
tooltip: Text(context.lang.contextMenuSendImage),
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
context
|
context
|
||||||
.read<SendNextMediaTo>()
|
.read<SendNextMediaTo>()
|
||||||
.updateSendNextMediaTo(widget.user.userId.toInt());
|
.updateSendNextMediaTo(widget.contact.userId.toInt());
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.camera),
|
child: const FaIcon(FontAwesomeIcons.camera),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
|
|
||||||
class VerifiedShield extends StatelessWidget {
|
class VerifiedShield extends StatelessWidget {
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
|
|
|
||||||
53
lib/src/database/contacts_db.dart
Normal file
53
lib/src/database/contacts_db.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
|
|
||||||
|
class Contacts extends Table {
|
||||||
|
IntColumn get userId => integer()();
|
||||||
|
|
||||||
|
TextColumn get username => text().unique()();
|
||||||
|
TextColumn get displayName => text().nullable()();
|
||||||
|
TextColumn get nickName => text().nullable()();
|
||||||
|
|
||||||
|
BoolColumn get accepted => boolean().withDefault(Constant(false))();
|
||||||
|
BoolColumn get requested => boolean().withDefault(Constant(false))();
|
||||||
|
BoolColumn get blocked => boolean().withDefault(Constant(false))();
|
||||||
|
BoolColumn get verified => boolean().withDefault(Constant(false))();
|
||||||
|
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
IntColumn get totalMediaCounter => integer().withDefault(Constant(0))();
|
||||||
|
|
||||||
|
DateTimeColumn get lastMessageSend => dateTime().nullable()();
|
||||||
|
DateTimeColumn get lastMessageReceived => dateTime().nullable()();
|
||||||
|
DateTimeColumn get lastMessage => dateTime().nullable()();
|
||||||
|
|
||||||
|
IntColumn get flameCounter => integer().withDefault(Constant(0))();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {userId};
|
||||||
|
}
|
||||||
|
|
||||||
|
String getContactDisplayName(Contact user) {
|
||||||
|
if (user.nickName != null) {
|
||||||
|
return user.nickName!;
|
||||||
|
}
|
||||||
|
if (user.displayName != null) {
|
||||||
|
return user.displayName!;
|
||||||
|
}
|
||||||
|
return user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFlameCounterFromContact(Contact contact) {
|
||||||
|
if (contact.lastMessageSend == null || contact.lastMessageReceived == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final now = DateTime.now();
|
||||||
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||||
|
final twoDaysAgo = startOfToday.subtract(Duration(days: 2));
|
||||||
|
if (contact.lastMessageSend!.isBefore(twoDaysAgo) &&
|
||||||
|
contact.lastMessageReceived!.isBefore(twoDaysAgo)) {
|
||||||
|
return contact.flameCounter;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
lib/src/database/database.dart
Normal file
113
lib/src/database/database.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/messages_db.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
// You can then create a database class that includes this table
|
||||||
|
@DriftDatabase(tables: [Contacts, Messages])
|
||||||
|
class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
|
TwonlyDatabase() : super(_openConnection());
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
static QueryExecutor _openConnection() {
|
||||||
|
return driftDatabase(
|
||||||
|
name: 'twonly_main_db',
|
||||||
|
native: const DriftNativeOptions(
|
||||||
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
|
||||||
|
Stream<List<Message>> watchMessageNotOpened(int userId) {
|
||||||
|
return (select(messages)
|
||||||
|
..where((t) => t.openedAt.isNull() & t.contactId.equals(userId)))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Message?> watchLastMessage(int userId) {
|
||||||
|
return (select(messages)
|
||||||
|
..where((t) => t.contactId.equals(userId))
|
||||||
|
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])
|
||||||
|
..limit(1))
|
||||||
|
.watchSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
|
||||||
|
Future<int> insertContact(ContactsCompanion contact) {
|
||||||
|
return into(contacts).insert(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleOrNullSelectable<Contact> getContactByUserId(int userId) {
|
||||||
|
return select(contacts)..where((t) => t.userId.equals(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream<int> getMaxTotalMediaCounter() {
|
||||||
|
// return customSelect(
|
||||||
|
// 'SELECT MAX(totalMediaCounter) AS maxTotal FROM contacts',
|
||||||
|
// readsFrom: {contacts},
|
||||||
|
// ).watchSingle().asyncMap((result) {
|
||||||
|
// return result.read<int>('maxTotal');
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future deleteContactByUserId(int userId) {
|
||||||
|
return (delete(contacts)..where((t) => t.userId.equals(userId))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future updateContact(int userId, ContactsCompanion updatedValues) {
|
||||||
|
return (update(contacts)..where((c) => c.userId.equals(userId)))
|
||||||
|
.write(updatedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||||
|
return (select(contacts)..where((t) => t.accepted.equals(false))).watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<Contact>> watchContactsForChatList() {
|
||||||
|
return (select(contacts)
|
||||||
|
..where((t) => t.accepted.equals(true) & t.blocked.equals(false))
|
||||||
|
..orderBy([(t) => OrderingTerm.asc(t.lastMessage)]))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int?> watchContactsBlocked() {
|
||||||
|
final count = contacts.blocked.count(distinct: true);
|
||||||
|
final query = selectOnly(contacts)..where(contacts.blocked.equals(true));
|
||||||
|
query.addColumns([count]);
|
||||||
|
return query.map((row) => row.read(count)).watchSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int?> watchContactsRequested() {
|
||||||
|
final count = contacts.requested.count(distinct: true);
|
||||||
|
final query = selectOnly(contacts)..where(contacts.requested.equals(true));
|
||||||
|
query.addColumns([count]);
|
||||||
|
return query.map((row) => row.read(count)).watchSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<Contact>> watchAllContacts() {
|
||||||
|
return select(contacts).watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int> watchFlameCounter(int userId) {
|
||||||
|
return (select(contacts)
|
||||||
|
..where(
|
||||||
|
(u) =>
|
||||||
|
u.userId.equals(userId) &
|
||||||
|
u.lastMessageReceived.isNotNull() &
|
||||||
|
u.lastMessageSend.isNotNull(),
|
||||||
|
))
|
||||||
|
.watchSingle()
|
||||||
|
.asyncMap((contact) {
|
||||||
|
return getFlameCounterFromContact(contact);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
2186
lib/src/database/database.g.dart
Normal file
2186
lib/src/database/database.g.dart
Normal file
File diff suppressed because it is too large
Load diff
32
lib/src/database/messages_db.dart
Normal file
32
lib/src/database/messages_db.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
||||||
|
enum DownloadState {
|
||||||
|
pending,
|
||||||
|
downloading,
|
||||||
|
downloaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Messages extends Table {
|
||||||
|
IntColumn get contactId => integer().references(Contacts, #userId)();
|
||||||
|
|
||||||
|
IntColumn get messageId => integer().autoIncrement()();
|
||||||
|
IntColumn get messageOtherId => integer().nullable()();
|
||||||
|
|
||||||
|
IntColumn get responseToMessageId => integer().nullable()();
|
||||||
|
IntColumn get responseToOtherMessageId => integer().nullable()();
|
||||||
|
|
||||||
|
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))();
|
||||||
|
IntColumn get downloadState => intEnum<DownloadState>()();
|
||||||
|
|
||||||
|
BoolColumn get acknowledgeByServer =>
|
||||||
|
boolean().withDefault(Constant(false))();
|
||||||
|
|
||||||
|
TextColumn get kind => textEnum<MessageKind>()();
|
||||||
|
TextColumn get contentJson => text().nullable()();
|
||||||
|
|
||||||
|
DateTimeColumn get openedAt => dateTime().nullable()();
|
||||||
|
DateTimeColumn get sendAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,9 @@
|
||||||
"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",
|
||||||
|
"contextMenuVerifyUser": "Kontakt verifizieren",
|
||||||
|
"contextMenuOpenChat": "Chat öffnen",
|
||||||
|
"contextMenuSendImage": "Bild senden",
|
||||||
"messageSendState_Received": "Empfangen",
|
"messageSendState_Received": "Empfangen",
|
||||||
"messageSendState_Opened": "Geöffnet",
|
"messageSendState_Opened": "Geöffnet",
|
||||||
"messageSendState_Send": "Gesendet",
|
"messageSendState_Send": "Gesendet",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@
|
||||||
"chatListViewSearchUserNameBtn": "Add your first twonly contact!",
|
"chatListViewSearchUserNameBtn": "Add your first twonly contact!",
|
||||||
"chatListViewSendFirstTwonly": "Send your first twonly!",
|
"chatListViewSendFirstTwonly": "Send your first twonly!",
|
||||||
"chatListDetailInput": "Type a message",
|
"chatListDetailInput": "Type a message",
|
||||||
|
"contextMenuVerifyUser": "Verify user",
|
||||||
|
"contextMenuOpenChat": "Open chat",
|
||||||
|
"contextMenuSendImage": "Send image",
|
||||||
"mediaViewerAuthReason": "Please authenticate to see this twonly!",
|
"mediaViewerAuthReason": "Please authenticate to see this twonly!",
|
||||||
"messageSendState_Received": "Received",
|
"messageSendState_Received": "Received",
|
||||||
"messageSendState_Opened": "Opened",
|
"messageSendState_Opened": "Opened",
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
enum MessageKind {
|
enum MessageKind {
|
||||||
textMessage,
|
textMessage,
|
||||||
image,
|
media,
|
||||||
video,
|
|
||||||
contactRequest,
|
contactRequest,
|
||||||
rejectRequest,
|
rejectRequest,
|
||||||
acceptRequest,
|
acceptRequest,
|
||||||
|
|
@ -11,28 +10,45 @@ enum MessageKind {
|
||||||
ack
|
ack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color getMessageColorFromType(MessageJson msg, Color primary) {
|
||||||
|
Color color;
|
||||||
|
|
||||||
|
final content = msg.content;
|
||||||
|
if (content is TextMessageContent) {
|
||||||
|
color = Colors.lightBlue;
|
||||||
|
} else {
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.isRealTwonly) {
|
||||||
|
color = primary;
|
||||||
|
} else {
|
||||||
|
if (content.isVideo) {
|
||||||
|
color = Colors.deepPurple;
|
||||||
|
} else {
|
||||||
|
color = const Color.fromARGB(255, 214, 47, 47);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Colors.black; // this should not happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
extension MessageKindExtension on MessageKind {
|
extension MessageKindExtension on MessageKind {
|
||||||
String get name => toString().split('.').last;
|
String get name => toString().split('.').last;
|
||||||
|
|
||||||
static MessageKind fromString(String name) {
|
static MessageKind fromString(String name) {
|
||||||
return MessageKind.values.firstWhere((e) => e.name == name);
|
return MessageKind.values.firstWhere((e) => e.name == name);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get index => this.index;
|
|
||||||
|
|
||||||
static MessageKind fromIndex(int index) {
|
|
||||||
return MessageKind.values[index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use message as base class, remove kind and flatten content
|
class MessageJson {
|
||||||
class Message {
|
|
||||||
final MessageKind kind;
|
final MessageKind kind;
|
||||||
final MessageContent content;
|
final MessageContent? content;
|
||||||
final int? messageId;
|
final int? messageId;
|
||||||
DateTime timestamp;
|
DateTime timestamp;
|
||||||
|
|
||||||
Message(
|
MessageJson(
|
||||||
{required this.kind,
|
{required this.kind,
|
||||||
this.messageId,
|
this.messageId,
|
||||||
required this.content,
|
required this.content,
|
||||||
|
|
@ -43,17 +59,21 @@ class Message {
|
||||||
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
|
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Message fromJson(Map<String, dynamic> json) => Message(
|
static MessageJson fromJson(Map<String, dynamic> json) {
|
||||||
kind: MessageKindExtension.fromString(json["kind"]),
|
final kind = MessageKindExtension.fromString(json["kind"]);
|
||||||
messageId: (json['messageId'] as num?)?.toInt(),
|
|
||||||
content:
|
return MessageJson(
|
||||||
MessageContent.fromJson(json['content'] as Map<String, dynamic>),
|
kind: kind,
|
||||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
messageId: (json['messageId'] as num?)?.toInt(),
|
||||||
);
|
content: MessageContent.fromJson(
|
||||||
|
kind, json['content'] as Map<String, dynamic>),
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||||
'kind': kind.name,
|
'kind': kind.name,
|
||||||
'content': content.toJson(),
|
'content': content?.toJson(),
|
||||||
'messageId': messageId,
|
'messageId': messageId,
|
||||||
'timestamp': timestamp.toIso8601String(),
|
'timestamp': timestamp.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
@ -62,37 +82,14 @@ class Message {
|
||||||
class MessageContent {
|
class MessageContent {
|
||||||
MessageContent();
|
MessageContent();
|
||||||
|
|
||||||
Color getColor(Color primary) {
|
static MessageContent? fromJson(MessageKind kind, Map json) {
|
||||||
Color color;
|
switch (kind) {
|
||||||
if (this is TextMessageContent) {
|
case MessageKind.media:
|
||||||
color = Colors.lightBlue;
|
|
||||||
} else {
|
|
||||||
final content = this;
|
|
||||||
if (content is MediaMessageContent) {
|
|
||||||
if (content.isRealTwonly) {
|
|
||||||
color = primary;
|
|
||||||
} else {
|
|
||||||
if (content.isVideo) {
|
|
||||||
color = Colors.deepPurple;
|
|
||||||
} else {
|
|
||||||
color = const Color.fromARGB(255, 214, 47, 47);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Colors.black; // this should not happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MessageContent fromJson(Map json) {
|
|
||||||
switch (json['type']) {
|
|
||||||
case 'MediaMessageContent':
|
|
||||||
return MediaMessageContent.fromJson(json);
|
return MediaMessageContent.fromJson(json);
|
||||||
case 'TextMessageContent':
|
case MessageKind.textMessage:
|
||||||
return TextMessageContent.fromJson(json);
|
return TextMessageContent.fromJson(json);
|
||||||
default:
|
default:
|
||||||
return MessageContent();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +122,6 @@ class MediaMessageContent extends MessageContent {
|
||||||
@override
|
@override
|
||||||
Map toJson() {
|
Map toJson() {
|
||||||
return {
|
return {
|
||||||
'type': 'MediaMessageContent',
|
|
||||||
'downloadToken': downloadToken,
|
'downloadToken': downloadToken,
|
||||||
'isRealTwonly': isRealTwonly,
|
'isRealTwonly': isRealTwonly,
|
||||||
'maxShowTime': maxShowTime,
|
'maxShowTime': maxShowTime,
|
||||||
|
|
@ -146,7 +142,6 @@ class TextMessageContent extends MessageContent {
|
||||||
@override
|
@override
|
||||||
Map toJson() {
|
Map toJson() {
|
||||||
return {
|
return {
|
||||||
'type': 'TextMessageContent',
|
|
||||||
'text': text,
|
'text': text,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:twonly/src/utils/json.dart';
|
import 'package:twonly/src/utils/json.dart';
|
||||||
part 'signal_identity.g.dart';
|
part 'signal_identity.g.dart';
|
||||||
|
|
@ -9,8 +8,7 @@ class SignalIdentity {
|
||||||
const SignalIdentity(
|
const SignalIdentity(
|
||||||
{required this.identityKeyPairU8List, required this.registrationId});
|
{required this.identityKeyPairU8List, required this.registrationId});
|
||||||
|
|
||||||
@Int64Converter()
|
final int registrationId;
|
||||||
final Int64 registrationId;
|
|
||||||
|
|
||||||
@Uint8ListConverter()
|
@Uint8ListConverter()
|
||||||
final Uint8List identityKeyPairU8List;
|
final Uint8List identityKeyPairU8List;
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,12 @@ SignalIdentity _$SignalIdentityFromJson(Map<String, dynamic> json) =>
|
||||||
SignalIdentity(
|
SignalIdentity(
|
||||||
identityKeyPairU8List: const Uint8ListConverter()
|
identityKeyPairU8List: const Uint8ListConverter()
|
||||||
.fromJson(json['identityKeyPairU8List'] as String),
|
.fromJson(json['identityKeyPairU8List'] as String),
|
||||||
registrationId:
|
registrationId: (json['registrationId'] as num).toInt(),
|
||||||
const Int64Converter().fromJson(json['registrationId'] as String),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SignalIdentityToJson(SignalIdentity instance) =>
|
Map<String, dynamic> _$SignalIdentityToJson(SignalIdentity instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'registrationId': const Int64Converter().toJson(instance.registrationId),
|
'registrationId': instance.registrationId,
|
||||||
'identityKeyPairU8List':
|
'identityKeyPairU8List':
|
||||||
const Uint8ListConverter().toJson(instance.identityKeyPairU8List),
|
const Uint8ListConverter().toJson(instance.identityKeyPairU8List),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'package:twonly/src/utils/json.dart';
|
|
||||||
part 'user_data.g.dart';
|
part 'user_data.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
@ -12,8 +10,7 @@ class UserData {
|
||||||
final String username;
|
final String username;
|
||||||
final String displayName;
|
final String displayName;
|
||||||
|
|
||||||
@Int64Converter()
|
final int userId;
|
||||||
final Int64 userId;
|
|
||||||
|
|
||||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||||
_$UserDataFromJson(json);
|
_$UserDataFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part of 'user_data.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
userId: const Int64Converter().fromJson(json['userId'] as String),
|
userId: (json['userId'] as num).toInt(),
|
||||||
username: json['username'] as String,
|
username: json['username'] as String,
|
||||||
displayName: json['displayName'] as String,
|
displayName: json['displayName'] as String,
|
||||||
);
|
);
|
||||||
|
|
@ -15,5 +15,5 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'username': instance.username,
|
'username': instance.username,
|
||||||
'displayName': instance.displayName,
|
'displayName': instance.displayName,
|
||||||
'userId': const Int64Converter().toJson(instance.userId),
|
'userId': instance.userId,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/app.dart';
|
import 'package:twonly/src/app.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import '../../../../.blocked/archives/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import '../../../../.blocked/archives/messages_model.dart';
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -30,7 +30,7 @@ Future tryTransmitMessages() async {
|
||||||
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
|
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
Result resp = await apiProvider.sendTextMessage(
|
Result resp = await apiProvider.sendTextMessage(
|
||||||
Int64(retransmit[i].otherUserId),
|
retransmit[i].otherUserId,
|
||||||
bytes,
|
bytes,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ Future tryTransmitMessages() async {
|
||||||
if (encryptedMedia != null) {
|
if (encryptedMedia != null) {
|
||||||
final content = retransmit[i].messageContent;
|
final content = retransmit[i].messageContent;
|
||||||
if (content is MediaMessageContent) {
|
if (content is MediaMessageContent) {
|
||||||
uploadMediaFile(msgId, Int64(retransmit[i].otherUserId), encryptedMedia,
|
uploadMediaFile(msgId, retransmit[i].otherUserId, encryptedMedia,
|
||||||
content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt);
|
content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ Future tryTransmitMessages() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
// this functions ensures that the message is received by the server and in case of errors will try again later
|
// this functions ensures that the message is received by the server and in case of errors will try again later
|
||||||
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
Future<Result> encryptAndSendMessage(int userId, Message msg) async {
|
||||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
||||||
|
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
|
|
@ -79,7 +79,7 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendTextMessage(Int64 target, String message) async {
|
Future sendTextMessage(int target, String message) async {
|
||||||
MessageContent content = TextMessageContent(text: message);
|
MessageContent content = TextMessageContent(text: message);
|
||||||
|
|
||||||
DateTime messageSendAt = DateTime.now();
|
DateTime messageSendAt = DateTime.now();
|
||||||
|
|
@ -105,7 +105,7 @@ Future sendTextMessage(Int64 target, String message) async {
|
||||||
// this will send the media file and ensures retransmission when errors occur
|
// this will send the media file and ensures retransmission when errors occur
|
||||||
Future uploadMediaFile(
|
Future uploadMediaFile(
|
||||||
int messageId,
|
int messageId,
|
||||||
Int64 target,
|
int target,
|
||||||
Uint8List encryptedMedia,
|
Uint8List encryptedMedia,
|
||||||
bool isRealTwonly,
|
bool isRealTwonly,
|
||||||
int maxShowTime,
|
int maxShowTime,
|
||||||
|
|
@ -179,7 +179,7 @@ Future uploadMediaFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendImage {
|
class SendImage {
|
||||||
final Int64 userId;
|
final int userId;
|
||||||
final Uint8List imageBytes;
|
final Uint8List imageBytes;
|
||||||
final bool isRealTwonly;
|
final bool isRealTwonly;
|
||||||
final int maxShowTime;
|
final int maxShowTime;
|
||||||
|
|
@ -231,7 +231,7 @@ class SendImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendImage(
|
Future sendImage(
|
||||||
List<Int64> userIds,
|
List<int> userIds,
|
||||||
Uint8List imageBytes,
|
Uint8List imageBytes,
|
||||||
bool isRealTwonly,
|
bool isRealTwonly,
|
||||||
int maxShowTime,
|
int maxShowTime,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/app.dart';
|
import 'package:twonly/src/app.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import '../../../../.blocked/archives/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import '../../../../.blocked/archives/messages_model.dart';
|
||||||
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||||
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
|
|
@ -102,7 +102,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
int? fromUserId = box.get("${data.uploadToken}_fromUserId");
|
int? fromUserId = box.get("${data.uploadToken}_fromUserId");
|
||||||
if (fromUserId != null) {
|
if (fromUserId != null) {
|
||||||
Uint8List? rawBytes =
|
Uint8List? rawBytes =
|
||||||
await SignalHelper.decryptBytes(downloadedBytes, Int64(fromUserId));
|
await SignalHelper.decryptBytes(downloadedBytes, fromUserId);
|
||||||
|
|
||||||
if (rawBytes != null) {
|
if (rawBytes != null) {
|
||||||
box.put("${data.uploadToken}_downloaded", rawBytes);
|
box.put("${data.uploadToken}_downloaded", rawBytes);
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ class ApiProvider {
|
||||||
|
|
||||||
var open = Handshake_OpenSession()
|
var open = Handshake_OpenSession()
|
||||||
..response = signature
|
..response = signature
|
||||||
..userId = userData.userId;
|
..userId = Int64(userData.userId);
|
||||||
|
|
||||||
var opensession = Handshake()..opensession = open;
|
var opensession = Handshake()..opensession = open;
|
||||||
|
|
||||||
|
|
@ -304,8 +304,8 @@ class ApiProvider {
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> getUsername(Int64 userId) async {
|
Future<Result> getUsername(int userId) async {
|
||||||
var get = ApplicationData_GetUserById()..userId = userId;
|
var get = ApplicationData_GetUserById()..userId = Int64(userId);
|
||||||
var appData = ApplicationData()..getuserbyid = get;
|
var appData = ApplicationData()..getuserbyid = get;
|
||||||
var req = createClientToServerFromApplicationData(appData);
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
|
|
@ -353,9 +353,9 @@ class ApiProvider {
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> sendTextMessage(Int64 target, Uint8List msg) async {
|
Future<Result> sendTextMessage(int target, Uint8List msg) async {
|
||||||
var testMessage = ApplicationData_TextMessage()
|
var testMessage = ApplicationData_TextMessage()
|
||||||
..userId = target
|
..userId = Int64(target)
|
||||||
..body = msg;
|
..body = msg;
|
||||||
|
|
||||||
var appData = ApplicationData()..textmessage = testMessage;
|
var appData = ApplicationData()..textmessage = testMessage;
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
|
|
||||||
// This provider will update the UI on changes in the contact list
|
|
||||||
class ContactChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|
||||||
List<Contact> _allContacts = [];
|
|
||||||
final Map<int, DbMessage> _lastMessagesGroupedByUser = <int, DbMessage>{};
|
|
||||||
|
|
||||||
int get newContactRequests => _allContacts
|
|
||||||
.where((contact) => !contact.accepted && contact.requested)
|
|
||||||
.length;
|
|
||||||
List<Contact> get allContacts => _allContacts;
|
|
||||||
|
|
||||||
void update() async {
|
|
||||||
_allContacts = await DbContacts.getUsers();
|
|
||||||
for (Contact contact in _allContacts) {
|
|
||||||
DbMessage? last = await DbMessages.getLastMessagesForPreviewForUser(
|
|
||||||
contact.userId.toInt());
|
|
||||||
if (last != null) {
|
|
||||||
_lastMessagesGroupedByUser[last.otherUserId] = last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/model/identity_key_store_model.dart';
|
import 'package:twonly/src/model/identity_key_store_model.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
import 'package:twonly/src/model/pre_key_model.dart';
|
import 'package:twonly/src/model/pre_key_model.dart';
|
||||||
import 'package:twonly/src/model/sender_key_store_model.dart';
|
import 'package:twonly/src/model/sender_key_store_model.dart';
|
||||||
import 'package:twonly/src/model/session_store_model.dart';
|
import 'package:twonly/src/model/session_store_model.dart';
|
||||||
|
|
@ -55,8 +53,6 @@ class DbProvider {
|
||||||
await DbSignalPreKeyStore.setupDatabaseTable(db);
|
await DbSignalPreKeyStore.setupDatabaseTable(db);
|
||||||
await DbSignalSenderKeyStore.setupDatabaseTable(db);
|
await DbSignalSenderKeyStore.setupDatabaseTable(db);
|
||||||
await DbSignalIdentityKeyStore.setupDatabaseTable(db);
|
await DbSignalIdentityKeyStore.setupDatabaseTable(db);
|
||||||
await DbContacts.setupDatabaseTable(db);
|
|
||||||
await DbMessages.setupDatabaseTable(db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future open() async {
|
Future open() async {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import 'dart:collection';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class DownloadChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|
||||||
final HashSet<String> _currentlyDownloading = HashSet<String>();
|
|
||||||
|
|
||||||
HashSet<String> get currentlyDownloading => _currentlyDownloading;
|
|
||||||
|
|
||||||
void update(List<int> token, bool add) {
|
|
||||||
debugPrint("Downloading: $add : $token");
|
|
||||||
|
|
||||||
if (add) {
|
|
||||||
_currentlyDownloading.add(token.toString());
|
|
||||||
} else {
|
|
||||||
_currentlyDownloading.remove(token.toString());
|
|
||||||
}
|
|
||||||
debugPrint("Downloading: $add : ${_currentlyDownloading.toList()}");
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
/// This provider does always contains the latest messages send or received
|
|
||||||
/// for every contact.
|
|
||||||
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|
||||||
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
|
||||||
final Map<int, List<DbMessage>> _allMessagesFromUser =
|
|
||||||
<int, List<DbMessage>>{};
|
|
||||||
final Map<int, int> _changeCounter = <int, int>{};
|
|
||||||
final Map<int, int> _flamesCounter = <int, int>{};
|
|
||||||
|
|
||||||
Map<int, DbMessage> get lastMessage => _lastMessage;
|
|
||||||
Map<int, List<DbMessage>> get allMessagesFromUser => _allMessagesFromUser;
|
|
||||||
Map<int, int> get changeCounter => _changeCounter;
|
|
||||||
Map<int, int> get flamesCounter => _flamesCounter;
|
|
||||||
|
|
||||||
Future updateLastMessageFor(int targetUserId, int? messageId) async {
|
|
||||||
DbMessage? last =
|
|
||||||
await DbMessages.getLastMessagesForPreviewForUser(targetUserId);
|
|
||||||
if (last != null) {
|
|
||||||
_lastMessage[last.otherUserId] = last;
|
|
||||||
}
|
|
||||||
flamesCounter[targetUserId] = await getFlamesForOtherUser(targetUserId);
|
|
||||||
// notifyListeners();
|
|
||||||
|
|
||||||
if (messageId == null || _allMessagesFromUser[targetUserId] == null) {
|
|
||||||
loadMessagesForUser(targetUserId, force: true);
|
|
||||||
} else {
|
|
||||||
DbMessage? msg = await DbMessages.getMessageById(messageId);
|
|
||||||
if (msg != null) {
|
|
||||||
int index = _allMessagesFromUser[targetUserId]!
|
|
||||||
.indexWhere((x) => x.messageId == messageId);
|
|
||||||
if (index == -1) {
|
|
||||||
print("should be indexed by time!!");
|
|
||||||
|
|
||||||
_allMessagesFromUser[targetUserId]!.insert(0, msg);
|
|
||||||
// reload all messages but async
|
|
||||||
loadMessagesForUser(targetUserId, force: true);
|
|
||||||
} else {
|
|
||||||
_allMessagesFromUser[targetUserId]![index] = msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future loadMessagesForUser(int targetUserId, {bool force = false}) async {
|
|
||||||
if (!force && _allMessagesFromUser[targetUserId] != null) return;
|
|
||||||
_allMessagesFromUser[targetUserId] =
|
|
||||||
await DbMessages.getAllMessagesForUser(targetUserId);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void init({bool afterPaused = false}) async {
|
|
||||||
// load everything from the database
|
|
||||||
List<Contact> allContacts = await DbContacts.getUsers();
|
|
||||||
for (Contact contact in allContacts) {
|
|
||||||
DbMessage? last = await DbMessages.getLastMessagesForPreviewForUser(
|
|
||||||
contact.userId.toInt());
|
|
||||||
if (last != null) {
|
|
||||||
_lastMessage[last.otherUserId] = last;
|
|
||||||
}
|
|
||||||
flamesCounter[contact.userId.toInt()] =
|
|
||||||
await getFlamesForOtherUser(contact.userId.toInt());
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
if (afterPaused) {
|
|
||||||
for (int targetUserId in _allMessagesFromUser.keys) {
|
|
||||||
loadMessagesForUser(targetUserId, force: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/app.dart';
|
import 'package:twonly/src/app.dart';
|
||||||
|
import 'package:twonly/src/database/database.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/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
import '../../firebase_options.dart';
|
import '../../firebase_options.dart';
|
||||||
|
|
@ -65,33 +65,26 @@ Future initFCMService() async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late TwonlyDatabase bgTwonlyDB;
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
// If you're going to use other Firebase services in the background, such as Firestore,
|
|
||||||
// make sure you call `initializeApp` before using other Firebase services.
|
|
||||||
|
|
||||||
// Wenn Tasks länger als 30 Sekunden ausgeführt werden, wird der Prozess möglicherweise automatisch vom Gerät beendet.
|
// Wenn Tasks länger als 30 Sekunden ausgeführt werden, wird der Prozess möglicherweise automatisch vom Gerät beendet.
|
||||||
// -> offer backend via http?
|
// -> offer backend via http?
|
||||||
|
|
||||||
print("Handling a background message: ${message.messageId}");
|
Logger("firebase-background")
|
||||||
|
.shout('Handling a background message: ${message.messageId}');
|
||||||
|
|
||||||
bool gotMessage = false;
|
bgTwonlyDB = TwonlyDatabase();
|
||||||
|
|
||||||
globalCallBackOnMessageChange = (a, b) async {
|
|
||||||
gotMessage = true;
|
|
||||||
print("Got message can exit");
|
|
||||||
};
|
|
||||||
|
|
||||||
dbProvider = DbProvider();
|
|
||||||
await dbProvider.ready;
|
|
||||||
apiProvider = ApiProvider();
|
apiProvider = ApiProvider();
|
||||||
await apiProvider.connect();
|
await apiProvider.connect();
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
while (!gotMessage) {
|
while (true) {
|
||||||
if (stopwatch.elapsed >= Duration(seconds: 20)) {
|
if (stopwatch.elapsed >= Duration(seconds: 20)) {
|
||||||
Logger("firebase-background").shout('Timeout reached. Exiting the loop.');
|
Logger("firebase-background").shout('Exiting background handler');
|
||||||
break; // Exit the loop if the timeout is reached.
|
break;
|
||||||
}
|
}
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart' as my;
|
import 'package:twonly/src/model/json/message.dart' as my;
|
||||||
|
|
||||||
/// Streams are created so that app can respond to notification-related events
|
/// Streams are created so that app can respond to notification-related events
|
||||||
|
|
@ -143,9 +142,9 @@ String getPushNotificationText(String key, String userName) {
|
||||||
if (systemLanguage.contains("de")) {
|
if (systemLanguage.contains("de")) {
|
||||||
pushNotificationText = {
|
pushNotificationText = {
|
||||||
"newTextMessage": "%userName% hat dir eine Nachricht gesendet.",
|
"newTextMessage": "%userName% hat dir eine Nachricht gesendet.",
|
||||||
"newTwonly": "%userName% hat dir einen twonly gesendet.",
|
"newTwonly": "%userName% hat dir ein twonly gesendet.",
|
||||||
"newVideo": "%userName% hat dir eine Video gesendet.",
|
"newVideo": "%userName% hat dir ein Video gesendet.",
|
||||||
"newImage": "%userName% hat dir eine Bild gesendet.",
|
"newImage": "%userName% hat dir ein Bild gesendet.",
|
||||||
"contactRequest": "%userName% möchte sich mir dir vernetzen.",
|
"contactRequest": "%userName% möchte sich mir dir vernetzen.",
|
||||||
"acceptRequest": "%userName% ist jetzt mit dir vernetzt.",
|
"acceptRequest": "%userName% ist jetzt mit dir vernetzt.",
|
||||||
};
|
};
|
||||||
|
|
@ -166,7 +165,10 @@ String getPushNotificationText(String key, String userName) {
|
||||||
|
|
||||||
Future localPushNotificationNewMessage(
|
Future localPushNotificationNewMessage(
|
||||||
int fromUserId, my.Message message, int messageId) async {
|
int fromUserId, my.Message message, int messageId) async {
|
||||||
Contact? user = await DbContacts.getUserById(fromUserId);
|
Contact? user = await TwonlyDatabase.provider
|
||||||
|
.getContactByUserId(fromUserId)
|
||||||
|
.getSingleOrNull();
|
||||||
|
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
||||||
String msg = "";
|
String msg = "";
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
// The callback function should always be a top-level or static function.
|
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/db_provider.dart';
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
void startCallback() {
|
|
||||||
FlutterForegroundTask.setTaskHandler(WebsocketForegroundTask());
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebsocketForegroundTask extends TaskHandler {
|
|
||||||
// Called when the task is started.
|
|
||||||
@override
|
|
||||||
Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
|
|
||||||
print('onStart(starter: ${starter.name})');
|
|
||||||
|
|
||||||
dbProvider = DbProvider();
|
|
||||||
await dbProvider.ready;
|
|
||||||
apiProvider = ApiProvider();
|
|
||||||
apiProvider.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called based on the eventAction set in ForegroundTaskOptions.
|
|
||||||
@override
|
|
||||||
void onRepeatEvent(DateTime timestamp) {
|
|
||||||
// Send data to main isolate.
|
|
||||||
final Map<String, dynamic> data = {
|
|
||||||
"timestampMillis": timestamp.millisecondsSinceEpoch,
|
|
||||||
};
|
|
||||||
FlutterForegroundTask.sendDataToMain(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the task is destroyed.
|
|
||||||
@override
|
|
||||||
Future<void> onDestroy(DateTime timestamp) async {
|
|
||||||
await apiProvider.close(() {});
|
|
||||||
print('onDestroy');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when data is sent using `FlutterForegroundTask.sendDataToTask`.
|
|
||||||
@override
|
|
||||||
void onReceiveData(Object data) {
|
|
||||||
apiProvider.close(() {});
|
|
||||||
print('onReceiveData: $data');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the notification button is pressed.
|
|
||||||
@override
|
|
||||||
void onNotificationButtonPressed(String id) {
|
|
||||||
print('onNotificationButtonPressed: $id');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the notification itself is pressed.
|
|
||||||
@override
|
|
||||||
void onNotificationPressed() {
|
|
||||||
apiProvider.close(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the notification itself is dismissed.
|
|
||||||
@override
|
|
||||||
void onNotificationDismissed() {
|
|
||||||
print('onNotificationDismissed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,7 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
class Int64Converter implements JsonConverter<Int64, String> {
|
|
||||||
const Int64Converter();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Int64 fromJson(String json) {
|
|
||||||
return Int64.parseInt(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toJson(Int64 object) {
|
|
||||||
return object.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
|
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
|
||||||
const Uint8ListConverter();
|
const Uint8ListConverter();
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
extension LocalizationExtension on BuildContext {
|
extension ShortCutsExtension on BuildContext {
|
||||||
AppLocalizations get lang => AppLocalizations.of(this)!;
|
AppLocalizations get lang => AppLocalizations.of(this)!;
|
||||||
|
TwonlyDatabase get db => Provider.of<TwonlyDatabase>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if a column exists
|
// Function to check if a column exists
|
||||||
|
|
@ -148,50 +150,48 @@ Future<Uint8List?> getCompressedImage(Uint8List imageBytes) async {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFlameCounter(List<DateTime> dates) {
|
// int getFlameCounter(List<DateTime> dates) {
|
||||||
if (dates.isEmpty) return 0;
|
// if (dates.isEmpty) return 0;
|
||||||
|
|
||||||
int flamesCount = 0;
|
// int flamesCount = 0;
|
||||||
DateTime lastFlameCount = DateTime.now();
|
// DateTime lastFlameCount = DateTime.now();
|
||||||
|
|
||||||
if (calculateTimeDifference(dates[0], lastFlameCount).inDays == 0) {
|
// if (calculateTimeDifference(dates[0], lastFlameCount).inDays == 0) {
|
||||||
flamesCount = 1;
|
// flamesCount = 1;
|
||||||
lastFlameCount = dates[0];
|
// lastFlameCount = dates[0];
|
||||||
}
|
// }
|
||||||
|
|
||||||
// print(dates[0]);
|
// // print(dates[0]);
|
||||||
for (int i = 1; i < dates.length; i++) {
|
// for (int i = 1; i < dates.length; i++) {
|
||||||
// print(
|
// // print(
|
||||||
// "${dates[i]} ${dates[i].difference(dates[i - 1]).inDays} ${dates[i].difference(lastFlameCount).inDays}");
|
// // "${dates[i]} ${dates[i].difference(dates[i - 1]).inDays} ${dates[i].difference(lastFlameCount).inDays}");
|
||||||
if (calculateTimeDifference(dates[i], dates[i - 1]).inDays == 0) {
|
// if (calculateTimeDifference(dates[i], dates[i - 1]).inDays == 0) {
|
||||||
if (lastFlameCount.difference(dates[i]).inDays == 1) {
|
// if (lastFlameCount.difference(dates[i]).inDays == 1) {
|
||||||
flamesCount++;
|
// flamesCount++;
|
||||||
lastFlameCount = dates[i];
|
// lastFlameCount = dates[i];
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
break; // Stop counting if there's a break in the sequence
|
// break; // Stop counting if there's a break in the sequence
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return flamesCount;
|
// return flamesCount;
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<int> getFlamesForOtherUser(int otherUserId) async {
|
// Future<int> getFlamesForOtherUser(int otherUserId) async {
|
||||||
List<(DateTime, int?)> dates = await DbMessages.getMessageDates(otherUserId);
|
// List<(DateTime, int?)> dates = await DbMessages.getMessageDates(otherUserId);
|
||||||
// print("Dates ${dates.length}");
|
// // print("Dates ${dates.length}");
|
||||||
if (dates.isEmpty) return 0;
|
// if (dates.isEmpty) return 0;
|
||||||
|
|
||||||
List<DateTime> received =
|
// List<DateTime> received =
|
||||||
dates.where((x) => x.$2 != null).map((x) => x.$1).toList();
|
// dates.where((x) => x.$2 != null).map((x) => x.$1).toList();
|
||||||
List<DateTime> send =
|
// List<DateTime> send =
|
||||||
dates.where((x) => x.$2 == null).map((x) => x.$1).toList();
|
// dates.where((x) => x.$2 == null).map((x) => x.$1).toList();
|
||||||
|
|
||||||
// print("Received ${received.length} and send ${send.length}");
|
// int a = getFlameCounter(received);
|
||||||
|
// int b = getFlameCounter(send);
|
||||||
int a = getFlameCounter(received);
|
// // print("Received $a and send $b");
|
||||||
int b = getFlameCounter(send);
|
// return min(a, b);
|
||||||
// print("Received $a and send $b");
|
// }
|
||||||
return min(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration calculateTimeDifference(DateTime now, DateTime startTime) {
|
Duration calculateTimeDifference(DateTime now, DateTime startTime) {
|
||||||
// Get the timezone offsets
|
// Get the timezone offsets
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Future<ECPrivateKey?> getPrivateKey() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addNewContact(Response_UserData userData) async {
|
Future<bool> addNewContact(Response_UserData userData) async {
|
||||||
final Int64 userId = userData.userId;
|
final int userId = userData.userId.toInt();
|
||||||
|
|
||||||
SignalProtocolAddress targetAddress =
|
SignalProtocolAddress targetAddress =
|
||||||
SignalProtocolAddress(userId.toString(), defaultDeviceId);
|
SignalProtocolAddress(userId.toString(), defaultDeviceId);
|
||||||
|
|
@ -140,14 +140,15 @@ Future createIfNotExistsSignalIdentity() async {
|
||||||
.storeSignedPreKey(signedPreKey.id, signedPreKey);
|
.storeSignedPreKey(signedPreKey.id, signedPreKey);
|
||||||
|
|
||||||
final storedSignalIdentity = SignalIdentity(
|
final storedSignalIdentity = SignalIdentity(
|
||||||
identityKeyPairU8List: identityKeyPair.serialize(),
|
identityKeyPairU8List: identityKeyPair.serialize(),
|
||||||
registrationId: Int64(registrationId));
|
registrationId: registrationId,
|
||||||
|
);
|
||||||
|
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
|
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Fingerprint?> generateSessionFingerPrint(Int64 target) async {
|
Future<Fingerprint?> generateSessionFingerPrint(int target) async {
|
||||||
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
||||||
UserData? user = await getUser();
|
UserData? user = await getUser();
|
||||||
if (signalStore == null || user == null) return null;
|
if (signalStore == null || user == null) return null;
|
||||||
|
|
@ -215,7 +216,7 @@ Future<Uint8List?> encryptBytes(Uint8List bytes, Int64 target) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> decryptBytes(Uint8List bytes, Int64 target) async {
|
Future<Uint8List?> decryptBytes(Uint8List bytes, int target) async {
|
||||||
try {
|
try {
|
||||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||||
|
|
||||||
|
|
@ -247,7 +248,7 @@ Future<Uint8List?> decryptBytes(Uint8List bytes, Int64 target) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> encryptMessage(Message msg, Int64 target) async {
|
Future<Uint8List?> encryptMessage(Message msg, int target) async {
|
||||||
try {
|
try {
|
||||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
if (sendNextMediaToUserId != null) {
|
if (sendNextMediaToUserId != null) {
|
||||||
Uint8List? imageBytes = await getMergedImage();
|
Uint8List? imageBytes = await getMergedImage();
|
||||||
sendImage(
|
sendImage(
|
||||||
[Int64(sendNextMediaToUserId)],
|
[sendNextMediaToUserId],
|
||||||
imageBytes!,
|
imageBytes!,
|
||||||
_isRealTwonly,
|
_isRealTwonly,
|
||||||
_maxShowTime,
|
_maxShowTime,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import 'package:twonly/src/components/flame.dart';
|
||||||
import 'package:twonly/src/components/headline.dart';
|
import 'package:twonly/src/components/headline.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/components/verified_shield.dart';
|
import 'package:twonly/src/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import '../../../../.blocked/archives/contacts_model.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/send_next_media_to.dart';
|
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import 'package:twonly/src/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/components/verified_shield.dart';
|
import 'package:twonly/src/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import '../../../../.blocked/archives/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import '../../../../.blocked/archives/messages_model.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/download_change_provider.dart';
|
import 'package:twonly/src/providers/download_change_provider.dart';
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@ import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/components/notification_badge.dart';
|
import 'package:twonly/src/components/notification_badge.dart';
|
||||||
import 'package:twonly/src/components/user_context_menu.dart';
|
import 'package:twonly/src/components/user_context_menu.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
|
import 'package:twonly/src/database/messages_db.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/download_change_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/send_next_media_to.dart';
|
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
|
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
|
||||||
|
|
@ -34,64 +32,12 @@ class ChatListView extends StatefulWidget {
|
||||||
class _ChatListViewState extends State<ChatListView> {
|
class _ChatListViewState extends State<ChatListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Map<int, DbMessage> lastMessages =
|
Stream<List<Contact>> contacts = context.db.watchContactsForChatList();
|
||||||
context.watch<MessagesChangeProvider>().lastMessage;
|
|
||||||
|
|
||||||
List<Contact> allUsers = context
|
|
||||||
.watch<ContactChangeProvider>()
|
|
||||||
.allContacts
|
|
||||||
.where((c) => c.accepted)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
allUsers.sort((b, a) {
|
|
||||||
DbMessage? msgA = lastMessages[a.userId.toInt()];
|
|
||||||
DbMessage? msgB = lastMessages[b.userId.toInt()];
|
|
||||||
if (msgA == null) return 1;
|
|
||||||
if (msgB == null) return -1;
|
|
||||||
return msgA.sendAt.compareTo(msgB.sendAt);
|
|
||||||
});
|
|
||||||
|
|
||||||
int maxTotalMediaCounter = 0;
|
|
||||||
if (allUsers.isNotEmpty) {
|
|
||||||
maxTotalMediaCounter = allUsers
|
|
||||||
.map((x) => x.totalMediaCounter)
|
|
||||||
.reduce((a, b) => a > b ? a : b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ProfileView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text("twonly"),
|
|
||||||
),
|
|
||||||
// title:
|
|
||||||
actions: [
|
|
||||||
NotificationBadge(
|
|
||||||
count: context
|
|
||||||
.watch<ContactChangeProvider>()
|
|
||||||
.newContactRequests
|
|
||||||
.toString(),
|
|
||||||
child: IconButton(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => SearchUsernameView(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
@ -99,16 +45,21 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: FaIcon(FontAwesomeIcons.gear, size: 19),
|
child: Text("twonly"),
|
||||||
)
|
),
|
||||||
],
|
// title:
|
||||||
),
|
actions: [
|
||||||
body: (allUsers.isEmpty)
|
StreamBuilder(
|
||||||
? Center(
|
stream: context.db.watchContactsRequested(),
|
||||||
child: Padding(
|
builder: (context, snapshot) {
|
||||||
padding: const EdgeInsets.all(10),
|
var count = 0;
|
||||||
child: OutlinedButton.icon(
|
if (snapshot.hasData && snapshot.data != null) {
|
||||||
icon: Icon(Icons.person_add),
|
count = snapshot.data!;
|
||||||
|
}
|
||||||
|
return NotificationBadge(
|
||||||
|
count: count.toString(),
|
||||||
|
child: IconButton(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
@ -117,35 +68,77 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: Text(context.lang.chatListViewSearchUserNameBtn)),
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
restorationId: 'chat_list_view',
|
|
||||||
itemCount: allUsers.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
final user = allUsers[index];
|
|
||||||
return UserListItem(
|
|
||||||
user: user,
|
|
||||||
maxTotalMediaCounter: maxTotalMediaCounter,
|
|
||||||
lastMessage: lastMessages[user.userId.toInt()],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ProfileView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: FaIcon(FontAwesomeIcons.gear, size: 19),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: StreamBuilder(
|
||||||
|
stream: contacts,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
final contacts = snapshot.data!;
|
||||||
|
if (contacts.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
icon: Icon(Icons.person_add),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchUsernameView()));
|
||||||
|
},
|
||||||
|
label: Text(context.lang.chatListViewSearchUserNameBtn)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxTotalMediaCounter = 0;
|
||||||
|
if (contacts.isNotEmpty) {
|
||||||
|
maxTotalMediaCounter = contacts
|
||||||
|
.map((x) => x.totalMediaCounter)
|
||||||
|
.reduce((a, b) => a > b ? a : b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
restorationId: 'chat_list_view',
|
||||||
|
itemCount: contacts.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final user = contacts[index];
|
||||||
|
return UserListItem(
|
||||||
|
user: user,
|
||||||
|
maxTotalMediaCounter: maxTotalMediaCounter,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserListItem extends StatefulWidget {
|
class UserListItem extends StatefulWidget {
|
||||||
final Contact user;
|
final Contact user;
|
||||||
final DbMessage? lastMessage;
|
|
||||||
final int maxTotalMediaCounter;
|
final int maxTotalMediaCounter;
|
||||||
|
|
||||||
const UserListItem(
|
const UserListItem(
|
||||||
{super.key,
|
{super.key, required this.user, required this.maxTotalMediaCounter});
|
||||||
required this.user,
|
|
||||||
required this.lastMessage,
|
|
||||||
required this.maxTotalMediaCounter});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UserListItem> createState() => _UserListItem();
|
State<UserListItem> createState() => _UserListItem();
|
||||||
|
|
@ -154,38 +147,38 @@ class UserListItem extends StatefulWidget {
|
||||||
class _UserListItem extends State<UserListItem> {
|
class _UserListItem extends State<UserListItem> {
|
||||||
int lastMessageInSeconds = 0;
|
int lastMessageInSeconds = 0;
|
||||||
MessageSendState state = MessageSendState.send;
|
MessageSendState state = MessageSendState.send;
|
||||||
bool isDownloading = false;
|
|
||||||
List<int> token = [];
|
List<int> token = [];
|
||||||
|
Message? currentMessage;
|
||||||
|
|
||||||
Timer? updateTime;
|
Timer? updateTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
// initAsync();
|
||||||
lastUpdateTime();
|
lastUpdateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future initAsync() async {
|
// Future initAsync() async {
|
||||||
if (widget.lastMessage != null) {
|
// if (currentMessage != null) {
|
||||||
if (!widget.lastMessage!.isDownloaded) {
|
// if (currentMessage!.downloadState != DownloadState.downloading) {
|
||||||
final content = widget.lastMessage!.messageContent;
|
// final content = widget.lastMessage!.messageContent;
|
||||||
if (content is MediaMessageContent) {
|
// if (content is MediaMessageContent) {
|
||||||
tryDownloadMedia(widget.lastMessage!.messageId,
|
// tryDownloadMedia(widget.lastMessage!.messageId,
|
||||||
widget.lastMessage!.otherUserId, content.downloadToken);
|
// widget.lastMessage!.otherUserId, content.downloadToken);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
void lastUpdateTime() {
|
void lastUpdateTime() {
|
||||||
// Change the color every 200 milliseconds
|
// Change the color every 200 milliseconds
|
||||||
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
|
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.lastMessage != null) {
|
if (currentMessage != null) {
|
||||||
lastMessageInSeconds = calculateTimeDifference(
|
lastMessageInSeconds =
|
||||||
DateTime.now(), widget.lastMessage!.sendAt)
|
calculateTimeDifference(DateTime.now(), currentMessage!.sendAt)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -199,72 +192,98 @@ class _UserListItem extends State<UserListItem> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.lastMessage != null) {
|
final notOpenedMessages =
|
||||||
state = widget.lastMessage!.getSendState();
|
context.db.watchMessageNotOpened(widget.user.userId);
|
||||||
|
final lastMessage = context.db.watchLastMessage(widget.user.userId);
|
||||||
|
|
||||||
final content = widget.lastMessage!.messageContent;
|
// if (widget.lastMessage != null) {
|
||||||
|
// state = widget.lastMessage!.getSendState();
|
||||||
|
|
||||||
if (widget.lastMessage!.messageReceived &&
|
// final content = widget.lastMessage!.messageContent;
|
||||||
content is MediaMessageContent) {
|
|
||||||
token = content.downloadToken;
|
|
||||||
isDownloading = context
|
|
||||||
.watch<DownloadChangeProvider>()
|
|
||||||
.currentlyDownloading
|
|
||||||
.contains(token.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int flameCounter = context
|
// if (widget.lastMessage!.messageReceived &&
|
||||||
.watch<MessagesChangeProvider>()
|
// content is MediaMessageContent) {
|
||||||
.flamesCounter[widget.user.userId.toInt()] ??
|
// token = content.downloadToken;
|
||||||
0;
|
// isDownloading = context
|
||||||
|
// .watch<DownloadChangeProvider>()
|
||||||
|
// .currentlyDownloading
|
||||||
|
// .contains(token.toString());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
int flameCounter = getFlameCounterFromContact(widget.user);
|
||||||
|
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
user: widget.user,
|
contact: widget.user,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(widget.user.displayName),
|
title: Text(getContactDisplayName(widget.user)),
|
||||||
subtitle: (widget.lastMessage == null)
|
subtitle: StreamBuilder(
|
||||||
? Text(context.lang.chatsTapToSend)
|
stream: lastMessage,
|
||||||
: Row(
|
builder: (context, lastMessageSnapshot) {
|
||||||
children: [
|
if (!lastMessageSnapshot.hasData) {
|
||||||
MessageSendStateIcon(widget.lastMessage!),
|
return Container();
|
||||||
Text("•"),
|
}
|
||||||
const SizedBox(width: 5),
|
if (lastMessageSnapshot.data == null) {
|
||||||
Text(
|
return Text(context.lang.chatsTapToSend);
|
||||||
formatDuration(lastMessageInSeconds),
|
}
|
||||||
style: TextStyle(fontSize: 12),
|
final lastMessage = lastMessageSnapshot.data!;
|
||||||
),
|
return StreamBuilder(
|
||||||
if (flameCounter > 0)
|
stream: notOpenedMessages,
|
||||||
FlameCounterWidget(
|
builder: (context, notOpenedMessagesSnapshot) {
|
||||||
widget.user,
|
if (!lastMessageSnapshot.hasData) {
|
||||||
flameCounter,
|
return Container();
|
||||||
widget.maxTotalMediaCounter,
|
}
|
||||||
prefix: true,
|
|
||||||
|
var lastMessages = [lastMessage];
|
||||||
|
if (notOpenedMessagesSnapshot.data != null) {
|
||||||
|
lastMessages = notOpenedMessagesSnapshot.data!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
MessageSendStateIcon(lastMessages),
|
||||||
|
Text("•"),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
formatDuration(lastMessageInSeconds),
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
if (flameCounter > 0)
|
||||||
),
|
FlameCounterWidget(
|
||||||
leading: InitialsAvatar(displayName: widget.user.displayName),
|
widget.user,
|
||||||
|
flameCounter,
|
||||||
|
widget.maxTotalMediaCounter,
|
||||||
|
prefix: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
leading: InitialsAvatar(getContactDisplayName(widget.user)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.lastMessage == null) {
|
if (currentMessage == null) {
|
||||||
context
|
context
|
||||||
.read<SendNextMediaTo>()
|
.read<SendNextMediaTo>()
|
||||||
.updateSendNextMediaTo(widget.user.userId.toInt());
|
.updateSendNextMediaTo(widget.user.userId.toInt());
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDownloading) return;
|
Message msg = currentMessage!;
|
||||||
if (!widget.lastMessage!.isDownloaded) {
|
if (msg.downloadState == DownloadState.downloading) {
|
||||||
tryDownloadMedia(widget.lastMessage!.messageId,
|
return;
|
||||||
widget.lastMessage!.otherUserId, token,
|
}
|
||||||
force: true);
|
if (msg.downloadState == DownloadState.pending) {
|
||||||
|
tryDownloadMedia(msg.messageId, msg.contactId, token, force: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state == MessageSendState.received &&
|
if (state == MessageSendState.received &&
|
||||||
widget.lastMessage!.containsOtherMedia()) {
|
msg.kind == MessageKind.media) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return MediaViewerView(widget.user, widget.lastMessage!);
|
return MediaViewerView(widget.user, msg);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import 'package:no_screenshot/no_screenshot.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/components/animate_icon.dart';
|
import 'package:twonly/src/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/components/media_view_sizing.dart';
|
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import '../../../../.blocked/archives/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import '../../../../.blocked/archives/messages_model.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/send_next_media_to.dart';
|
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/alert_dialog.dart';
|
import 'package:twonly/src/components/alert_dialog.dart';
|
||||||
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/components/headline.dart';
|
import 'package:twonly/src/components/headline.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/model/json/message.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/providers/api/api.dart';
|
||||||
// ignore: library_prefixes
|
// ignore: library_prefixes
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||||
|
|
@ -31,18 +31,28 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
|
|
||||||
final res = await apiProvider.getUserData(searchUserName.text);
|
final res = await apiProvider.getUserData(searchUserName.text);
|
||||||
|
|
||||||
if (res.isSuccess) {
|
if (!context.mounted) {
|
||||||
bool added = await DbContacts.insertNewContact(
|
return;
|
||||||
searchUserName.text,
|
}
|
||||||
res.value.userdata.userId.toInt(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (added) {
|
if (res.isSuccess) {
|
||||||
|
final addUser = await showAlertDialog(
|
||||||
|
context, "User found", "Do you want to create a follow request?");
|
||||||
|
if (!addUser || !context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int added = await context.db.insertContact(ContactsCompanion(
|
||||||
|
username: Value(searchUserName.text),
|
||||||
|
userId: Value(res.value.userdata.userId),
|
||||||
|
requested: Value(false),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (added > 0) {
|
||||||
if (await SignalHelper.addNewContact(res.value.userdata)) {
|
if (await SignalHelper.addNewContact(res.value.userdata)) {
|
||||||
encryptAndSendMessage(
|
encryptAndSendMessage(
|
||||||
res.value.userdata.userId,
|
res.value.userdata.userId,
|
||||||
Message(
|
MessageJson(
|
||||||
kind: MessageKind.contactRequest,
|
kind: MessageKind.contactRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
content: MessageContent(),
|
content: MessageContent(),
|
||||||
|
|
@ -50,7 +60,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (context.mounted) {
|
} else {
|
||||||
showAlertDialog(context, context.lang.searchUsernameNotFound,
|
showAlertDialog(context, context.lang.searchUsernameNotFound,
|
||||||
context.lang.searchUsernameNotFoundBody(searchUserName.text));
|
context.lang.searchUsernameNotFoundBody(searchUserName.text));
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +89,8 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<List<Contact>> contacts = context.db.watchNotAcceptedContacts();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.searchUsernameTitle),
|
title: Text(context.lang.searchUsernameTitle),
|
||||||
|
|
@ -108,14 +120,24 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
label: Text(context.lang.searchUsernameQrCodeBtn),
|
label: Text(context.lang.searchUsernameQrCodeBtn),
|
||||||
),
|
),
|
||||||
SizedBox(height: 30),
|
SizedBox(height: 30),
|
||||||
if (context
|
StreamBuilder(
|
||||||
.watch<ContactChangeProvider>()
|
stream: contacts,
|
||||||
.allContacts
|
builder: (context, snapshot) {
|
||||||
.where((contact) => !contact.accepted)
|
if (!snapshot.hasData || snapshot.data != null) {
|
||||||
.isNotEmpty)
|
return Container();
|
||||||
HeadLineComponent(context.lang.searchUsernameNewFollowerTitle),
|
}
|
||||||
Expanded(
|
final contacts = snapshot.data!;
|
||||||
child: ContactsListView(),
|
if (contacts.isEmpty) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
return Row(children: [
|
||||||
|
HeadLineComponent(
|
||||||
|
context.lang.searchUsernameNewFollowerTitle),
|
||||||
|
Expanded(
|
||||||
|
child: ContactsListView(contacts),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -136,7 +158,9 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactsListView extends StatefulWidget {
|
class ContactsListView extends StatefulWidget {
|
||||||
const ContactsListView({super.key});
|
const ContactsListView(this.contacts, {super.key});
|
||||||
|
|
||||||
|
final List<Contact> contacts;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContactsListView> createState() => _ContactsListViewState();
|
State<ContactsListView> createState() => _ContactsListViewState();
|
||||||
|
|
@ -145,18 +169,14 @@ class ContactsListView extends StatefulWidget {
|
||||||
class _ContactsListViewState extends State<ContactsListView> {
|
class _ContactsListViewState extends State<ContactsListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Contact> contacts = context
|
|
||||||
.read<ContactChangeProvider>()
|
|
||||||
.allContacts
|
|
||||||
.where((contact) => !contact.accepted)
|
|
||||||
.toList();
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: contacts.length,
|
itemCount: widget.contacts.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final contact = contacts[index];
|
final contact = widget.contacts[index];
|
||||||
|
final displayName = getContactDisplayName(contact);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(contact.displayName),
|
title: Text(displayName),
|
||||||
leading: InitialsAvatar(displayName: contact.displayName),
|
leading: InitialsAvatar(displayName),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -168,7 +188,8 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
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)),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await DbContacts.blockUser(contact.userId.toInt());
|
final update = ContactsCompanion(blocked: Value(true));
|
||||||
|
await context.db.updateContact(contact.userId, update);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -177,10 +198,10 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(Icons.close, color: Colors.red),
|
icon: Icon(Icons.close, color: Colors.red),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await DbContacts.deleteUser(contact.userId.toInt());
|
await context.db.deleteContactByUserId(contact.userId);
|
||||||
encryptAndSendMessage(
|
encryptAndSendMessage(
|
||||||
contact.userId,
|
contact.userId,
|
||||||
Message(
|
MessageJson(
|
||||||
kind: MessageKind.rejectRequest,
|
kind: MessageKind.rejectRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
content: MessageContent(),
|
content: MessageContent(),
|
||||||
|
|
@ -192,10 +213,11 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.check, color: Colors.green),
|
icon: Icon(Icons.check, color: Colors.green),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await DbContacts.acceptUser(contact.userId.toInt());
|
final update = ContactsCompanion(accepted: Value(true));
|
||||||
|
await context.db.updateContact(contact.userId, update);
|
||||||
encryptAndSendMessage(
|
encryptAndSendMessage(
|
||||||
contact.userId,
|
contact.userId,
|
||||||
Message(
|
MessageJson(
|
||||||
kind: MessageKind.acceptRequest,
|
kind: MessageKind.acceptRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
content: MessageContent(),
|
content: MessageContent(),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:twonly/src/components/format_long_string.dart';
|
import 'package:twonly/src/components/format_long_string.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/signal.dart';
|
import 'package:twonly/src/utils/signal.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
@ -36,10 +36,9 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Contact contact = context
|
Stream<Contact?> contact = context.db
|
||||||
.watch<ContactChangeProvider>()
|
.getContactByUserId(widget.contact.userId)
|
||||||
.allContacts
|
.watchSingleOrNull();
|
||||||
.firstWhere((c) => c.userId == widget.contact.userId);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -92,13 +91,21 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
StreamBuilder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
stream: contact,
|
||||||
child: Text(
|
builder: (context, snapshot) {
|
||||||
context.lang
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
.contactVerifyNumberLongDesc(contact.displayName),
|
return Container();
|
||||||
textAlign: TextAlign.center,
|
}
|
||||||
),
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
|
child: Text(
|
||||||
|
context.lang.contactVerifyNumberLongDesc(
|
||||||
|
getContactDisplayName(snapshot.data!)),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding:
|
||||||
|
|
@ -125,24 +132,34 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
contact.verified
|
StreamBuilder(
|
||||||
? OutlinedButton.icon(
|
stream: contact,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final contact = snapshot.data!;
|
||||||
|
if (contact.verified) {
|
||||||
|
return OutlinedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
DbContacts.updateVerificationStatus(
|
final update =
|
||||||
contact.userId.toInt(), false);
|
ContactsCompanion(verified: Value(false));
|
||||||
|
context.db.updateContact(contact.userId, update);
|
||||||
},
|
},
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.contactVerifyNumberClearVerification),
|
context.lang.contactVerifyNumberClearVerification),
|
||||||
)
|
);
|
||||||
: FilledButton.icon(
|
}
|
||||||
icon: FaIcon(FontAwesomeIcons.shieldHeart),
|
return FilledButton.icon(
|
||||||
onPressed: () {
|
icon: FaIcon(FontAwesomeIcons.shieldHeart),
|
||||||
DbContacts.updateVerificationStatus(
|
onPressed: () {
|
||||||
contact.userId.toInt(), true);
|
final update = ContactsCompanion(verified: Value(true));
|
||||||
},
|
context.db.updateContact(contact.userId, update);
|
||||||
label:
|
},
|
||||||
Text(context.lang.contactVerifyNumberMarkAsVerified),
|
label: Text(context.lang.contactVerifyNumberMarkAsVerified),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
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/alert_dialog.dart';
|
||||||
import 'package:twonly/src/components/better_list_title.dart';
|
import 'package:twonly/src/components/better_list_title.dart';
|
||||||
import 'package:twonly/src/components/flame.dart';
|
import 'package:twonly/src/components/flame.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/components/verified_shield.dart';
|
import 'package:twonly/src/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/contact/contact_verify_view.dart';
|
import 'package:twonly/src/views/contact/contact_verify_view.dart';
|
||||||
|
|
||||||
|
|
@ -24,93 +23,100 @@ class ContactView extends StatefulWidget {
|
||||||
class _ContactViewState extends State<ContactView> {
|
class _ContactViewState extends State<ContactView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Contact contact = context
|
Stream<Contact?> contact =
|
||||||
.watch<ContactChangeProvider>()
|
context.db.getContactByUserId(widget.userId).watchSingleOrNull();
|
||||||
.allContacts
|
|
||||||
.firstWhere((c) => c.userId == widget.userId);
|
|
||||||
|
|
||||||
int flameCounter = context
|
|
||||||
.watch<MessagesChangeProvider>()
|
|
||||||
.flamesCounter[contact.userId.toInt()] ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(""),
|
title: Text(""),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: StreamBuilder(
|
||||||
children: [
|
stream: contact,
|
||||||
Padding(
|
builder: (context, snapshot) {
|
||||||
padding: const EdgeInsets.all(10),
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
child: InitialsAvatar(
|
return Container();
|
||||||
displayName: contact.displayName,
|
}
|
||||||
fontSize: 30,
|
final contact = snapshot.data!;
|
||||||
),
|
int flameCounter = getFlameCounterFromContact(contact);
|
||||||
),
|
return ListView(
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: VerifiedShield(contact)),
|
child: InitialsAvatar(
|
||||||
Text(
|
getContactDisplayName(contact),
|
||||||
contact.displayName,
|
fontSize: 30,
|
||||||
style: TextStyle(fontSize: 20),
|
|
||||||
),
|
|
||||||
if (flameCounter > 0)
|
|
||||||
FlameCounterWidget(
|
|
||||||
contact,
|
|
||||||
flameCounter,
|
|
||||||
110000000,
|
|
||||||
prefix: true,
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
Row(
|
||||||
SizedBox(height: 50),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
BetterListTile(
|
children: [
|
||||||
icon: FontAwesomeIcons.pencil,
|
Padding(
|
||||||
text: context.lang.contactNickname,
|
padding: EdgeInsets.only(right: 10),
|
||||||
onTap: () async {
|
child: VerifiedShield(contact)),
|
||||||
String? newUsername =
|
Text(
|
||||||
await showNicknameChangeDialog(context, contact);
|
getContactDisplayName(contact),
|
||||||
if (newUsername != null && newUsername != "") {
|
style: TextStyle(fontSize: 20),
|
||||||
await DbContacts.changeDisplayName(
|
),
|
||||||
contact.userId.toInt(), newUsername);
|
if (flameCounter > 0)
|
||||||
}
|
FlameCounterWidget(
|
||||||
},
|
contact,
|
||||||
),
|
flameCounter,
|
||||||
const Divider(),
|
110000000,
|
||||||
BetterListTile(
|
prefix: true,
|
||||||
icon: FontAwesomeIcons.shieldHeart,
|
),
|
||||||
text: context.lang.contactVerifyNumberTitle,
|
],
|
||||||
onTap: () {
|
),
|
||||||
Navigator.push(context, MaterialPageRoute(
|
SizedBox(height: 50),
|
||||||
builder: (context) {
|
BetterListTile(
|
||||||
return ContactVerifyView(contact);
|
icon: FontAwesomeIcons.pencil,
|
||||||
|
text: context.lang.contactNickname,
|
||||||
|
onTap: () async {
|
||||||
|
String? nickName =
|
||||||
|
await showNicknameChangeDialog(context, contact);
|
||||||
|
|
||||||
|
if (context.mounted && nickName != null && nickName != "") {
|
||||||
|
final update = ContactsCompanion(nickName: Value(nickName));
|
||||||
|
context.db.updateContact(contact.userId, update);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
},
|
const Divider(),
|
||||||
),
|
BetterListTile(
|
||||||
BetterListTile(
|
icon: FontAwesomeIcons.shieldHeart,
|
||||||
icon: FontAwesomeIcons.ban,
|
text: context.lang.contactVerifyNumberTitle,
|
||||||
color: Colors.red,
|
onTap: () {
|
||||||
text: context.lang.contactBlock,
|
Navigator.push(context, MaterialPageRoute(
|
||||||
onTap: () async {
|
builder: (context) {
|
||||||
bool block = await showAlertDialog(
|
return ContactVerifyView(contact);
|
||||||
context,
|
},
|
||||||
context.lang.contactBlockTitle(contact.displayName),
|
));
|
||||||
context.lang.contactBlockBody,
|
},
|
||||||
);
|
),
|
||||||
if (block) {
|
BetterListTile(
|
||||||
await DbContacts.blockUser(contact.userId.toInt());
|
icon: FontAwesomeIcons.ban,
|
||||||
// go back to the first
|
color: Colors.red,
|
||||||
if (context.mounted) {
|
text: context.lang.contactBlock,
|
||||||
Navigator.popUntil(context, (route) => route.isFirst);
|
onTap: () async {
|
||||||
}
|
bool block = await showAlertDialog(
|
||||||
}
|
context,
|
||||||
},
|
context.lang
|
||||||
),
|
.contactBlockTitle(getContactDisplayName(contact)),
|
||||||
],
|
context.lang.contactBlockBody,
|
||||||
|
);
|
||||||
|
if (block) {
|
||||||
|
final update = ContactsCompanion(blocked: Value(true));
|
||||||
|
if (context.mounted) {
|
||||||
|
await context.db.updateContact(contact.userId, update);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.popUntil(context, (route) => route.isFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/settings/privacy_view_block_users.dart';
|
import 'package:twonly/src/views/settings/privacy_view_block_users.dart';
|
||||||
|
|
||||||
|
|
@ -11,17 +10,9 @@ class PrivacyView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PrivacyViewState extends State<PrivacyView> {
|
class _PrivacyViewState extends State<PrivacyView> {
|
||||||
List<Contact> blockedUsers = [];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
updateBlockedUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future updateBlockedUsers() async {
|
|
||||||
blockedUsers = await DbContacts.getBlockedUsers();
|
|
||||||
setState(() {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -34,15 +25,25 @@ class _PrivacyViewState extends State<PrivacyView> {
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsPrivacyBlockUsers),
|
title: Text(context.lang.settingsPrivacyBlockUsers),
|
||||||
subtitle: Text(
|
subtitle: StreamBuilder(
|
||||||
context.lang.settingsPrivacyBlockUsersCount(blockedUsers.length),
|
stream: context.db.watchContactsBlocked(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData && snapshot.data != null) {
|
||||||
|
return Text(
|
||||||
|
context.lang.settingsPrivacyBlockUsersCount(snapshot.data!),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text(
|
||||||
|
context.lang.settingsPrivacyBlockUsersCount(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return PrivacyViewBlockUsers();
|
return PrivacyViewBlockUsers();
|
||||||
}));
|
}));
|
||||||
updateBlockedUsers();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/initialsavatar.dart';
|
import 'package:twonly/src/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/database/contacts_db.dart';
|
||||||
|
import 'package:twonly/src/database/database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class PrivacyViewBlockUsers extends StatefulWidget {
|
class PrivacyViewBlockUsers extends StatefulWidget {
|
||||||
|
|
@ -11,35 +13,34 @@ class PrivacyViewBlockUsers extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
List<Contact> allUsers = [];
|
late Stream<List<Contact>> allUsers;
|
||||||
List<Contact> filteredUsers = [];
|
List<Contact> filteredUsers = [];
|
||||||
String lastQuery = "";
|
String filter = "";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
allUsers = context.db.watchAllContacts();
|
||||||
loadAsync();
|
loadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future loadAsync() async {
|
Future loadAsync() async {
|
||||||
allUsers = await DbContacts.getAllUsers();
|
|
||||||
_filterUsers(lastQuery);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _filterUsers(String query) async {
|
// Future _filterUsers(String query) async {
|
||||||
lastQuery = query;
|
// lastQuery = query;
|
||||||
if (query.isEmpty) {
|
// if (query.isEmpty) {
|
||||||
filteredUsers = allUsers;
|
// filteredUsers = allUsers;
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
filteredUsers = allUsers
|
// filteredUsers = allUsers
|
||||||
.where((user) =>
|
// .where((user) =>
|
||||||
user.displayName.toLowerCase().contains(query.toLowerCase()))
|
// user.displayName.toLowerCase().contains(query.toLowerCase()))
|
||||||
.toList();
|
// .toList();
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -54,7 +55,9 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: _filterUsers,
|
onChanged: (value) => setState(() {
|
||||||
|
filter = value;
|
||||||
|
}),
|
||||||
decoration: getInputDecoration(
|
decoration: getInputDecoration(
|
||||||
context,
|
context,
|
||||||
context.lang.searchUsernameInput,
|
context.lang.searchUsernameInput,
|
||||||
|
|
@ -68,10 +71,22 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserList(
|
child: StreamBuilder(
|
||||||
List.from(filteredUsers),
|
stream: allUsers,
|
||||||
updateStatus: () {
|
builder: (context, snapshot) {
|
||||||
loadAsync();
|
if (!snapshot.hasData) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
final filteredContacts = snapshot.data!.where((contact) {
|
||||||
|
return getContactDisplayName(contact)
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(filter.toLowerCase());
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return UserList(
|
||||||
|
List.from(filteredContacts),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -83,20 +98,21 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserList extends StatelessWidget {
|
class UserList extends StatelessWidget {
|
||||||
const UserList(this.users, {super.key, required this.updateStatus});
|
const UserList(this.users, {super.key});
|
||||||
final List<Contact> users;
|
final List<Contact> users;
|
||||||
final Function updateStatus;
|
|
||||||
|
|
||||||
Future block(bool? value, int userId) async {
|
Future block(BuildContext context, int userId, bool? value) async {
|
||||||
if (value == null) return;
|
if (value != null) {
|
||||||
await DbContacts.blockUser(userId, unblock: !value);
|
final update = ContactsCompanion(blocked: Value(!value));
|
||||||
updateStatus();
|
await context.db.updateContact(userId, update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Step 1: Sort the users alphabetically
|
// Step 1: Sort the users alphabetically
|
||||||
users.sort((a, b) => a.displayName.compareTo(b.displayName));
|
users.sort(
|
||||||
|
(a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)));
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
restorationId: 'new_message_users_list',
|
restorationId: 'new_message_users_list',
|
||||||
|
|
@ -105,20 +121,20 @@ class UserList extends StatelessWidget {
|
||||||
Contact user = users[i];
|
Contact user = users[i];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
Text(user.displayName),
|
Text(getContactDisplayName(user)),
|
||||||
]),
|
]),
|
||||||
leading: InitialsAvatar(
|
leading: InitialsAvatar(
|
||||||
displayName: user.displayName,
|
getContactDisplayName(user),
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
),
|
),
|
||||||
trailing: Checkbox(
|
trailing: Checkbox(
|
||||||
value: user.blocked,
|
value: user.blocked,
|
||||||
onChanged: (bool? value) {
|
onChanged: (bool? value) {
|
||||||
block(value, user.userId.toInt());
|
block(context, user.userId, value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
block(!user.blocked, user.userId.toInt());
|
block(context, user.userId, !user.blocked);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
InitialsAvatar(
|
InitialsAvatar(
|
||||||
displayName: userData!.username,
|
userData!.username,
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
|
|
|
||||||
72
pubspec.lock
72
pubspec.lock
|
|
@ -93,18 +93,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.14"
|
version: "2.4.15"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -153,6 +153,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -273,6 +281,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
drift:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift
|
||||||
|
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.25.1"
|
||||||
|
drift_dev:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: drift_dev
|
||||||
|
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.25.2"
|
||||||
|
drift_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift_flutter
|
||||||
|
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.4"
|
||||||
ed25519_edwards:
|
ed25519_edwards:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1133,6 +1165,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
recase:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: recase
|
||||||
|
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
reorderables:
|
reorderables:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1290,6 +1330,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0+1"
|
version: "3.1.0+1"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.4"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3_flutter_libs
|
||||||
|
sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.31"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.41.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ dependencies:
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
connectivity_plus: ^6.1.2
|
connectivity_plus: ^6.1.2
|
||||||
cv: ^1.1.3
|
cv: ^1.1.3
|
||||||
|
drift: ^2.25.1
|
||||||
|
drift_flutter: ^0.2.4
|
||||||
exif: ^3.3.0
|
exif: ^3.3.0
|
||||||
firebase_core: ^3.11.0
|
firebase_core: ^3.11.0
|
||||||
firebase_messaging: ^15.2.2
|
firebase_messaging: ^15.2.2
|
||||||
|
|
@ -60,10 +62,11 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.3.3
|
build_runner: ^2.4.15
|
||||||
json_serializable: ^6.8.0
|
json_serializable: ^6.8.0
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
flutter_launcher_icons: ^0.14.1
|
flutter_launcher_icons: ^0.14.1
|
||||||
|
drift_dev: ^2.25.2
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: true
|
android: true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue