mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
custom avatar
This commit is contained in:
parent
0de6d40aa4
commit
ec095fea9c
26 changed files with 374 additions and 66 deletions
21
ios/Podfile
21
ios/Podfile
|
|
@ -46,8 +46,29 @@ end
|
|||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
|
||||
|
||||
# You can remove unused permissions here
|
||||
# for more information: https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
|
||||
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'$(inherited)',
|
||||
|
||||
## dart: PermissionGroup.camera
|
||||
'PERMISSION_CAMERA=1',
|
||||
|
||||
## dart: PermissionGroup.microphone
|
||||
'PERMISSION_MICROPHONE=1',
|
||||
|
||||
## dart: PermissionGroup.notification
|
||||
'PERMISSION_NOTIFICATIONS=1',
|
||||
|
||||
## dart: PermissionGroup.mediaLibrary
|
||||
'PERMISSION_PHOTOS=1',
|
||||
]
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -339,6 +339,6 @@ SPEC CHECKSUMS:
|
|||
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
|
||||
PODFILE CHECKSUM: 84a7d8d37f41d292cbd8505494629ed779242400
|
||||
PODFILE CHECKSUM: eda8ac661dab0c3d1e1b175d40ebbf2becd0ce86
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
|||
|
|
@ -45,10 +45,13 @@
|
|||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>To create photos that can be shared.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>To create videos that can be securely shared.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Store photos in the gallery.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>To protect others twonlies!</string>
|
||||
</dict>
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ class UserCheckbox extends StatelessWidget {
|
|||
),
|
||||
child: Row(
|
||||
children: [
|
||||
InitialsAvatar(
|
||||
displayName,
|
||||
ContactAvatar(
|
||||
contact: user,
|
||||
fontSize: 12,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,66 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:twonly/src/database/tables/contacts_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/json_models/userdata.dart';
|
||||
|
||||
class InitialsAvatar extends StatelessWidget {
|
||||
final String displayName;
|
||||
class ContactAvatar extends StatelessWidget {
|
||||
final Contact? contact;
|
||||
final UserData? userData;
|
||||
final double? fontSize;
|
||||
|
||||
const InitialsAvatar(this.displayName, {super.key, this.fontSize = 20});
|
||||
const ContactAvatar(
|
||||
{super.key, this.contact, this.userData, this.fontSize = 20});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String displayName = "";
|
||||
String? avatarSvg;
|
||||
|
||||
if (contact != null) {
|
||||
displayName = getContactDisplayName(contact!);
|
||||
avatarSvg = contact!.avatarSvg;
|
||||
} else if (userData != null) {
|
||||
displayName = userData!.displayName;
|
||||
avatarSvg = userData!.avatarSvg;
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
|
||||
double proSize = (fontSize == null) ? 40 : (fontSize! * 2);
|
||||
|
||||
if (avatarSvg != null) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 2 * (fontSize ?? 20),
|
||||
minWidth: 2 * (fontSize ?? 20),
|
||||
maxWidth: 2 * (fontSize ?? 20),
|
||||
maxHeight: 2 * (fontSize ?? 20),
|
||||
),
|
||||
child: Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: SizedBox(
|
||||
height: proSize,
|
||||
width: proSize,
|
||||
child: Center(
|
||||
// child: Container(
|
||||
// color: Colors.green,
|
||||
// ),
|
||||
child: SvgPicture.string(
|
||||
avatarSvg,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
print("Error: $error");
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Extract initials from the displayName
|
||||
List<String> nameParts = displayName.split(' ');
|
||||
String initials = nameParts.map((part) => part[0]).join().toUpperCase();
|
||||
|
|
@ -35,8 +88,6 @@ class InitialsAvatar extends StatelessWidget {
|
|||
|
||||
bool isPro = initials[0] == "T";
|
||||
|
||||
double proSize = (fontSize == null) ? 40 : (fontSize! * 2);
|
||||
|
||||
return isPro
|
||||
? //or 15.0
|
||||
Container(
|
||||
|
|
@ -60,7 +111,10 @@ class InitialsAvatar extends StatelessWidget {
|
|||
),
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundColor: avatarColor, radius: fontSize, child: child);
|
||||
backgroundColor: avatarColor,
|
||||
radius: fontSize,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Color _getTextColor(Color color) {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
|||
.watch();
|
||||
}
|
||||
|
||||
Future<List<Contact>> getAllNotBlockedContacts() {
|
||||
return (select(contacts)
|
||||
..where((t) => t.accepted.equals(true) & t.blocked.equals(false))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)]))
|
||||
.get();
|
||||
}
|
||||
|
||||
Stream<int?> watchContactsBlocked() {
|
||||
final count = contacts.blocked.count(distinct: true);
|
||||
final query = selectOnly(contacts)..where(contacts.blocked.equals(true));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ class Contacts extends Table {
|
|||
TextColumn get username => text().unique()();
|
||||
TextColumn get displayName => text().nullable()();
|
||||
TextColumn get nickName => text().nullable()();
|
||||
TextColumn get avatarSvg => text().nullable()();
|
||||
|
||||
IntColumn get myAvatarCounter => integer().withDefault(Constant(0))();
|
||||
|
||||
BoolColumn get accepted => boolean().withDefault(Constant(false))();
|
||||
BoolColumn get requested => boolean().withDefault(Constant(false))();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,20 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
late final GeneratedColumn<String> nickName = GeneratedColumn<String>(
|
||||
'nick_name', aliasedName, true,
|
||||
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const VerificationMeta _avatarSvgMeta =
|
||||
const VerificationMeta('avatarSvg');
|
||||
@override
|
||||
late final GeneratedColumn<String> avatarSvg = GeneratedColumn<String>(
|
||||
'avatar_svg', aliasedName, true,
|
||||
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const VerificationMeta _myAvatarCounterMeta =
|
||||
const VerificationMeta('myAvatarCounter');
|
||||
@override
|
||||
late final GeneratedColumn<int> myAvatarCounter = GeneratedColumn<int>(
|
||||
'my_avatar_counter', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: Constant(0));
|
||||
static const VerificationMeta _acceptedMeta =
|
||||
const VerificationMeta('accepted');
|
||||
@override
|
||||
|
|
@ -129,6 +143,8 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
username,
|
||||
displayName,
|
||||
nickName,
|
||||
avatarSvg,
|
||||
myAvatarCounter,
|
||||
accepted,
|
||||
requested,
|
||||
blocked,
|
||||
|
|
@ -171,6 +187,16 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
context.handle(_nickNameMeta,
|
||||
nickName.isAcceptableOrUnknown(data['nick_name']!, _nickNameMeta));
|
||||
}
|
||||
if (data.containsKey('avatar_svg')) {
|
||||
context.handle(_avatarSvgMeta,
|
||||
avatarSvg.isAcceptableOrUnknown(data['avatar_svg']!, _avatarSvgMeta));
|
||||
}
|
||||
if (data.containsKey('my_avatar_counter')) {
|
||||
context.handle(
|
||||
_myAvatarCounterMeta,
|
||||
myAvatarCounter.isAcceptableOrUnknown(
|
||||
data['my_avatar_counter']!, _myAvatarCounterMeta));
|
||||
}
|
||||
if (data.containsKey('accepted')) {
|
||||
context.handle(_acceptedMeta,
|
||||
accepted.isAcceptableOrUnknown(data['accepted']!, _acceptedMeta));
|
||||
|
|
@ -244,6 +270,10 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
.read(DriftSqlType.string, data['${effectivePrefix}display_name']),
|
||||
nickName: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}nick_name']),
|
||||
avatarSvg: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}avatar_svg']),
|
||||
myAvatarCounter: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}my_avatar_counter'])!,
|
||||
accepted: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!,
|
||||
requested: attachedDatabase.typeMapping
|
||||
|
|
@ -283,6 +313,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
final String username;
|
||||
final String? displayName;
|
||||
final String? nickName;
|
||||
final String? avatarSvg;
|
||||
final int myAvatarCounter;
|
||||
final bool accepted;
|
||||
final bool requested;
|
||||
final bool blocked;
|
||||
|
|
@ -299,6 +331,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
required this.username,
|
||||
this.displayName,
|
||||
this.nickName,
|
||||
this.avatarSvg,
|
||||
required this.myAvatarCounter,
|
||||
required this.accepted,
|
||||
required this.requested,
|
||||
required this.blocked,
|
||||
|
|
@ -321,6 +355,10 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
if (!nullToAbsent || nickName != null) {
|
||||
map['nick_name'] = Variable<String>(nickName);
|
||||
}
|
||||
if (!nullToAbsent || avatarSvg != null) {
|
||||
map['avatar_svg'] = Variable<String>(avatarSvg);
|
||||
}
|
||||
map['my_avatar_counter'] = Variable<int>(myAvatarCounter);
|
||||
map['accepted'] = Variable<bool>(accepted);
|
||||
map['requested'] = Variable<bool>(requested);
|
||||
map['blocked'] = Variable<bool>(blocked);
|
||||
|
|
@ -352,6 +390,10 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
nickName: nickName == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(nickName),
|
||||
avatarSvg: avatarSvg == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(avatarSvg),
|
||||
myAvatarCounter: Value(myAvatarCounter),
|
||||
accepted: Value(accepted),
|
||||
requested: Value(requested),
|
||||
blocked: Value(blocked),
|
||||
|
|
@ -380,6 +422,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
username: serializer.fromJson<String>(json['username']),
|
||||
displayName: serializer.fromJson<String?>(json['displayName']),
|
||||
nickName: serializer.fromJson<String?>(json['nickName']),
|
||||
avatarSvg: serializer.fromJson<String?>(json['avatarSvg']),
|
||||
myAvatarCounter: serializer.fromJson<int>(json['myAvatarCounter']),
|
||||
accepted: serializer.fromJson<bool>(json['accepted']),
|
||||
requested: serializer.fromJson<bool>(json['requested']),
|
||||
blocked: serializer.fromJson<bool>(json['blocked']),
|
||||
|
|
@ -404,6 +448,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
'username': serializer.toJson<String>(username),
|
||||
'displayName': serializer.toJson<String?>(displayName),
|
||||
'nickName': serializer.toJson<String?>(nickName),
|
||||
'avatarSvg': serializer.toJson<String?>(avatarSvg),
|
||||
'myAvatarCounter': serializer.toJson<int>(myAvatarCounter),
|
||||
'accepted': serializer.toJson<bool>(accepted),
|
||||
'requested': serializer.toJson<bool>(requested),
|
||||
'blocked': serializer.toJson<bool>(blocked),
|
||||
|
|
@ -424,6 +470,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
String? username,
|
||||
Value<String?> displayName = const Value.absent(),
|
||||
Value<String?> nickName = const Value.absent(),
|
||||
Value<String?> avatarSvg = const Value.absent(),
|
||||
int? myAvatarCounter,
|
||||
bool? accepted,
|
||||
bool? requested,
|
||||
bool? blocked,
|
||||
|
|
@ -440,6 +488,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
username: username ?? this.username,
|
||||
displayName: displayName.present ? displayName.value : this.displayName,
|
||||
nickName: nickName.present ? nickName.value : this.nickName,
|
||||
avatarSvg: avatarSvg.present ? avatarSvg.value : this.avatarSvg,
|
||||
myAvatarCounter: myAvatarCounter ?? this.myAvatarCounter,
|
||||
accepted: accepted ?? this.accepted,
|
||||
requested: requested ?? this.requested,
|
||||
blocked: blocked ?? this.blocked,
|
||||
|
|
@ -465,6 +515,10 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
displayName:
|
||||
data.displayName.present ? data.displayName.value : this.displayName,
|
||||
nickName: data.nickName.present ? data.nickName.value : this.nickName,
|
||||
avatarSvg: data.avatarSvg.present ? data.avatarSvg.value : this.avatarSvg,
|
||||
myAvatarCounter: data.myAvatarCounter.present
|
||||
? data.myAvatarCounter.value
|
||||
: this.myAvatarCounter,
|
||||
accepted: data.accepted.present ? data.accepted.value : this.accepted,
|
||||
requested: data.requested.present ? data.requested.value : this.requested,
|
||||
blocked: data.blocked.present ? data.blocked.value : this.blocked,
|
||||
|
|
@ -498,6 +552,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
..write('username: $username, ')
|
||||
..write('displayName: $displayName, ')
|
||||
..write('nickName: $nickName, ')
|
||||
..write('avatarSvg: $avatarSvg, ')
|
||||
..write('myAvatarCounter: $myAvatarCounter, ')
|
||||
..write('accepted: $accepted, ')
|
||||
..write('requested: $requested, ')
|
||||
..write('blocked: $blocked, ')
|
||||
|
|
@ -519,6 +575,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
username,
|
||||
displayName,
|
||||
nickName,
|
||||
avatarSvg,
|
||||
myAvatarCounter,
|
||||
accepted,
|
||||
requested,
|
||||
blocked,
|
||||
|
|
@ -538,6 +596,8 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
other.username == this.username &&
|
||||
other.displayName == this.displayName &&
|
||||
other.nickName == this.nickName &&
|
||||
other.avatarSvg == this.avatarSvg &&
|
||||
other.myAvatarCounter == this.myAvatarCounter &&
|
||||
other.accepted == this.accepted &&
|
||||
other.requested == this.requested &&
|
||||
other.blocked == this.blocked &&
|
||||
|
|
@ -556,6 +616,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
final Value<String> username;
|
||||
final Value<String?> displayName;
|
||||
final Value<String?> nickName;
|
||||
final Value<String?> avatarSvg;
|
||||
final Value<int> myAvatarCounter;
|
||||
final Value<bool> accepted;
|
||||
final Value<bool> requested;
|
||||
final Value<bool> blocked;
|
||||
|
|
@ -572,6 +634,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
this.username = const Value.absent(),
|
||||
this.displayName = const Value.absent(),
|
||||
this.nickName = const Value.absent(),
|
||||
this.avatarSvg = const Value.absent(),
|
||||
this.myAvatarCounter = const Value.absent(),
|
||||
this.accepted = const Value.absent(),
|
||||
this.requested = const Value.absent(),
|
||||
this.blocked = const Value.absent(),
|
||||
|
|
@ -589,6 +653,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
required String username,
|
||||
this.displayName = const Value.absent(),
|
||||
this.nickName = const Value.absent(),
|
||||
this.avatarSvg = const Value.absent(),
|
||||
this.myAvatarCounter = const Value.absent(),
|
||||
this.accepted = const Value.absent(),
|
||||
this.requested = const Value.absent(),
|
||||
this.blocked = const Value.absent(),
|
||||
|
|
@ -606,6 +672,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
Expression<String>? username,
|
||||
Expression<String>? displayName,
|
||||
Expression<String>? nickName,
|
||||
Expression<String>? avatarSvg,
|
||||
Expression<int>? myAvatarCounter,
|
||||
Expression<bool>? accepted,
|
||||
Expression<bool>? requested,
|
||||
Expression<bool>? blocked,
|
||||
|
|
@ -623,6 +691,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
if (username != null) 'username': username,
|
||||
if (displayName != null) 'display_name': displayName,
|
||||
if (nickName != null) 'nick_name': nickName,
|
||||
if (avatarSvg != null) 'avatar_svg': avatarSvg,
|
||||
if (myAvatarCounter != null) 'my_avatar_counter': myAvatarCounter,
|
||||
if (accepted != null) 'accepted': accepted,
|
||||
if (requested != null) 'requested': requested,
|
||||
if (blocked != null) 'blocked': blocked,
|
||||
|
|
@ -645,6 +715,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
Value<String>? username,
|
||||
Value<String?>? displayName,
|
||||
Value<String?>? nickName,
|
||||
Value<String?>? avatarSvg,
|
||||
Value<int>? myAvatarCounter,
|
||||
Value<bool>? accepted,
|
||||
Value<bool>? requested,
|
||||
Value<bool>? blocked,
|
||||
|
|
@ -661,6 +733,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
username: username ?? this.username,
|
||||
displayName: displayName ?? this.displayName,
|
||||
nickName: nickName ?? this.nickName,
|
||||
avatarSvg: avatarSvg ?? this.avatarSvg,
|
||||
myAvatarCounter: myAvatarCounter ?? this.myAvatarCounter,
|
||||
accepted: accepted ?? this.accepted,
|
||||
requested: requested ?? this.requested,
|
||||
blocked: blocked ?? this.blocked,
|
||||
|
|
@ -691,6 +765,12 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
if (nickName.present) {
|
||||
map['nick_name'] = Variable<String>(nickName.value);
|
||||
}
|
||||
if (avatarSvg.present) {
|
||||
map['avatar_svg'] = Variable<String>(avatarSvg.value);
|
||||
}
|
||||
if (myAvatarCounter.present) {
|
||||
map['my_avatar_counter'] = Variable<int>(myAvatarCounter.value);
|
||||
}
|
||||
if (accepted.present) {
|
||||
map['accepted'] = Variable<bool>(accepted.value);
|
||||
}
|
||||
|
|
@ -737,6 +817,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
..write('username: $username, ')
|
||||
..write('displayName: $displayName, ')
|
||||
..write('nickName: $nickName, ')
|
||||
..write('avatarSvg: $avatarSvg, ')
|
||||
..write('myAvatarCounter: $myAvatarCounter, ')
|
||||
..write('accepted: $accepted, ')
|
||||
..write('requested: $requested, ')
|
||||
..write('blocked: $blocked, ')
|
||||
|
|
@ -805,8 +887,6 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("acknowledge_by_user" IN (0, 1))'),
|
||||
defaultValue: Constant(false));
|
||||
static const VerificationMeta _downloadStateMeta =
|
||||
const VerificationMeta('downloadState');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<DownloadState, int>
|
||||
downloadState = GeneratedColumn<int>('download_state', aliasedName, false,
|
||||
|
|
@ -824,7 +904,6 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("acknowledge_by_server" IN (0, 1))'),
|
||||
defaultValue: Constant(false));
|
||||
static const VerificationMeta _kindMeta = const VerificationMeta('kind');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<MessageKind, String> kind =
|
||||
GeneratedColumn<String>('kind', aliasedName, false,
|
||||
|
|
@ -918,14 +997,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
acknowledgeByUser.isAcceptableOrUnknown(
|
||||
data['acknowledge_by_user']!, _acknowledgeByUserMeta));
|
||||
}
|
||||
context.handle(_downloadStateMeta, const VerificationResult.success());
|
||||
if (data.containsKey('acknowledge_by_server')) {
|
||||
context.handle(
|
||||
_acknowledgeByServerMeta,
|
||||
acknowledgeByServer.isAcceptableOrUnknown(
|
||||
data['acknowledge_by_server']!, _acknowledgeByServerMeta));
|
||||
}
|
||||
context.handle(_kindMeta, const VerificationResult.success());
|
||||
if (data.containsKey('content_json')) {
|
||||
context.handle(
|
||||
_contentJsonMeta,
|
||||
|
|
@ -2448,6 +2525,8 @@ typedef $$ContactsTableCreateCompanionBuilder = ContactsCompanion Function({
|
|||
required String username,
|
||||
Value<String?> displayName,
|
||||
Value<String?> nickName,
|
||||
Value<String?> avatarSvg,
|
||||
Value<int> myAvatarCounter,
|
||||
Value<bool> accepted,
|
||||
Value<bool> requested,
|
||||
Value<bool> blocked,
|
||||
|
|
@ -2465,6 +2544,8 @@ typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({
|
|||
Value<String> username,
|
||||
Value<String?> displayName,
|
||||
Value<String?> nickName,
|
||||
Value<String?> avatarSvg,
|
||||
Value<int> myAvatarCounter,
|
||||
Value<bool> accepted,
|
||||
Value<bool> requested,
|
||||
Value<bool> blocked,
|
||||
|
|
@ -2519,6 +2600,13 @@ class $$ContactsTableFilterComposer
|
|||
ColumnFilters<String> get nickName => $composableBuilder(
|
||||
column: $table.nickName, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get avatarSvg => $composableBuilder(
|
||||
column: $table.avatarSvg, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get myAvatarCounter => $composableBuilder(
|
||||
column: $table.myAvatarCounter,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get accepted => $composableBuilder(
|
||||
column: $table.accepted, builder: (column) => ColumnFilters(column));
|
||||
|
||||
|
|
@ -2600,6 +2688,13 @@ class $$ContactsTableOrderingComposer
|
|||
ColumnOrderings<String> get nickName => $composableBuilder(
|
||||
column: $table.nickName, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get avatarSvg => $composableBuilder(
|
||||
column: $table.avatarSvg, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get myAvatarCounter => $composableBuilder(
|
||||
column: $table.myAvatarCounter,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get accepted => $composableBuilder(
|
||||
column: $table.accepted, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
|
|
@ -2661,6 +2756,12 @@ class $$ContactsTableAnnotationComposer
|
|||
GeneratedColumn<String> get nickName =>
|
||||
$composableBuilder(column: $table.nickName, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get avatarSvg =>
|
||||
$composableBuilder(column: $table.avatarSvg, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get myAvatarCounter => $composableBuilder(
|
||||
column: $table.myAvatarCounter, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get accepted =>
|
||||
$composableBuilder(column: $table.accepted, builder: (column) => column);
|
||||
|
||||
|
|
@ -2743,6 +2844,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
Value<String> username = const Value.absent(),
|
||||
Value<String?> displayName = const Value.absent(),
|
||||
Value<String?> nickName = const Value.absent(),
|
||||
Value<String?> avatarSvg = const Value.absent(),
|
||||
Value<int> myAvatarCounter = const Value.absent(),
|
||||
Value<bool> accepted = const Value.absent(),
|
||||
Value<bool> requested = const Value.absent(),
|
||||
Value<bool> blocked = const Value.absent(),
|
||||
|
|
@ -2760,6 +2863,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
username: username,
|
||||
displayName: displayName,
|
||||
nickName: nickName,
|
||||
avatarSvg: avatarSvg,
|
||||
myAvatarCounter: myAvatarCounter,
|
||||
accepted: accepted,
|
||||
requested: requested,
|
||||
blocked: blocked,
|
||||
|
|
@ -2777,6 +2882,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
required String username,
|
||||
Value<String?> displayName = const Value.absent(),
|
||||
Value<String?> nickName = const Value.absent(),
|
||||
Value<String?> avatarSvg = const Value.absent(),
|
||||
Value<int> myAvatarCounter = const Value.absent(),
|
||||
Value<bool> accepted = const Value.absent(),
|
||||
Value<bool> requested = const Value.absent(),
|
||||
Value<bool> blocked = const Value.absent(),
|
||||
|
|
@ -2794,6 +2901,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
username: username,
|
||||
displayName: displayName,
|
||||
nickName: nickName,
|
||||
avatarSvg: avatarSvg,
|
||||
myAvatarCounter: myAvatarCounter,
|
||||
accepted: accepted,
|
||||
requested: requested,
|
||||
blocked: blocked,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ enum MessageKind {
|
|||
textMessage,
|
||||
media,
|
||||
contactRequest,
|
||||
avatarChange,
|
||||
rejectRequest,
|
||||
acceptRequest,
|
||||
opened,
|
||||
|
|
@ -93,6 +94,8 @@ class MessageContent {
|
|||
return MediaMessageContent.fromJson(json);
|
||||
case MessageKind.textMessage:
|
||||
return TextMessageContent.fromJson(json);
|
||||
case MessageKind.avatarChange:
|
||||
return AvatarContent.fromJson(json);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -160,15 +163,25 @@ class TextMessageContent extends MessageContent {
|
|||
TextMessageContent({required this.text});
|
||||
|
||||
static TextMessageContent fromJson(Map json) {
|
||||
return TextMessageContent(
|
||||
text: json['text'],
|
||||
);
|
||||
return TextMessageContent(text: json['text']);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
return {
|
||||
'text': text,
|
||||
};
|
||||
return {'text': text};
|
||||
}
|
||||
}
|
||||
|
||||
class AvatarContent extends MessageContent {
|
||||
String svg;
|
||||
AvatarContent({required this.svg});
|
||||
|
||||
static AvatarContent fromJson(Map json) {
|
||||
return AvatarContent(svg: json['svg']);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
return {'svg': svg};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
part 'user_data.g.dart';
|
||||
part 'userdata.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class UserData {
|
||||
const UserData(
|
||||
{required this.userId,
|
||||
required this.username,
|
||||
required this.displayName});
|
||||
final String username;
|
||||
final String displayName;
|
||||
UserData({
|
||||
required this.userId,
|
||||
required this.username,
|
||||
required this.displayName,
|
||||
});
|
||||
|
||||
String username;
|
||||
String displayName;
|
||||
|
||||
String? avatarSvg;
|
||||
String? avatarJson;
|
||||
int? avatarCounter;
|
||||
|
||||
final int userId;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,16 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
|||
userId: (json['userId'] as num).toInt(),
|
||||
username: json['username'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
);
|
||||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt();
|
||||
|
||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||
'username': instance.username,
|
||||
'displayName': instance.displayName,
|
||||
'avatarSvg': instance.avatarSvg,
|
||||
'avatarJson': instance.avatarJson,
|
||||
'avatarCounter': instance.avatarCounter,
|
||||
'userId': instance.userId,
|
||||
};
|
||||
|
|
@ -6,11 +6,13 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/json_models/message.dart';
|
||||
import 'package:twonly/src/json_models/userdata.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||
import 'package:twonly/src/providers/hive.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
Future tryTransmitMessages() async {
|
||||
// List<Message> retransmit =
|
||||
|
|
@ -122,3 +124,27 @@ Future notifyContactAboutOpeningMessage(
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future notifyContactsAboutAvatarChange() async {
|
||||
List<Contact> contacts =
|
||||
await twonlyDatabase.contactsDao.getAllNotBlockedContacts();
|
||||
|
||||
UserData? user = await getUser();
|
||||
if (user == null) return;
|
||||
if (user.avatarCounter == null) return;
|
||||
if (user.avatarSvg == null) return;
|
||||
|
||||
for (Contact contact in contacts) {
|
||||
if (contact.myAvatarCounter < user.avatarCounter!) {
|
||||
encryptAndSendMessage(
|
||||
null,
|
||||
contact.userId,
|
||||
MessageJson(
|
||||
kind: MessageKind.avatarChange,
|
||||
content: AvatarContent(svg: user.avatarSvg!),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,6 +199,14 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
final update = ContactsCompanion(accepted: Value(true));
|
||||
twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
||||
localPushNotificationNewMessage(fromUserId.toInt(), message, 8888888);
|
||||
notifyContactsAboutAvatarChange();
|
||||
break;
|
||||
case MessageKind.avatarChange:
|
||||
var content = message.content;
|
||||
if (content is AvatarContent) {
|
||||
final update = ContactsCompanion(avatarSvg: Value(content.svg));
|
||||
twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
||||
}
|
||||
break;
|
||||
case MessageKind.ack:
|
||||
final update = MessagesCompanion(acknowledgeByUser: Value(true));
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class ApiProvider {
|
|||
if (!globalIsAppInBackground) {
|
||||
tryTransmitMessages();
|
||||
tryDownloadAllMediaFiles();
|
||||
notifyContactsAboutAvatarChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ Future<UserData?> getUser() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future updateUser(UserData userData) async {
|
||||
final storage = getSecureStorage();
|
||||
storage.write(key: "userData", value: jsonEncode(userData));
|
||||
}
|
||||
|
||||
Future<bool> deleteLocalUserData() async {
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
if (appDir.existsSync()) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
void selectCamera(int sCameraId, {bool init = false}) {
|
||||
if (sCameraId > gCameras.length) return;
|
||||
if (sCameraId >= gCameras.length) return;
|
||||
if (init) {
|
||||
for (; sCameraId < gCameras.length; sCameraId++) {
|
||||
if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
isZoomAble = false;
|
||||
});
|
||||
|
|
@ -115,12 +122,19 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
if (cameraId < gCameras.length) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (cameraId >= gCameras.length) {
|
||||
return Center(
|
||||
child: Text("No camera found."),
|
||||
);
|
||||
}
|
||||
return MediaViewSizing(
|
||||
Stack(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -303,8 +303,8 @@ class UserList extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
leading: InitialsAvatar(
|
||||
getContactDisplayName(user),
|
||||
leading: ContactAvatar(
|
||||
contact: user,
|
||||
fontSize: 15,
|
||||
),
|
||||
trailing: Checkbox(
|
||||
|
|
|
|||
|
|
@ -214,8 +214,8 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
|||
? Container()
|
||||
: Row(
|
||||
children: [
|
||||
InitialsAvatar(
|
||||
getContactDisplayName(user!),
|
||||
ContactAvatar(
|
||||
contact: user!,
|
||||
fontSize: 19,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ class _UserListItem extends State<UserListItem> {
|
|||
);
|
||||
},
|
||||
),
|
||||
leading: InitialsAvatar(getContactDisplayName(widget.user)),
|
||||
leading: ContactAvatar(contact: widget.user),
|
||||
onTap: () {
|
||||
if (currentMessage == null) {
|
||||
context
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
final displayName = getContactDisplayName(contact);
|
||||
return ListTile(
|
||||
title: Text(displayName),
|
||||
leading: InitialsAvatar(displayName),
|
||||
leading: ContactAvatar(contact: contact),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -44,10 +44,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: InitialsAvatar(
|
||||
getContactDisplayName(contact),
|
||||
fontSize: 30,
|
||||
),
|
||||
child: ContactAvatar(contact: contact, fontSize: 30),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ import 'dart:math';
|
|||
|
||||
import 'package:avatar_maker/avatar_maker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/json_models/userdata.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
class AvatarCreator extends StatefulWidget {
|
||||
const AvatarCreator({super.key});
|
||||
|
|
@ -28,7 +31,7 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
height: 25,
|
||||
),
|
||||
AvatarMakerAvatar(
|
||||
backgroundColor: Colors.grey[200],
|
||||
backgroundColor: Colors.transparent,
|
||||
radius: 100,
|
||||
),
|
||||
SizedBox(
|
||||
|
|
@ -39,13 +42,17 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
Spacer(flex: 2),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
height: 35,
|
||||
child: ElevatedButton.icon(
|
||||
icon: Icon(Icons.edit),
|
||||
label: Text("Customize"),
|
||||
onPressed: () => Navigator.push(context,
|
||||
new MaterialPageRoute(builder: (context) => NewPage())),
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NewPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -64,6 +71,21 @@ class _AvatarCreatorState extends State<AvatarCreator> {
|
|||
class NewPage extends StatelessWidget {
|
||||
const NewPage({super.key});
|
||||
|
||||
Future updateUserAvatar(String json, String svg) async {
|
||||
UserData? user = await getUser();
|
||||
if (user == null) return null;
|
||||
|
||||
user.avatarJson = json;
|
||||
user.avatarSvg = svg;
|
||||
if (user.avatarCounter == null) {
|
||||
user.avatarCounter = 1;
|
||||
} else {
|
||||
user.avatarCounter = user.avatarCounter! + 1;
|
||||
}
|
||||
await updateUser(user);
|
||||
await notifyContactsAboutAvatarChange();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
|
|
@ -75,9 +97,9 @@ class NewPage extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30),
|
||||
padding: const EdgeInsets.symmetric(vertical: 00),
|
||||
child: AvatarMakerAvatar(
|
||||
radius: 100,
|
||||
radius: 130,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
|
|
@ -85,12 +107,18 @@ class NewPage extends StatelessWidget {
|
|||
width: min(600, _width * 0.85),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Customize:",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Spacer(),
|
||||
AvatarMakerSaveWidget(),
|
||||
AvatarMakerSaveWidget(
|
||||
onTap: () async {
|
||||
final json =
|
||||
await AvatarMakerController.getJsonOptions();
|
||||
final svg = await AvatarMakerController.getAvatarSVG();
|
||||
await updateUserAvatar(json, svg);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
AvatarMakerRandomWidget(),
|
||||
AvatarMakerResetWidget(),
|
||||
],
|
||||
|
|
@ -106,12 +134,20 @@ class NewPage extends StatelessWidget {
|
|||
boxDecoration: BoxDecoration(
|
||||
boxShadow: [BoxShadow()],
|
||||
),
|
||||
// primaryBgColor:
|
||||
// Theme.of(context).colorScheme.surfaceContainerLowest,
|
||||
// secondaryBgColor:
|
||||
// const Color.fromARGB(255, 203, 203, 203),
|
||||
// labelTextStyle: TextStyle(
|
||||
// color: Theme.of(context).colorScheme.tertiary),
|
||||
unselectedTileDecoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 83, 83, 83),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
selectedTileDecoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 117, 117, 117),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
selectedIconColor: Colors.white,
|
||||
unselectedIconColor: Colors.grey,
|
||||
primaryBgColor: Colors.transparent,
|
||||
secondaryBgColor: Colors.transparent,
|
||||
labelTextStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -124,10 +124,7 @@ class UserList extends StatelessWidget {
|
|||
title: Row(children: [
|
||||
Text(getContactDisplayName(user)),
|
||||
]),
|
||||
leading: InitialsAvatar(
|
||||
getContactDisplayName(user),
|
||||
fontSize: 15,
|
||||
),
|
||||
leading: ContactAvatar(contact: user, fontSize: 15),
|
||||
trailing: Checkbox(
|
||||
value: user.blocked,
|
||||
onChanged: (bool? value) {
|
||||
|
|
|
|||
|
|
@ -47,14 +47,15 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
onTap: () async {
|
||||
await Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return AvatarCreator();
|
||||
}));
|
||||
initAsync();
|
||||
},
|
||||
child: InitialsAvatar(
|
||||
userData!.username,
|
||||
child: ContactAvatar(
|
||||
userData: userData!,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ dependencies:
|
|||
web_socket_channel: ^3.0.1
|
||||
camera: ^0.11.1
|
||||
avatar_maker: ^0.2.0
|
||||
flutter_svg: ^2.0.17
|
||||
# avatar_maker
|
||||
# avatar_maker:
|
||||
# path: ./dependencies/avatar_maker/
|
||||
|
|
|
|||
Loading…
Reference in a new issue