mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
start with share image view
This commit is contained in:
parent
6502859cc1
commit
50efa6530f
7 changed files with 289 additions and 99 deletions
|
|
@ -46,7 +46,7 @@
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Why? What is the purpose of the app?</string>
|
<string>To create photos that can be shared.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Explanation on why the microphone access is needed.</string>
|
<string>Explanation on why the microphone access is needed.</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
||||||
96
lib/src/components/initialsavatar_component.dart
Normal file
96
lib/src/components/initialsavatar_component.dart
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InitialsAvatar extends StatelessWidget {
|
||||||
|
final String displayName;
|
||||||
|
|
||||||
|
const InitialsAvatar({super.key, required this.displayName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Extract initials from the displayName
|
||||||
|
List<String> nameParts = displayName.split(' ');
|
||||||
|
String 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)
|
||||||
|
Color avatarColor = _getColorFromUsername(
|
||||||
|
displayName, Theme.of(context).brightness == Brightness.dark);
|
||||||
|
|
||||||
|
return CircleAvatar(
|
||||||
|
backgroundColor: avatarColor,
|
||||||
|
child: Text(
|
||||||
|
initials,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _getTextColor(avatarColor),
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getTextColor(Color color) {
|
||||||
|
double value = 100.0;
|
||||||
|
// Ensure the value does not exceed the RGB limits
|
||||||
|
int newRed = ((color.r * 255) - value).clamp(0, 255).round();
|
||||||
|
int newGreen = (color.g * 255 - value).clamp(0, 255).round();
|
||||||
|
int 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
|
||||||
|
List<Color> lightColors = [
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Color> darkColors = [
|
||||||
|
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
|
||||||
|
int hash = displayName.codeUnits.fold(0, (prev, element) => prev + element);
|
||||||
|
|
||||||
|
// Select the appropriate color list based on the current theme brightness
|
||||||
|
List<Color> colors = isDarkMode ? darkColors : lightColors;
|
||||||
|
|
||||||
|
// Use the hash to select a color from the list
|
||||||
|
return colors[hash % colors.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
|
@ -114,88 +113,3 @@ Future<Result> createNewUser(String username, String inviteCode) async {
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget createInitialsAvatar(String username, bool isDarkMode) {
|
|
||||||
// Extract initials from the username
|
|
||||||
List<String> nameParts = username.split(' ');
|
|
||||||
String initials = nameParts.map((part) => part[0]).join().toUpperCase();
|
|
||||||
if (initials.length > 2) {
|
|
||||||
initials = initials[0] + initials[1];
|
|
||||||
} else if (initials.length == 1) {
|
|
||||||
initials = username[0] + username[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
initials = initials.toUpperCase();
|
|
||||||
|
|
||||||
// Generate a color based on the initials (you can customize this logic)
|
|
||||||
Color avatarColor = _getColorFromUsername(username, isDarkMode);
|
|
||||||
|
|
||||||
return CircleAvatar(
|
|
||||||
backgroundColor: avatarColor,
|
|
||||||
child: Text(
|
|
||||||
initials,
|
|
||||||
style: TextStyle(
|
|
||||||
color: _getTextColor(avatarColor),
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: 20),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getTextColor(Color color) {
|
|
||||||
double value = 100.0;
|
|
||||||
// Ensure the value does not exceed the RGB limits
|
|
||||||
int newRed = ((color.r * 255) - value).clamp(0, 255).round();
|
|
||||||
int newGreen = (color.g * 255 - value).clamp(0, 255).round();
|
|
||||||
int newBlue = (color.b * 255 - value).clamp(0, 255).round();
|
|
||||||
|
|
||||||
return Color.fromARGB((color.a * 255).round(), newRed, newGreen, newBlue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getColorFromUsername(String username, bool isDarkMode) {
|
|
||||||
// Define color lists for light and dark themes
|
|
||||||
List<Color> lightColors = [
|
|
||||||
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,
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Color> darkColors = [
|
|
||||||
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
|
|
||||||
int hash = username.codeUnits.fold(0, (prev, element) => prev + element);
|
|
||||||
|
|
||||||
// Select the appropriate color list based on the current theme brightness
|
|
||||||
List<Color> colors = isDarkMode ? darkColors : lightColors;
|
|
||||||
|
|
||||||
// Use the hash to select a color from the list
|
|
||||||
return colors[hash % colors.length];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
// import 'package:camera/camera.dart';
|
import 'dart:io';
|
||||||
// import 'camera_editor_view.dart';
|
|
||||||
// import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:camerawesome/pigeon.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/src/views/share_image_view.dart';
|
||||||
|
|
||||||
class CameraPreviewView extends StatefulWidget {
|
class CameraPreviewView extends StatefulWidget {
|
||||||
const CameraPreviewView({super.key});
|
const CameraPreviewView({super.key});
|
||||||
|
|
@ -30,6 +29,50 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
child: CameraAwesomeBuilder.custom(
|
child: CameraAwesomeBuilder.custom(
|
||||||
progressIndicator: Container(),
|
progressIndicator: Container(),
|
||||||
|
onMediaCaptureEvent: (event) {
|
||||||
|
switch ((event.status, event.isPicture, event.isVideo)) {
|
||||||
|
case (MediaCaptureStatus.capturing, true, false):
|
||||||
|
debugPrint('Capturing picture...');
|
||||||
|
case (MediaCaptureStatus.success, true, false):
|
||||||
|
event.captureRequest.when(
|
||||||
|
single: (single) {
|
||||||
|
final path = single.file?.path;
|
||||||
|
if (path == null) return;
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ShareImageView(image: path),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
debugPrint('Picture saved: ${path}');
|
||||||
|
},
|
||||||
|
multiple: (multiple) {
|
||||||
|
multiple.fileBySensor.forEach((key, value) {
|
||||||
|
debugPrint('multiple image taken: $key ${value?.path}');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case (MediaCaptureStatus.failure, true, false):
|
||||||
|
debugPrint('Failed to capture picture: ${event.exception}');
|
||||||
|
case (MediaCaptureStatus.capturing, false, true):
|
||||||
|
debugPrint('Capturing video...');
|
||||||
|
case (MediaCaptureStatus.success, false, true):
|
||||||
|
event.captureRequest.when(
|
||||||
|
single: (single) {
|
||||||
|
debugPrint('Video saved: ${single.file?.path}');
|
||||||
|
},
|
||||||
|
multiple: (multiple) {
|
||||||
|
multiple.fileBySensor.forEach((key, value) {
|
||||||
|
debugPrint('multiple video taken: $key ${value?.path}');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case (MediaCaptureStatus.failure, false, true):
|
||||||
|
debugPrint('Failed to capture video: ${event.exception}');
|
||||||
|
default:
|
||||||
|
debugPrint('Unknown event: $event');
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (cameraState, preview) {
|
builder: (cameraState, preview) {
|
||||||
return Container(
|
return Container(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
|
@ -99,7 +142,25 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
saveConfig: SaveConfig.photoAndVideo(),
|
saveConfig: SaveConfig.photoAndVideo(
|
||||||
|
photoPathBuilder: (sensors) async {
|
||||||
|
final Directory extDir = await getTemporaryDirectory();
|
||||||
|
final testDir = await Directory(
|
||||||
|
'${extDir.path}/images',
|
||||||
|
).create(recursive: true);
|
||||||
|
final String filePath =
|
||||||
|
'${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
return SingleCaptureRequest(filePath, sensors.first);
|
||||||
|
// // Separate pictures taken with front and back camera
|
||||||
|
// return MultipleCaptureRequest(
|
||||||
|
// {
|
||||||
|
// for (final sensor in sensors)
|
||||||
|
// sensor:
|
||||||
|
// '${testDir.path}/${sensor.position == SensorPosition.front ? 'front_' : "back_"}${DateTime.now().millisecondsSinceEpoch}.jpg',
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
previewPadding: const EdgeInsets.all(10),
|
previewPadding: const EdgeInsets.all(10),
|
||||||
// onPreviewTapBuilder: (state) => OnPreviewTap(
|
// onPreviewTapBuilder: (state) => OnPreviewTap(
|
||||||
// onTap: (Offset position, PreviewSize flutterPreviewSize,
|
// onTap: (Offset position, PreviewSize flutterPreviewSize,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:twonly/src/utils.dart';
|
import 'package:twonly/src/components/initialsavatar_component.dart';
|
||||||
import 'package:twonly/src/views/search_username_view.dart';
|
import 'package:twonly/src/views/search_username_view.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'new_message_view.dart';
|
import 'new_message_view.dart';
|
||||||
|
|
@ -193,8 +193,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(item.username),
|
title: Text(item.username),
|
||||||
subtitle: getSubtitle(item),
|
subtitle: getSubtitle(item),
|
||||||
leading: createInitialsAvatar(item.username,
|
leading: InitialsAvatar(displayName: item.username),
|
||||||
Theme.of(context).brightness == Brightness.dark),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:twonly/src/components/initialsavatar_component.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
import 'package:twonly/src/utils.dart';
|
|
||||||
import 'package:twonly/src/views/search_username_view.dart';
|
import 'package:twonly/src/views/search_username_view.dart';
|
||||||
|
|
||||||
class NewMessageView extends StatefulWidget {
|
class NewMessageView extends StatefulWidget {
|
||||||
|
|
@ -166,8 +166,7 @@ class UserList extends StatelessWidget {
|
||||||
...users.map((username) {
|
...users.map((username) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(username),
|
title: Text(username),
|
||||||
leading: createInitialsAvatar(
|
leading: InitialsAvatar(displayName: username),
|
||||||
username, Theme.of(context).brightness == Brightness.dark),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Handle tap
|
// Handle tap
|
||||||
},
|
},
|
||||||
|
|
|
||||||
121
lib/src/views/share_image_view.dart
Normal file
121
lib/src/views/share_image_view.dart
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:twonly/src/components/initialsavatar_component.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
|
||||||
|
class ShareImageView extends StatefulWidget {
|
||||||
|
const ShareImageView({super.key, required this.image});
|
||||||
|
final String image;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShareImageView> createState() => _ShareImageView();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShareImageView extends State<ShareImageView> {
|
||||||
|
List<Contact> _knownUsers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadUsers() async {
|
||||||
|
final users = await DbContacts.getUsers();
|
||||||
|
setState(() {
|
||||||
|
_knownUsers = users;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.newMessageTitle),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
UserCheckboxList(users: _knownUsers),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// Expanded(
|
||||||
|
// child: UserList(_filteredUsers),
|
||||||
|
// )
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserCheckboxList extends StatelessWidget {
|
||||||
|
final List<Contact> users;
|
||||||
|
|
||||||
|
UserCheckboxList({required this.users});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Limit the number of users to 8
|
||||||
|
final limitedUsers = users.length > 8 ? users.sublist(0, 8) : users;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: List.generate((limitedUsers.length + 1) ~/ 2, (rowIndex) {
|
||||||
|
final firstUserIndex = rowIndex * 2;
|
||||||
|
final secondUserIndex = firstUserIndex + 1;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Text(limitedUsers[firstUserIndex].displayName),
|
||||||
|
if (secondUserIndex < limitedUsers.length)
|
||||||
|
Text(limitedUsers[secondUserIndex].displayName),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserCheckbox extends StatefulWidget {
|
||||||
|
final Contact user;
|
||||||
|
|
||||||
|
UserCheckbox({required this.user});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_UserCheckboxState createState() => _UserCheckboxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserCheckboxState extends State<UserCheckbox> {
|
||||||
|
bool isChecked = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: isChecked,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
setState(() {
|
||||||
|
isChecked = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CircleAvatar(
|
||||||
|
child: InitialsAvatar(
|
||||||
|
displayName:
|
||||||
|
widget.user.displayName), // Display first letter of the name
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.user.displayName.length > 10
|
||||||
|
? '${widget.user.displayName.substring(0, 10)}...' // Trim if too long
|
||||||
|
: widget.user.displayName,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue