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.INTERNET" />
 | 
				
			||||||
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 | 
					   <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_EXTERNAL_STORAGE" />
 | 
				
			||||||
 | 
					   <uses-permission android:name="android.permission.READ_CONTACTS" />
 | 
				
			||||||
   <queries>
 | 
					   <queries>
 | 
				
			||||||
     <intent>
 | 
					     <intent>
 | 
				
			||||||
       <action android:name="android.intent.action.VIEW" />
 | 
					       <action android:name="android.intent.action.VIEW" />
 | 
				
			||||||
 | 
				
			|||||||
@ -442,6 +442,11 @@ files:
 | 
				
			|||||||
        attributes:
 | 
					        attributes:
 | 
				
			||||||
          deviceId: int
 | 
					          deviceId: int
 | 
				
			||||||
          jid: String
 | 
					          jid: String
 | 
				
			||||||
 | 
					      - name: GetContactsCommandDebug
 | 
				
			||||||
 | 
					        extends: BackgroundCommand
 | 
				
			||||||
 | 
					        implements:
 | 
				
			||||||
 | 
					          - JsonImplementation
 | 
				
			||||||
 | 
					        attributes:
 | 
				
			||||||
    generate_builder: true
 | 
					    generate_builder: true
 | 
				
			||||||
    # get${builder_Name}FromJson
 | 
					    # get${builder_Name}FromJson
 | 
				
			||||||
    builder_name: "Command"
 | 
					    builder_name: "Command"
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,6 @@ String _cleanBase64String(String original) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AvatarService {
 | 
					class AvatarService {
 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  AvatarService() : _log = Logger('AvatarService');
 | 
					  AvatarService() : _log = Logger('AvatarService');
 | 
				
			||||||
  final Logger _log;
 | 
					  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:moxlib/moxlib.dart';
 | 
				
			||||||
import 'package:moxxmpp/moxxmpp.dart';
 | 
					import 'package:moxxmpp/moxxmpp.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/database.dart';
 | 
					import 'package:moxxyv2/service/database/database.dart';
 | 
				
			||||||
 | 
					import 'package:moxxyv2/service/not_specified.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/preferences.dart';
 | 
					import 'package:moxxyv2/service/preferences.dart';
 | 
				
			||||||
import 'package:moxxyv2/shared/cache.dart';
 | 
					import 'package:moxxyv2/shared/cache.dart';
 | 
				
			||||||
import 'package:moxxyv2/shared/models/conversation.dart';
 | 
					import 'package:moxxyv2/shared/models/conversation.dart';
 | 
				
			||||||
@ -64,6 +65,7 @@ class ConversationService {
 | 
				
			|||||||
    ChatState? chatState,
 | 
					    ChatState? chatState,
 | 
				
			||||||
    bool? muted,
 | 
					    bool? muted,
 | 
				
			||||||
    bool? encrypted,
 | 
					    bool? encrypted,
 | 
				
			||||||
 | 
					    Object? contactId = notSpecified,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
    final conversation = (await _getConversationById(id))!;
 | 
					    final conversation = (await _getConversationById(id))!;
 | 
				
			||||||
    var newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
 | 
					    var newConversation = await GetIt.I.get<DatabaseService>().updateConversation(
 | 
				
			||||||
@ -76,6 +78,7 @@ class ConversationService {
 | 
				
			|||||||
      chatState: conversation.chatState,
 | 
					      chatState: conversation.chatState,
 | 
				
			||||||
      muted: muted,
 | 
					      muted: muted,
 | 
				
			||||||
      encrypted: encrypted,
 | 
					      encrypted: encrypted,
 | 
				
			||||||
 | 
					      contactId: contactId,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Copy over the old lastMessage if a new one was not set
 | 
					    // Copy over the old lastMessage if a new one was not set
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ const omemoTrustDeviceListTable = 'OmemoTrustDeviceList';
 | 
				
			|||||||
const omemoTrustEnableListTable = 'OmemoTrustEnableList';
 | 
					const omemoTrustEnableListTable = 'OmemoTrustEnableList';
 | 
				
			||||||
const omemoFingerprintCache = 'OmemoFingerprintCache';
 | 
					const omemoFingerprintCache = 'OmemoFingerprintCache';
 | 
				
			||||||
const xmppStateTable = 'XmppState';
 | 
					const xmppStateTable = 'XmppState';
 | 
				
			||||||
 | 
					const contactsTable = 'Contacts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const typeString = 0;
 | 
					const typeString = 0;
 | 
				
			||||||
const typeInt = 1;
 | 
					const typeInt = 1;
 | 
				
			||||||
 | 
				
			|||||||
@ -62,19 +62,15 @@ Future<void> createDatabase(Database db, int version) async {
 | 
				
			|||||||
  // Conversations
 | 
					  // Conversations
 | 
				
			||||||
  await db.execute(
 | 
					  await db.execute(
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    CREATE TABLE $conversationsTable (
 | 
					''',
 | 
				
			||||||
      id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					  );
 | 
				
			||||||
      jid TEXT NOT NULL,
 | 
					
 | 
				
			||||||
      title TEXT NOT NULL,
 | 
					  // Contacts
 | 
				
			||||||
      avatarUrl TEXT NOT NULL,
 | 
					  await db.execute(
 | 
				
			||||||
      lastChangeTimestamp INTEGER NOT NULL,
 | 
					    '''
 | 
				
			||||||
      unreadCounter INTEGER NOT NULL,
 | 
					    CREATE TABLE $contactsTable (
 | 
				
			||||||
      open INTEGER NOT NULL,
 | 
					      id TEXT PRIMARY KEY
 | 
				
			||||||
      muted INTEGER NOT NULL,
 | 
					    )'''
 | 
				
			||||||
      encrypted INTEGER NOT NULL,
 | 
					 | 
				
			||||||
      lastMessageId INTEGER,
 | 
					 | 
				
			||||||
      CONSTRAINT fk_last_message FOREIGN KEY (lastMessageId) REFERENCES $messagesTable (id)
 | 
					 | 
				
			||||||
    )''',
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  // Shared media
 | 
					  // Shared media
 | 
				
			||||||
@ -102,7 +98,10 @@ Future<void> createDatabase(Database db, int version) async {
 | 
				
			|||||||
      avatarUrl TEXT NOT NULL,
 | 
					      avatarUrl TEXT NOT NULL,
 | 
				
			||||||
      avatarHash TEXT NOT NULL,
 | 
					      avatarHash TEXT NOT NULL,
 | 
				
			||||||
      subscription 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/constants.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/creation.dart';
 | 
					import 'package:moxxyv2/service/database/creation.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/helpers.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_conversations.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/migrations/0000_conversations2.dart';
 | 
					import 'package:moxxyv2/service/database/migrations/0000_conversations2.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/migrations/0000_conversations3.dart';
 | 
					import 'package:moxxyv2/service/database/migrations/0000_conversations3.dart';
 | 
				
			||||||
@ -67,7 +69,7 @@ class DatabaseService {
 | 
				
			|||||||
    _db = await openDatabase(
 | 
					    _db = await openDatabase(
 | 
				
			||||||
      dbPath,
 | 
					      dbPath,
 | 
				
			||||||
      password: key,
 | 
					      password: key,
 | 
				
			||||||
      version: 13,
 | 
					      version: 15,
 | 
				
			||||||
      onCreate: createDatabase,
 | 
					      onCreate: createDatabase,
 | 
				
			||||||
      onConfigure: (db) async {
 | 
					      onConfigure: (db) async {
 | 
				
			||||||
        // In order to do schema changes during database upgrades, we disable foreign
 | 
					        // 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');
 | 
					          _log.finest('Running migration for database version 13');
 | 
				
			||||||
          await upgradeFromV12ToV13(db);
 | 
					          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,
 | 
					    ChatState? chatState,
 | 
				
			||||||
    bool? muted,
 | 
					    bool? muted,
 | 
				
			||||||
    bool? encrypted,
 | 
					    bool? encrypted,
 | 
				
			||||||
 | 
					    Object? contactId = notSpecified,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
    final cd = (await _db.query(
 | 
					    final cd = (await _db.query(
 | 
				
			||||||
      'Conversations',
 | 
					      'Conversations',
 | 
				
			||||||
@ -245,6 +256,9 @@ class DatabaseService {
 | 
				
			|||||||
    if (encrypted != null) {
 | 
					    if (encrypted != null) {
 | 
				
			||||||
      c['encrypted'] = boolToInt(encrypted);
 | 
					      c['encrypted'] = boolToInt(encrypted);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (contactId != notSpecified) {
 | 
				
			||||||
 | 
					      c['contactId'] = contactId as String?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await _db.update(
 | 
					    await _db.update(
 | 
				
			||||||
      'Conversations',
 | 
					      'Conversations',
 | 
				
			||||||
@ -636,10 +650,11 @@ class DatabaseService {
 | 
				
			|||||||
      String? subscription,
 | 
					      String? subscription,
 | 
				
			||||||
      String? ask,
 | 
					      String? ask,
 | 
				
			||||||
      List<String>? groups,
 | 
					      List<String>? groups,
 | 
				
			||||||
 | 
					      Object? contactId = notSpecified,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
    final id_ = (await _db.query(
 | 
					    final id_ = (await _db.query(
 | 
				
			||||||
      'RosterItems',
 | 
					      rosterTable,
 | 
				
			||||||
      where: 'id = ?',
 | 
					      where: 'id = ?',
 | 
				
			||||||
      whereArgs: [id],
 | 
					      whereArgs: [id],
 | 
				
			||||||
      limit: 1,
 | 
					      limit: 1,
 | 
				
			||||||
@ -666,9 +681,12 @@ class DatabaseService {
 | 
				
			|||||||
    if (ask != null) {
 | 
					    if (ask != null) {
 | 
				
			||||||
      i['ask'] = ask;
 | 
					      i['ask'] = ask;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (contactId != notSpecified) {
 | 
				
			||||||
 | 
					      i['contactId'] = contactId as String?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await _db.update(
 | 
					    await _db.update(
 | 
				
			||||||
      'RosterItems',
 | 
					      rosterTable,
 | 
				
			||||||
      i,
 | 
					      i,
 | 
				
			||||||
      where: 'id = ?',
 | 
					      where: 'id = ?',
 | 
				
			||||||
      whereArgs: [id],
 | 
					      whereArgs: [id],
 | 
				
			||||||
@ -1042,4 +1060,27 @@ class DatabaseService {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      .toList();
 | 
					      .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/avatars.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/blocking.dart';
 | 
					import 'package:moxxyv2/service/blocking.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/conversation.dart';
 | 
					import 'package:moxxyv2/service/conversation.dart';
 | 
				
			||||||
 | 
					import 'package:moxxyv2/service/contact.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/database.dart';
 | 
					import 'package:moxxyv2/service/database/database.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/helpers.dart';
 | 
					import 'package:moxxyv2/service/helpers.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
 | 
					import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
 | 
				
			||||||
@ -70,6 +71,7 @@ void setupBackgroundEventHandler() {
 | 
				
			|||||||
      EventTypeMatcher<AddReactionToMessageCommand>(performAddMessageReaction),
 | 
					      EventTypeMatcher<AddReactionToMessageCommand>(performAddMessageReaction),
 | 
				
			||||||
      EventTypeMatcher<RemoveReactionFromMessageCommand>(performRemoveMessageReaction),
 | 
					      EventTypeMatcher<RemoveReactionFromMessageCommand>(performRemoveMessageReaction),
 | 
				
			||||||
      EventTypeMatcher<MarkOmemoDeviceAsVerifiedCommand>(performMarkDeviceVerified),
 | 
					      EventTypeMatcher<MarkOmemoDeviceAsVerifiedCommand>(performMarkDeviceVerified),
 | 
				
			||||||
 | 
					      EventTypeMatcher<GetContactsCommandDebug>(performGetContacts),
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GetIt.I.registerSingleton<EventHandler>(handler);
 | 
					  GetIt.I.registerSingleton<EventHandler>(handler);
 | 
				
			||||||
@ -744,3 +746,7 @@ Future<void> performMarkDeviceVerified(MarkOmemoDeviceAsVerifiedCommand command,
 | 
				
			|||||||
    command.jid,
 | 
					    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:moxxmpp/moxxmpp.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/conversation.dart';
 | 
					import 'package:moxxyv2/service/conversation.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/database.dart';
 | 
					import 'package:moxxyv2/service/database/database.dart';
 | 
				
			||||||
 | 
					import 'package:moxxyv2/service/not_specified.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/service.dart';
 | 
					import 'package:moxxyv2/service/service.dart';
 | 
				
			||||||
import 'package:moxxyv2/shared/events.dart';
 | 
					import 'package:moxxyv2/shared/events.dart';
 | 
				
			||||||
import 'package:moxxyv2/shared/models/conversation.dart';
 | 
					import 'package:moxxyv2/shared/models/conversation.dart';
 | 
				
			||||||
@ -223,6 +224,7 @@ class RosterService {
 | 
				
			|||||||
      String? subscription,
 | 
					      String? subscription,
 | 
				
			||||||
      String? ask,
 | 
					      String? ask,
 | 
				
			||||||
      List<String>? groups,
 | 
					      List<String>? groups,
 | 
				
			||||||
 | 
					      Object? contactId = notSpecified,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
    final newItem = await GetIt.I.get<DatabaseService>().updateRosterItem(
 | 
					    final newItem = await GetIt.I.get<DatabaseService>().updateRosterItem(
 | 
				
			||||||
@ -233,6 +235,7 @@ class RosterService {
 | 
				
			|||||||
      subscription: subscription,
 | 
					      subscription: subscription,
 | 
				
			||||||
      ask: ask,
 | 
					      ask: ask,
 | 
				
			||||||
      groups: groups,
 | 
					      groups: groups,
 | 
				
			||||||
 | 
					      contactId: contactId,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update cache
 | 
					    // Update cache
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ import 'package:moxxyv2/service/avatars.dart';
 | 
				
			|||||||
import 'package:moxxyv2/service/blocking.dart';
 | 
					import 'package:moxxyv2/service/blocking.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/connectivity.dart';
 | 
					import 'package:moxxyv2/service/connectivity.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/connectivity_watcher.dart';
 | 
					import 'package:moxxyv2/service/connectivity_watcher.dart';
 | 
				
			||||||
 | 
					import 'package:moxxyv2/service/contact.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/conversation.dart';
 | 
					import 'package:moxxyv2/service/conversation.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/cryptography/cryptography.dart';
 | 
					import 'package:moxxyv2/service/cryptography/cryptography.dart';
 | 
				
			||||||
import 'package:moxxyv2/service/database/database.dart';
 | 
					import 'package:moxxyv2/service/database/database.dart';
 | 
				
			||||||
@ -153,6 +154,7 @@ Future<void> entrypoint() async {
 | 
				
			|||||||
  GetIt.I.registerSingleton<MessageService>(MessageService());
 | 
					  GetIt.I.registerSingleton<MessageService>(MessageService());
 | 
				
			||||||
  GetIt.I.registerSingleton<OmemoService>(OmemoService());
 | 
					  GetIt.I.registerSingleton<OmemoService>(OmemoService());
 | 
				
			||||||
  GetIt.I.registerSingleton<CryptographyService>(CryptographyService());
 | 
					  GetIt.I.registerSingleton<CryptographyService>(CryptographyService());
 | 
				
			||||||
 | 
					  GetIt.I.registerSingleton<ContactsService>(ContactsService());
 | 
				
			||||||
  final xmpp = XmppService();
 | 
					  final xmpp = XmppService();
 | 
				
			||||||
  GetIt.I.registerSingleton<XmppService>(xmpp);
 | 
					  GetIt.I.registerSingleton<XmppService>(xmpp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,10 @@ class Conversation with _$Conversation {
 | 
				
			|||||||
    bool encrypted,
 | 
					    bool encrypted,
 | 
				
			||||||
    // The current chat state
 | 
					    // The current chat state
 | 
				
			||||||
    @ConversationChatStateConverter() ChatState chatState,
 | 
					    @ConversationChatStateConverter() ChatState chatState,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      // The id of the contact in the device's phonebook if it exists
 | 
				
			||||||
 | 
					      String? contactId,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  ) = _Conversation;
 | 
					  ) = _Conversation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const Conversation._();
 | 
					  const Conversation._();
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,10 @@ class RosterItem with _$RosterItem {
 | 
				
			|||||||
    String subscription,
 | 
					    String subscription,
 | 
				
			||||||
    String ask,
 | 
					    String ask,
 | 
				
			||||||
    List<String> groups,
 | 
					    List<String> groups,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      // The id of the contact in the device's phonebook if it exists
 | 
				
			||||||
 | 
					      String? contactId,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  ) = _RosterItem;
 | 
					  ) = _RosterItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const RosterItem._();
 | 
					  const RosterItem._();
 | 
				
			||||||
 | 
				
			|||||||
@ -121,7 +121,8 @@ class ConversationBloc extends Bloc<ConversationEvent, ConversationState> {
 | 
				
			|||||||
        state: s.toString().split('.').last,
 | 
					        state: s.toString().split('.').last,
 | 
				
			||||||
        jid: state.conversation!.jid,
 | 
					        jid: state.conversation!.jid,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      awaitable: false,);
 | 
					      awaitable: false,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  Future<void> _onInit(InitConversationEvent event, Emitter<ConversationState> emit) async {
 | 
					  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/conversation.dart';
 | 
				
			||||||
import 'package:moxxyv2/ui/widgets/overview_menu.dart';
 | 
					import 'package:moxxyv2/ui/widgets/overview_menu.dart';
 | 
				
			||||||
import 'package:moxxyv2/ui/widgets/topbar.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 {
 | 
					enum ConversationsOptions {
 | 
				
			||||||
  settings
 | 
					  settings
 | 
				
			||||||
@ -127,6 +130,7 @@ class ConversationsPageState extends State<ConversationsPage> with TickerProvide
 | 
				
			|||||||
        itemCount: state.conversations.length,
 | 
					        itemCount: state.conversations.length,
 | 
				
			||||||
        itemBuilder: (_context, index) {
 | 
					        itemBuilder: (_context, index) {
 | 
				
			||||||
          final item = state.conversations[index];
 | 
					          final item = state.conversations[index];
 | 
				
			||||||
 | 
					          print('${item.jid} -> ${item.contactId}');
 | 
				
			||||||
          final row = ConversationsListRow(
 | 
					          final row = ConversationsListRow(
 | 
				
			||||||
            maxTextWidth,
 | 
					            maxTextWidth,
 | 
				
			||||||
            item,
 | 
					            item,
 | 
				
			||||||
@ -295,7 +299,16 @@ class ConversationsPageState extends State<ConversationsPage> with TickerProvide
 | 
				
			|||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            SpeedDialChild(
 | 
					            SpeedDialChild(
 | 
				
			||||||
              child: const Icon(Icons.group),
 | 
					              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,
 | 
					              backgroundColor: primaryColor,
 | 
				
			||||||
              // TODO(Unknown): Theme dependent?
 | 
					              // TODO(Unknown): Theme dependent?
 | 
				
			||||||
              foregroundColor: Colors.white,
 | 
					              foregroundColor: Colors.white,
 | 
				
			||||||
 | 
				
			|||||||
@ -124,6 +124,7 @@ class NewConversationPage extends StatelessWidget {
 | 
				
			|||||||
                        false,
 | 
					                        false,
 | 
				
			||||||
                        false,
 | 
					                        false,
 | 
				
			||||||
                        ChatState.gone,
 | 
					                        ChatState.gone,
 | 
				
			||||||
 | 
					                        contactId: item.contactId,
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                      false,
 | 
					                      false,
 | 
				
			||||||
                      showTimestamp: false,
 | 
					                      showTimestamp: false,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					import 'dart:typed_data';
 | 
				
			||||||
import 'package:badges/badges.dart';
 | 
					import 'package:badges/badges.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_contacts/flutter_contacts.dart';
 | 
				
			||||||
import 'package:get_it/get_it.dart';
 | 
					import 'package:get_it/get_it.dart';
 | 
				
			||||||
import 'package:moxxyv2/i18n/strings.g.dart';
 | 
					import 'package:moxxyv2/i18n/strings.g.dart';
 | 
				
			||||||
import 'package:moxxyv2/shared/constants.dart';
 | 
					import 'package:moxxyv2/shared/constants.dart';
 | 
				
			||||||
@ -22,6 +24,7 @@ class ConversationsListRow extends StatefulWidget {
 | 
				
			|||||||
      this.showLock = false,
 | 
					      this.showLock = false,
 | 
				
			||||||
      this.extra,
 | 
					      this.extra,
 | 
				
			||||||
      this.avatarOnTap,
 | 
					      this.avatarOnTap,
 | 
				
			||||||
 | 
					      this.avatarWidget,
 | 
				
			||||||
      super.key,
 | 
					      super.key,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@ -31,6 +34,7 @@ class ConversationsListRow extends StatefulWidget {
 | 
				
			|||||||
  final bool showLock;
 | 
					  final bool showLock;
 | 
				
			||||||
  final bool showTimestamp;
 | 
					  final bool showTimestamp;
 | 
				
			||||||
  final void Function()? avatarOnTap;
 | 
					  final void Function()? avatarOnTap;
 | 
				
			||||||
 | 
					  final Widget? avatarWidget;
 | 
				
			||||||
  final Widget? extra;
 | 
					  final Widget? extra;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -83,12 +87,17 @@ class ConversationsListRowState extends State<ConversationsListRow> {
 | 
				
			|||||||
    super.dispose();
 | 
					    super.dispose();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Widget _buildAvatar() {
 | 
					  Widget _buildAvatar(Uint8List? data) {
 | 
				
			||||||
    final avatar = AvatarWrapper(
 | 
					    final avatar = data != null ?
 | 
				
			||||||
      radius: 35,
 | 
					      CircleAvatar(
 | 
				
			||||||
      avatarUrl: widget.conversation.avatarUrl,
 | 
					        radius: 35,
 | 
				
			||||||
      altText: widget.conversation.title,
 | 
					        backgroundImage: MemoryImage(data),
 | 
				
			||||||
    );
 | 
					      ) : 
 | 
				
			||||||
 | 
					      AvatarWrapper(
 | 
				
			||||||
 | 
					        radius: 35,
 | 
				
			||||||
 | 
					        avatarUrl: widget.conversation.avatarUrl,
 | 
				
			||||||
 | 
					        altText: widget.conversation.title,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (widget.avatarOnTap != null) {
 | 
					    if (widget.avatarOnTap != null) {
 | 
				
			||||||
      return InkWell(
 | 
					      return InkWell(
 | 
				
			||||||
@ -192,8 +201,7 @@ class ConversationsListRowState extends State<ConversationsListRow> {
 | 
				
			|||||||
    return const SizedBox();
 | 
					    return const SizedBox();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  Widget _build(String title, Uint8List? avatar) {
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    final badgeText = widget.conversation.unreadCounter > 99 ?
 | 
					    final badgeText = widget.conversation.unreadCounter > 99 ?
 | 
				
			||||||
      '99+' :
 | 
					      '99+' :
 | 
				
			||||||
      widget.conversation.unreadCounter.toString();
 | 
					      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 sentBySelf = widget.conversation.lastMessage?.sender == GetIt.I.get<UIDataService>().ownJid!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final showBadge = widget.conversation.unreadCounter > 0 && !sentBySelf;
 | 
					    final showBadge = widget.conversation.unreadCounter > 0 && !sentBySelf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Padding(
 | 
					    return Padding(
 | 
				
			||||||
      padding: const EdgeInsets.all(8),
 | 
					      padding: const EdgeInsets.all(8),
 | 
				
			||||||
      child: Row(
 | 
					      child: Row(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          _buildAvatar(),
 | 
					          _buildAvatar(avatar),
 | 
				
			||||||
          Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: const EdgeInsets.only(left: 8),
 | 
					            padding: const EdgeInsets.only(left: 8),
 | 
				
			||||||
            child: LimitedBox(
 | 
					            child: LimitedBox(
 | 
				
			||||||
@ -221,7 +230,7 @@ class ConversationsListRowState extends State<ConversationsListRow> {
 | 
				
			|||||||
                    mainAxisSize: MainAxisSize.min,
 | 
					                    mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
                    children: [
 | 
					                    children: [
 | 
				
			||||||
                      Text(
 | 
					                      Text(
 | 
				
			||||||
                        widget.conversation.title,
 | 
					                        title,
 | 
				
			||||||
                        style: const TextStyle(
 | 
					                        style: const TextStyle(
 | 
				
			||||||
                          fontWeight: FontWeight.bold,
 | 
					                          fontWeight: FontWeight.bold,
 | 
				
			||||||
                          fontSize: 17,
 | 
					                          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"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.7.0"
 | 
					    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:
 | 
					  flutter_driver:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description: flutter
 | 
					    description: flutter
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@ dependencies:
 | 
				
			|||||||
    sdk: flutter
 | 
					    sdk: flutter
 | 
				
			||||||
  flutter_bloc: 8.1.1
 | 
					  flutter_bloc: 8.1.1
 | 
				
			||||||
  flutter_blurhash: 0.7.0
 | 
					  flutter_blurhash: 0.7.0
 | 
				
			||||||
 | 
					  flutter_contacts: 1.1.5+1
 | 
				
			||||||
  flutter_image_compress: 1.1.0
 | 
					  flutter_image_compress: 1.1.0
 | 
				
			||||||
  flutter_isolate: 2.0.2
 | 
					  flutter_isolate: 2.0.2
 | 
				
			||||||
  flutter_localizations:
 | 
					  flutter_localizations:
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user