mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +00:00
add default avatar
This commit is contained in:
parent
cdb89a139f
commit
d3e7a63f38
8 changed files with 80 additions and 155 deletions
27
assets/images/default_avatar.svg
Normal file
27
assets/images/default_avatar.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!-- Taken from: https://getavataaars.com/ -->
|
||||||
|
<svg width="264px" height="280px" viewBox="0 0 264 280" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<path
|
||||||
|
d="M124,144.610951 L124,163 L128,163 L128,163 C167.764502,163 200,195.235498 200,235 L200,244 L0,244 L0,235 C-4.86974701e-15,195.235498 32.235498,163 72,163 L72,163 L76,163 L76,144.610951 C58.7626345,136.422372 46.3722246,119.687011 44.3051388,99.8812385 C38.4803105,99.0577866 34,94.0521096 34,88 L34,74 C34,68.0540074 38.3245733,63.1180731 44,62.1659169 L44,56 L44,56 C44,25.072054 69.072054,5.68137151e-15 100,0 L100,0 L100,0 C130.927946,-5.68137151e-15 156,25.072054 156,56 L156,62.1659169 C161.675427,63.1180731 166,68.0540074 166,74 L166,88 C166,94.0521096 161.51969,99.0577866 155.694861,99.8812385 C153.627775,119.687011 141.237365,136.422372 124,144.610951 Z"
|
||||||
|
id="react-path-3"></path>
|
||||||
|
</defs>
|
||||||
|
<g id="Avataaar" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g transform="translate(-825.000000, -1100.000000)">
|
||||||
|
<g transform="translate(825.000000, 1100.000000)">
|
||||||
|
<g id="Avataaar" stroke-width="1" fill-rule="evenodd">
|
||||||
|
<g id="Body" transform="translate(32.000000, 36.000000)">
|
||||||
|
<mask id="react-mask-6" fill="white">
|
||||||
|
<use xlink:href="#react-path-3"></use>
|
||||||
|
</mask>
|
||||||
|
<g id="Skin/👶🏽-03-Brown" mask="url(#react-mask-6)" fill="#57CC99">
|
||||||
|
<g transform="translate(0.000000, 0.000000)" id="Color">
|
||||||
|
<rect x="0" y="0" width="264" height="280"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -115,15 +115,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> newMessageExchange(int userId) {
|
|
||||||
return updateContact(
|
|
||||||
userId,
|
|
||||||
ContactsCompanion(
|
|
||||||
lastMessageExchange: Value(DateTime.now()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||||
return (select(contacts)
|
return (select(contacts)
|
||||||
..where((t) =>
|
..where((t) =>
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,9 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
Future<int?> insertMessage(MessagesCompanion message) async {
|
Future<int?> insertMessage(MessagesCompanion message) async {
|
||||||
try {
|
try {
|
||||||
await (update(contacts)
|
await (update(contacts)
|
||||||
..where((c) => c.userId.equals(message.contactId.value)))
|
..where(
|
||||||
|
(c) => c.userId.equals(message.contactId.value),
|
||||||
|
))
|
||||||
.write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
|
.write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
|
||||||
|
|
||||||
return await into(messages).insert(message);
|
return await into(messages).insert(message);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) {
|
||||||
color = context.color.primary;
|
color = context.color.primary;
|
||||||
} else {
|
} else {
|
||||||
if (content.isVideo) {
|
if (content.isVideo) {
|
||||||
color = const Color.fromARGB(255, 240, 243, 33);
|
color = const Color.fromARGB(255, 243, 33, 208);
|
||||||
} else {
|
} else {
|
||||||
color = Colors.redAccent;
|
color = Colors.redAccent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/media_download.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -21,14 +22,15 @@ import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart';
|
import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||||
|
import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
||||||
import 'package:twonly/src/views/chats/start_new_chat.view.dart';
|
import 'package:twonly/src/views/chats/start_new_chat.view.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/views/components/message_send_state_icon.dart';
|
|
||||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||||
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
||||||
|
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
||||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||||
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
||||||
|
|
@ -43,6 +45,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
late StreamSubscription<List<Contact>> _contactsSub;
|
late StreamSubscription<List<Contact>> _contactsSub;
|
||||||
List<Contact> _contacts = [];
|
List<Contact> _contacts = [];
|
||||||
List<Contact> _pinnedContacts = [];
|
List<Contact> _pinnedContacts = [];
|
||||||
|
UserData? _user;
|
||||||
|
|
||||||
GlobalKey firstUserListItemKey = GlobalKey();
|
GlobalKey firstUserListItemKey = GlobalKey();
|
||||||
GlobalKey searchForOtherUsers = GlobalKey();
|
GlobalKey searchForOtherUsers = GlobalKey();
|
||||||
|
|
@ -76,6 +79,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
_user = user;
|
||||||
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
||||||
final changeLogHash =
|
final changeLogHash =
|
||||||
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
|
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
|
||||||
|
|
@ -112,6 +116,23 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return const ProfileView();
|
||||||
|
}));
|
||||||
|
_user = await getUser();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: ContactAvatar(
|
||||||
|
userData: _user,
|
||||||
|
fontSize: 14,
|
||||||
|
color: context.color.onSurface.withAlpha(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text('twonly '),
|
const Text('twonly '),
|
||||||
if (planId != 'Free')
|
if (planId != 'Free')
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/views/components/message_send_state_icon.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -11,21 +10,20 @@ class ContactAvatar extends StatelessWidget {
|
||||||
this.contact,
|
this.contact,
|
||||||
this.userData,
|
this.userData,
|
||||||
this.fontSize = 20,
|
this.fontSize = 20,
|
||||||
|
this.color,
|
||||||
});
|
});
|
||||||
final Contact? contact;
|
final Contact? contact;
|
||||||
final UserData? userData;
|
final UserData? userData;
|
||||||
final double? fontSize;
|
final double? fontSize;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var displayName = '';
|
|
||||||
String? avatarSvg;
|
String? avatarSvg;
|
||||||
|
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
displayName = getContactDisplayName(contact!).replaceAll('\u0336', '');
|
|
||||||
avatarSvg = contact!.avatarSvg;
|
avatarSvg = contact!.avatarSvg;
|
||||||
} else if (userData != null) {
|
} else if (userData != null) {
|
||||||
displayName = userData!.displayName;
|
|
||||||
avatarSvg = userData!.avatarSvg;
|
avatarSvg = userData!.avatarSvg;
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
|
|
@ -33,148 +31,34 @@ class ContactAvatar extends StatelessWidget {
|
||||||
|
|
||||||
final proSize = (fontSize == null) ? 40 : (fontSize! * 2);
|
final proSize = (fontSize == null) ? 40 : (fontSize! * 2);
|
||||||
|
|
||||||
if (avatarSvg != null) {
|
return Container(
|
||||||
return Container(
|
constraints: BoxConstraints(
|
||||||
constraints: BoxConstraints(
|
minHeight: 2 * (fontSize ?? 20),
|
||||||
minHeight: 2 * (fontSize ?? 20),
|
minWidth: 2 * (fontSize ?? 20),
|
||||||
minWidth: 2 * (fontSize ?? 20),
|
maxWidth: 2 * (fontSize ?? 20),
|
||||||
maxWidth: 2 * (fontSize ?? 20),
|
maxHeight: 2 * (fontSize ?? 20),
|
||||||
maxHeight: 2 * (fontSize ?? 20),
|
),
|
||||||
),
|
child: Center(
|
||||||
child: Center(
|
child: ClipRRect(
|
||||||
child: ClipRRect(
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderRadius: BorderRadius.circular(12),
|
child: Container(
|
||||||
child: SizedBox(
|
height: proSize as double,
|
||||||
height: proSize as double,
|
width: proSize,
|
||||||
width: proSize,
|
color: color,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SvgPicture.string(
|
child: avatarSvg == null
|
||||||
avatarSvg,
|
? SvgPicture.asset('assets/images/default_avatar.svg')
|
||||||
errorBuilder: (context, error, stackTrace) {
|
: SvgPicture.string(
|
||||||
Log.error('$error');
|
avatarSvg,
|
||||||
return Container();
|
errorBuilder: (context, error, stackTrace) {
|
||||||
},
|
Log.error('$error');
|
||||||
),
|
return Container();
|
||||||
),
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract initials from the displayName
|
|
||||||
final nameParts = displayName.split(' ');
|
|
||||||
var initials = nameParts.map((part) => part[0]).join().toUpperCase();
|
|
||||||
|
|
||||||
if (initials.length > 2) {
|
|
||||||
initials = initials[0] + initials[1];
|
|
||||||
} else if (initials.length == 1) {
|
|
||||||
initials = displayName[0] + displayName[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
initials = initials.toUpperCase();
|
|
||||||
|
|
||||||
// Generate a color based on the initials (you can customize this logic)
|
|
||||||
final avatarColor = _getColorFromUsername(
|
|
||||||
displayName, Theme.of(context).brightness == Brightness.dark);
|
|
||||||
|
|
||||||
final Widget child = Text(
|
|
||||||
initials,
|
|
||||||
style: TextStyle(
|
|
||||||
color: _getTextColor(avatarColor),
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: fontSize,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final isPro = initials[0] == 'T';
|
|
||||||
|
|
||||||
if (isPro) {
|
|
||||||
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),
|
|
||||||
child: Container(
|
|
||||||
height: proSize as double,
|
|
||||||
width: proSize,
|
|
||||||
//padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
|
||||||
color: avatarColor,
|
|
||||||
child: Center(child: child),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return CircleAvatar(
|
|
||||||
backgroundColor: avatarColor,
|
|
||||||
radius: fontSize,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getTextColor(Color color) {
|
|
||||||
const value = 100.0;
|
|
||||||
// Ensure the value does not exceed the RGB limits
|
|
||||||
final newRed = ((color.r * 255) - value).clamp(0, 255).round();
|
|
||||||
final newGreen = (color.g * 255 - value).clamp(0, 255).round();
|
|
||||||
final newBlue = (color.b * 255 - value).clamp(0, 255).round();
|
|
||||||
|
|
||||||
return Color.fromARGB((color.a * 255).round(), newRed, newGreen, newBlue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getColorFromUsername(String displayName, bool isDarkMode) {
|
|
||||||
// Define color lists for light and dark themes
|
|
||||||
final lightColors = <Color>[
|
|
||||||
Colors.red,
|
|
||||||
Colors.green,
|
|
||||||
Colors.blue,
|
|
||||||
Colors.orange,
|
|
||||||
Colors.purple,
|
|
||||||
Colors.teal,
|
|
||||||
Colors.amber,
|
|
||||||
Colors.indigo,
|
|
||||||
Colors.cyan,
|
|
||||||
Colors.lime,
|
|
||||||
Colors.pink,
|
|
||||||
Colors.brown,
|
|
||||||
Colors.grey,
|
|
||||||
];
|
|
||||||
|
|
||||||
final darkColors = <Color>[
|
|
||||||
const Color.fromARGB(255, 246, 227, 254), // Light Lavender
|
|
||||||
const Color.fromARGB(255, 246, 216, 215), // Light Pink
|
|
||||||
const Color.fromARGB(255, 226, 236, 235), // Light Teal
|
|
||||||
const Color.fromARGB(255, 255, 224, 178), // Light Yellow
|
|
||||||
const Color.fromARGB(255, 255, 182, 193), // Light Pink (Hot Pink)
|
|
||||||
const Color.fromARGB(255, 173, 216, 230), // Light Blue
|
|
||||||
const Color.fromARGB(255, 221, 160, 221), // Plum
|
|
||||||
const Color.fromARGB(255, 255, 228, 196), // Bisque
|
|
||||||
const Color.fromARGB(255, 240, 230, 140), // Khaki
|
|
||||||
const Color.fromARGB(255, 255, 192, 203), // Pink
|
|
||||||
const Color.fromARGB(255, 255, 218, 185), // Peach Puff
|
|
||||||
const Color.fromARGB(255, 255, 160, 122), // Light Salmon
|
|
||||||
const Color.fromARGB(255, 135, 206, 250), // Light Sky Blue
|
|
||||||
const Color.fromARGB(255, 255, 228, 225), // Misty Rose
|
|
||||||
const Color.fromARGB(255, 240, 248, 255), // Alice Blue
|
|
||||||
const Color.fromARGB(255, 255, 250, 205), // Lemon Chiffon
|
|
||||||
const Color.fromARGB(255, 255, 218, 185), // Peach Puff
|
|
||||||
];
|
|
||||||
|
|
||||||
// Simple logic to generate a hash from initials
|
|
||||||
final hash =
|
|
||||||
displayName.codeUnits.fold(0, (prev, element) => prev + element);
|
|
||||||
|
|
||||||
// Select the appropriate color list based on the current theme brightness
|
|
||||||
final colors = isDarkMode ? darkColors : lightColors;
|
|
||||||
|
|
||||||
// Use the hash to select a color from the list
|
|
||||||
return colors[hash % colors.length];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue