feat(service): First attempt at handling phone contacts
This commit is contained in:
parent
73913c4ae6
commit
e060b0f549
@ -48,6 +48,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -442,6 +442,11 @@ files:
|
||||
attributes:
|
||||
deviceId: int
|
||||
jid: String
|
||||
- name: GetContactsCommandDebug
|
||||
extends: BackgroundCommand
|
||||
implements:
|
||||
- JsonImplementation
|
||||
attributes:
|
||||
generate_builder: true
|
||||
# get${builder_Name}FromJson
|
||||
builder_name: "Command"
|
||||
|
@ -27,7 +27,6 @@ String _cleanBase64String(String original) {
|
||||
}
|
||||
|
||||
class AvatarService {
|
||||
|
||||
AvatarService() : _log = Logger('AvatarService');
|
||||
final Logger _log;
|
||||
|
||||
|
122
lib/service/contact.dart
Normal file
122
lib/service/contact.dart
Normal file
@ -0,0 +1,122 @@
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/roster.dart';
|
||||
import 'package:moxxyv2/service/service.dart';
|
||||
import 'package:moxxyv2/shared/events.dart';
|
||||
import 'package:moxxyv2/shared/models/roster.dart';
|
||||
|
||||
class ContactWrapper {
|
||||
const ContactWrapper(this.title, this.id, this.jid);
|
||||
final String title;
|
||||
final String id;
|
||||
final String jid;
|
||||
}
|
||||
|
||||
class ContactsService {
|
||||
ContactsService() : _log = Logger('ContactsService') {
|
||||
// NOTE: Apparently, this means that if false, contacts that are in 0 groups
|
||||
// are not returned.
|
||||
FlutterContacts.config.includeNonVisibleOnAndroid = true;
|
||||
|
||||
// Allow us to react to database changes
|
||||
FlutterContacts.addListener(_onContactsDatabaseUpdate);
|
||||
}
|
||||
final Logger _log;
|
||||
|
||||
List<String>? _contactIds;
|
||||
|
||||
Future<List<ContactWrapper>> fetchContactsWithJabber() async {
|
||||
final contacts = await FlutterContacts.getContacts(withProperties: true);
|
||||
_log.finest('Got ${contacts.length} contacts');
|
||||
|
||||
final jabberContacts = List<ContactWrapper>.empty(growable: true);
|
||||
for (final c in contacts) {
|
||||
final index = c.socialMedias
|
||||
.indexWhere((s) => s.label == SocialMediaLabel.jabber);
|
||||
if (index == -1) continue;
|
||||
|
||||
jabberContacts.add(
|
||||
ContactWrapper(
|
||||
'${c.name.first} ${c.name.last}',
|
||||
c.id,
|
||||
c.socialMedias[index].userName,
|
||||
),
|
||||
);
|
||||
}
|
||||
_log.finest('${jabberContacts.length} contacts have an XMPP address');
|
||||
|
||||
return jabberContacts;
|
||||
}
|
||||
|
||||
Future<void> _onContactsDatabaseUpdate() async {
|
||||
_log.finest('Got contacts database update');
|
||||
}
|
||||
|
||||
Future<List<String>> _getContactIds() async {
|
||||
if (_contactIds != null) return _contactIds!;
|
||||
|
||||
_contactIds = List<String>.from(
|
||||
await GetIt.I.get<DatabaseService>().getContactIds(),
|
||||
);
|
||||
return _contactIds!;
|
||||
}
|
||||
|
||||
Future<void> scanContacts() async {
|
||||
final db = GetIt.I.get<DatabaseService>();
|
||||
final cs = GetIt.I.get<ConversationService>();
|
||||
final rs = GetIt.I.get<RosterService>();
|
||||
final contacts = await fetchContactsWithJabber();
|
||||
final knownContactIds = await _getContactIds();
|
||||
|
||||
for (final id in knownContactIds) {
|
||||
final index = contacts.indexWhere((c) => c.id == id);
|
||||
if (index != -1) continue;
|
||||
|
||||
await db.removeContactId(id);
|
||||
_contactIds!.remove(id);
|
||||
}
|
||||
|
||||
final modifiedRosterItems = List<RosterItem>.empty(growable: true);
|
||||
for (final contact in contacts) {
|
||||
if (!knownContactIds.contains(contact.id)) {
|
||||
await db.addContactId(contact.id);
|
||||
_contactIds!.add(contact.id);
|
||||
}
|
||||
|
||||
final c = await cs.getConversationByJid(contact.jid);
|
||||
if (c != null) {
|
||||
final newConv = await cs.updateConversation(
|
||||
c.id,
|
||||
contactId: contact.id,
|
||||
);
|
||||
sendEvent(
|
||||
ConversationUpdatedEvent(
|
||||
conversation: newConv,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final r = await rs.getRosterItemByJid(contact.jid);
|
||||
if (r != null) {
|
||||
final newRosterItem = await rs.updateRosterItem(
|
||||
r.id,
|
||||
contactId: contact.id,
|
||||
);
|
||||
modifiedRosterItems.add(newRosterItem);
|
||||
} else {
|
||||
// TODO(PapaTutuWawa): Create it
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiedRosterItems.isNotEmpty) {
|
||||
sendEvent(
|
||||
RosterDiffEvent(
|
||||
modified: modifiedRosterItems,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/not_specified.dart';
|
||||
import 'package:moxxyv2/service/preferences.dart';
|
||||
import 'package:moxxyv2/shared/cache.dart';
|
||||
import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
@ -64,6 +65,7 @@ class ConversationService {
|
||||
ChatState? chatState,
|
||||
bool? muted,
|
||||
bool? encrypted,
|
||||
Object? contactId = notSpecified,
|
||||
}) async {
|
||||
final conversation = (await _getConversationById(id))!;
|
||||
var newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
|
||||
@ -76,6 +78,7 @@ class ConversationService {
|
||||
chatState: conversation.chatState,
|
||||
muted: muted,
|
||||
encrypted: encrypted,
|
||||
contactId: contactId,
|
||||
);
|
||||
|
||||
// Copy over the old lastMessage if a new one was not set
|
||||
|
@ -11,6 +11,7 @@ const omemoTrustDeviceListTable = 'OmemoTrustDeviceList';
|
||||
const omemoTrustEnableListTable = 'OmemoTrustEnableList';
|
||||
const omemoFingerprintCache = 'OmemoFingerprintCache';
|
||||
const xmppStateTable = 'XmppState';
|
||||
const contactsTable = 'Contacts';
|
||||
|
||||
const typeString = 0;
|
||||
const typeInt = 1;
|
||||
|
@ -62,21 +62,17 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
// Conversations
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $conversationsTable (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
lastChangeTimestamp INTEGER NOT NULL,
|
||||
unreadCounter INTEGER NOT NULL,
|
||||
open INTEGER NOT NULL,
|
||||
muted INTEGER NOT NULL,
|
||||
encrypted INTEGER NOT NULL,
|
||||
lastMessageId INTEGER,
|
||||
CONSTRAINT fk_last_message FOREIGN KEY (lastMessageId) REFERENCES $messagesTable (id)
|
||||
)''',
|
||||
''',
|
||||
);
|
||||
|
||||
// Contacts
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $contactsTable (
|
||||
id TEXT PRIMARY KEY
|
||||
)'''
|
||||
);
|
||||
|
||||
// Shared media
|
||||
await db.execute(
|
||||
'''
|
||||
@ -102,7 +98,10 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
avatarUrl TEXT NOT NULL,
|
||||
avatarHash TEXT NOT NULL,
|
||||
subscription TEXT NOT NULL,
|
||||
ask TEXT NOT NULL
|
||||
ask TEXT NOT NULL,
|
||||
contactId TEXT,
|
||||
CONSTRAINT fk_contact_id FOREIGN KEY (contactId) REFERENCES $contactsTable (id)
|
||||
ON DELETE SET NULL
|
||||
)''',
|
||||
);
|
||||
|
||||
|
@ -8,6 +8,8 @@ import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:moxxyv2/service/database/creation.dart';
|
||||
import 'package:moxxyv2/service/database/helpers.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_contacts_integration.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_contacts_integration_fixup.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_conversations.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_conversations2.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_conversations3.dart';
|
||||
@ -67,7 +69,7 @@ class DatabaseService {
|
||||
_db = await openDatabase(
|
||||
dbPath,
|
||||
password: key,
|
||||
version: 13,
|
||||
version: 15,
|
||||
onCreate: createDatabase,
|
||||
onConfigure: (db) async {
|
||||
// In order to do schema changes during database upgrades, we disable foreign
|
||||
@ -128,6 +130,14 @@ class DatabaseService {
|
||||
_log.finest('Running migration for database version 13');
|
||||
await upgradeFromV12ToV13(db);
|
||||
}
|
||||
if (oldVersion < 14) {
|
||||
_log.finest('Running migration for database version 14');
|
||||
await upgradeFromV13ToV14(db);
|
||||
}
|
||||
if (oldVersion < 15) {
|
||||
_log.finest('Running migration for database version 15');
|
||||
await upgradeFromV14ToV15(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -209,6 +219,7 @@ class DatabaseService {
|
||||
ChatState? chatState,
|
||||
bool? muted,
|
||||
bool? encrypted,
|
||||
Object? contactId = notSpecified,
|
||||
}) async {
|
||||
final cd = (await _db.query(
|
||||
'Conversations',
|
||||
@ -245,6 +256,9 @@ class DatabaseService {
|
||||
if (encrypted != null) {
|
||||
c['encrypted'] = boolToInt(encrypted);
|
||||
}
|
||||
if (contactId != notSpecified) {
|
||||
c['contactId'] = contactId as String?;
|
||||
}
|
||||
|
||||
await _db.update(
|
||||
'Conversations',
|
||||
@ -636,10 +650,11 @@ class DatabaseService {
|
||||
String? subscription,
|
||||
String? ask,
|
||||
List<String>? groups,
|
||||
Object? contactId = notSpecified,
|
||||
}
|
||||
) async {
|
||||
final id_ = (await _db.query(
|
||||
'RosterItems',
|
||||
rosterTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
@ -666,9 +681,12 @@ class DatabaseService {
|
||||
if (ask != null) {
|
||||
i['ask'] = ask;
|
||||
}
|
||||
if (contactId != notSpecified) {
|
||||
i['contactId'] = contactId as String?;
|
||||
}
|
||||
|
||||
await _db.update(
|
||||
'RosterItems',
|
||||
rosterTable,
|
||||
i,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
@ -1042,4 +1060,27 @@ class DatabaseService {
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<String>> getContactIds() async {
|
||||
return (await _db.query(contactsTable))
|
||||
.map((item) => item['id']! as String)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> addContactId(String id) async {
|
||||
await _db.insert(
|
||||
contactsTable,
|
||||
<String, String>{
|
||||
'id': id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeContactId(String id) async {
|
||||
await _db.delete(
|
||||
contactsTable,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
|
||||
Future<void> upgradeFromV13ToV14(Database db) async {
|
||||
// Create the new table
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $contactsTable (
|
||||
id TEXT PRIMARY KEY
|
||||
)'''
|
||||
);
|
||||
|
||||
// Migrate the conversations
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE ${conversationsTable}_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
lastChangeTimestamp INTEGER NOT NULL,
|
||||
unreadCounter INTEGER NOT NULL,
|
||||
open INTEGER NOT NULL,
|
||||
muted INTEGER NOT NULL,
|
||||
encrypted INTEGER NOT NULL,
|
||||
lastMessageId INTEGER,
|
||||
contactId TEXT,
|
||||
CONSTRAINT fk_last_message FOREIGN KEY (lastMessageId) REFERENCES $messagesTable (id),
|
||||
CONSTRAINT fk_contact_id FOREIGN KEY (contactId) REFERENCES $contactsTable (id)
|
||||
ON DELETE SET NULL
|
||||
)''',
|
||||
);
|
||||
await db.execute('INSERT INTO ${conversationsTable}_new SELECT *, NULL from $conversationsTable');
|
||||
await db.execute('DROP TABLE $conversationsTable;');
|
||||
await db.execute('ALTER TABLE ${conversationsTable}_new RENAME TO $conversationsTable;');
|
||||
|
||||
// Migrate the roster items
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE ${rosterTable}_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
avatarHash TEXT NOT NULL,
|
||||
subscription TEXT NOT NULL,
|
||||
ask TEXT NOT NULL,
|
||||
contactId TEXT,
|
||||
CONSTRAINT fk_contact_id FOREIGN KEY (contactId) REFERENCES $contactsTable (id)
|
||||
ON DELETE SET NULL
|
||||
)''',
|
||||
);
|
||||
await db.execute('INSERT INTO ${rosterTable}_new SELECT *, NULL from $rosterTable');
|
||||
await db.execute('DROP TABLE $rosterTable;');
|
||||
await db.execute('ALTER TABLE ${rosterTable}_new RENAME TO $rosterTable;');
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
|
||||
Future<void> upgradeFromV14ToV15(Database db) async {
|
||||
// Add the missing primary key
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE ${contactsTable}_new (
|
||||
id TEXT PRIMARY KEY
|
||||
)''',
|
||||
);
|
||||
await db.execute('INSERT INTO ${contactsTable}_new SELECT * from $contactsTable');
|
||||
await db.execute('DROP TABLE $contactsTable;');
|
||||
await db.execute('ALTER TABLE ${contactsTable}_new RENAME TO $contactsTable;');
|
||||
}
|
@ -8,6 +8,7 @@ import 'package:moxxyv2/i18n/strings.g.dart';
|
||||
import 'package:moxxyv2/service/avatars.dart';
|
||||
import 'package:moxxyv2/service/blocking.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/contact.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/helpers.dart';
|
||||
import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
|
||||
@ -70,6 +71,7 @@ void setupBackgroundEventHandler() {
|
||||
EventTypeMatcher<AddReactionToMessageCommand>(performAddMessageReaction),
|
||||
EventTypeMatcher<RemoveReactionFromMessageCommand>(performRemoveMessageReaction),
|
||||
EventTypeMatcher<MarkOmemoDeviceAsVerifiedCommand>(performMarkDeviceVerified),
|
||||
EventTypeMatcher<GetContactsCommandDebug>(performGetContacts),
|
||||
]);
|
||||
|
||||
GetIt.I.registerSingleton<EventHandler>(handler);
|
||||
@ -744,3 +746,7 @@ Future<void> performMarkDeviceVerified(MarkOmemoDeviceAsVerifiedCommand command,
|
||||
command.jid,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> performGetContacts(GetContactsCommandDebug command, { dynamic extra }) async {
|
||||
await GetIt.I.get<ContactsService>().scanContacts();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/not_specified.dart';
|
||||
import 'package:moxxyv2/service/service.dart';
|
||||
import 'package:moxxyv2/shared/events.dart';
|
||||
import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
@ -223,6 +224,7 @@ class RosterService {
|
||||
String? subscription,
|
||||
String? ask,
|
||||
List<String>? groups,
|
||||
Object? contactId = notSpecified,
|
||||
}
|
||||
) async {
|
||||
final newItem = await GetIt.I.get<DatabaseService>().updateRosterItem(
|
||||
@ -233,6 +235,7 @@ class RosterService {
|
||||
subscription: subscription,
|
||||
ask: ask,
|
||||
groups: groups,
|
||||
contactId: contactId,
|
||||
);
|
||||
|
||||
// Update cache
|
||||
|
@ -13,6 +13,7 @@ import 'package:moxxyv2/service/avatars.dart';
|
||||
import 'package:moxxyv2/service/blocking.dart';
|
||||
import 'package:moxxyv2/service/connectivity.dart';
|
||||
import 'package:moxxyv2/service/connectivity_watcher.dart';
|
||||
import 'package:moxxyv2/service/contact.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/cryptography/cryptography.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
@ -153,6 +154,7 @@ Future<void> entrypoint() async {
|
||||
GetIt.I.registerSingleton<MessageService>(MessageService());
|
||||
GetIt.I.registerSingleton<OmemoService>(OmemoService());
|
||||
GetIt.I.registerSingleton<CryptographyService>(CryptographyService());
|
||||
GetIt.I.registerSingleton<ContactsService>(ContactsService());
|
||||
final xmpp = XmppService();
|
||||
GetIt.I.registerSingleton<XmppService>(xmpp);
|
||||
|
||||
|
@ -59,6 +59,10 @@ class Conversation with _$Conversation {
|
||||
bool encrypted,
|
||||
// The current chat state
|
||||
@ConversationChatStateConverter() ChatState chatState,
|
||||
{
|
||||
// The id of the contact in the device's phonebook if it exists
|
||||
String? contactId,
|
||||
}
|
||||
) = _Conversation;
|
||||
|
||||
const Conversation._();
|
||||
|
@ -14,6 +14,10 @@ class RosterItem with _$RosterItem {
|
||||
String subscription,
|
||||
String ask,
|
||||
List<String> groups,
|
||||
{
|
||||
// The id of the contact in the device's phonebook if it exists
|
||||
String? contactId,
|
||||
}
|
||||
) = _RosterItem;
|
||||
|
||||
const RosterItem._();
|
||||
|
@ -121,7 +121,8 @@ class ConversationBloc extends Bloc<ConversationEvent, ConversationState> {
|
||||
state: s.toString().split('.').last,
|
||||
jid: state.conversation!.jid,
|
||||
),
|
||||
awaitable: false,);
|
||||
awaitable: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onInit(InitConversationEvent event, Emitter<ConversationState> emit) async {
|
||||
|
@ -15,6 +15,9 @@ import 'package:moxxyv2/ui/widgets/avatar.dart';
|
||||
import 'package:moxxyv2/ui/widgets/conversation.dart';
|
||||
import 'package:moxxyv2/ui/widgets/overview_menu.dart';
|
||||
import 'package:moxxyv2/ui/widgets/topbar.dart';
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxxyv2/shared/commands.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
|
||||
enum ConversationsOptions {
|
||||
settings
|
||||
@ -127,6 +130,7 @@ class ConversationsPageState extends State<ConversationsPage> with TickerProvide
|
||||
itemCount: state.conversations.length,
|
||||
itemBuilder: (_context, index) {
|
||||
final item = state.conversations[index];
|
||||
print('${item.jid} -> ${item.contactId}');
|
||||
final row = ConversationsListRow(
|
||||
maxTextWidth,
|
||||
item,
|
||||
@ -295,7 +299,16 @@ class ConversationsPageState extends State<ConversationsPage> with TickerProvide
|
||||
children: [
|
||||
SpeedDialChild(
|
||||
child: const Icon(Icons.group),
|
||||
onTap: () => showNotImplementedDialog('groupchat', context),
|
||||
onTap: () async {
|
||||
final r = await FlutterContacts.requestPermission(readonly: true);
|
||||
print(r);
|
||||
if (!r) return;
|
||||
|
||||
await MoxplatformPlugin.handler.getDataSender().sendData(
|
||||
GetContactsCommandDebug(),
|
||||
awaitable: false,
|
||||
);
|
||||
},
|
||||
backgroundColor: primaryColor,
|
||||
// TODO(Unknown): Theme dependent?
|
||||
foregroundColor: Colors.white,
|
||||
|
@ -124,6 +124,7 @@ class NewConversationPage extends StatelessWidget {
|
||||
false,
|
||||
false,
|
||||
ChatState.gone,
|
||||
contactId: item.contactId,
|
||||
),
|
||||
false,
|
||||
showTimestamp: false,
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:moxxyv2/i18n/strings.g.dart';
|
||||
import 'package:moxxyv2/shared/constants.dart';
|
||||
@ -22,6 +24,7 @@ class ConversationsListRow extends StatefulWidget {
|
||||
this.showLock = false,
|
||||
this.extra,
|
||||
this.avatarOnTap,
|
||||
this.avatarWidget,
|
||||
super.key,
|
||||
}
|
||||
);
|
||||
@ -31,6 +34,7 @@ class ConversationsListRow extends StatefulWidget {
|
||||
final bool showLock;
|
||||
final bool showTimestamp;
|
||||
final void Function()? avatarOnTap;
|
||||
final Widget? avatarWidget;
|
||||
final Widget? extra;
|
||||
|
||||
@override
|
||||
@ -83,12 +87,17 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildAvatar() {
|
||||
final avatar = AvatarWrapper(
|
||||
radius: 35,
|
||||
avatarUrl: widget.conversation.avatarUrl,
|
||||
altText: widget.conversation.title,
|
||||
);
|
||||
Widget _buildAvatar(Uint8List? data) {
|
||||
final avatar = data != null ?
|
||||
CircleAvatar(
|
||||
radius: 35,
|
||||
backgroundImage: MemoryImage(data),
|
||||
) :
|
||||
AvatarWrapper(
|
||||
radius: 35,
|
||||
avatarUrl: widget.conversation.avatarUrl,
|
||||
altText: widget.conversation.title,
|
||||
);
|
||||
|
||||
if (widget.avatarOnTap != null) {
|
||||
return InkWell(
|
||||
@ -191,9 +200,8 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Widget _build(String title, Uint8List? avatar) {
|
||||
final badgeText = widget.conversation.unreadCounter > 99 ?
|
||||
'99+' :
|
||||
widget.conversation.unreadCounter.toString();
|
||||
@ -205,11 +213,12 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
final sentBySelf = widget.conversation.lastMessage?.sender == GetIt.I.get<UIDataService>().ownJid!;
|
||||
|
||||
final showBadge = widget.conversation.unreadCounter > 0 && !sentBySelf;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildAvatar(),
|
||||
_buildAvatar(avatar),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: LimitedBox(
|
||||
@ -221,7 +230,7 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.conversation.title,
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 17,
|
||||
@ -294,4 +303,27 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.conversation.contactId != null) {
|
||||
return FutureBuilder<Contact?>(
|
||||
future: FlutterContacts.getContact(widget.conversation.contactId!, withThumbnail: true),
|
||||
builder: (_, snapshot) {
|
||||
final hasData = snapshot.hasData && snapshot.data != null;
|
||||
|
||||
if (hasData) {
|
||||
return _build(
|
||||
'${snapshot.data!.name.first} ${snapshot.data!.name.last}',
|
||||
snapshot.data!.thumbnail,
|
||||
);
|
||||
}
|
||||
|
||||
return _build(widget.conversation.title, null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return _build(widget.conversation.title, null);
|
||||
}
|
||||
}
|
||||
|
@ -407,6 +407,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_contacts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_contacts
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.5+1"
|
||||
flutter_driver:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -30,6 +30,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_bloc: 8.1.1
|
||||
flutter_blurhash: 0.7.0
|
||||
flutter_contacts: 1.1.5+1
|
||||
flutter_image_compress: 1.1.0
|
||||
flutter_isolate: 2.0.2
|
||||
flutter_localizations:
|
||||
|
Loading…
Reference in New Issue
Block a user