start with share image view

This commit is contained in:
otsmr 2025-01-24 01:06:47 +01:00
parent 6502859cc1
commit 50efa6530f
7 changed files with 289 additions and 99 deletions

View file

@ -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>

View 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];
}
}

View file

@ -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];
}

View file

@ -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,

View file

@ -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,

View file

@ -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
}, },

View 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,
),
),
],
);
}
}