diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index f03ccf5..4642b9b 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -46,7 +46,7 @@
UIApplicationSupportsIndirectInputEvents
NSCameraUsageDescription
- Why? What is the purpose of the app?
+ To create photos that can be shared.
NSMicrophoneUsageDescription
Explanation on why the microphone access is needed.
diff --git a/lib/src/components/initialsavatar_component.dart b/lib/src/components/initialsavatar_component.dart
new file mode 100644
index 0000000..ef86654
--- /dev/null
+++ b/lib/src/components/initialsavatar_component.dart
@@ -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 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 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 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 colors = isDarkMode ? darkColors : lightColors;
+
+ // Use the hash to select a color from the list
+ return colors[hash % colors.length];
+ }
+}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 95a66bb..2b1b1cd 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -1,7 +1,6 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
-import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:twonly/main.dart';
import 'package:twonly/src/model/contacts_model.dart';
@@ -114,88 +113,3 @@ Future createNewUser(String username, String inviteCode) async {
return res;
}
-
-Widget createInitialsAvatar(String username, bool isDarkMode) {
- // Extract initials from the username
- List 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 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 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 colors = isDarkMode ? darkColors : lightColors;
-
- // Use the hash to select a color from the list
- return colors[hash % colors.length];
-}
diff --git a/lib/src/views/camera_preview_view.dart b/lib/src/views/camera_preview_view.dart
index dbcdb79..92cbd45 100644
--- a/lib/src/views/camera_preview_view.dart
+++ b/lib/src/views/camera_preview_view.dart
@@ -1,10 +1,9 @@
-// import 'package:camera/camera.dart';
-// import 'camera_editor_view.dart';
-// import 'package:flutter/gestures.dart';
-import 'package:camerawesome/pigeon.dart';
+import 'dart:io';
+
import 'package:flutter/material.dart';
-import 'dart:math';
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 {
const CameraPreviewView({super.key});
@@ -30,6 +29,50 @@ class _CameraPreviewViewState extends State {
borderRadius: BorderRadius.circular(22),
child: CameraAwesomeBuilder.custom(
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) {
return Container(
child: Stack(
@@ -99,7 +142,25 @@ class _CameraPreviewViewState extends State {
),
);
},
- 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),
// onPreviewTapBuilder: (state) => OnPreviewTap(
// onTap: (Offset position, PreviewSize flutterPreviewSize,
diff --git a/lib/src/views/chat_list_view.dart b/lib/src/views/chat_list_view.dart
index 8cde652..1a41078 100644
--- a/lib/src/views/chat_list_view.dart
+++ b/lib/src/views/chat_list_view.dart
@@ -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:flutter_gen/gen_l10n/app_localizations.dart';
import 'new_message_view.dart';
@@ -193,8 +193,7 @@ class _ChatListViewState extends State {
return ListTile(
title: Text(item.username),
subtitle: getSubtitle(item),
- leading: createInitialsAvatar(item.username,
- Theme.of(context).brightness == Brightness.dark),
+ leading: InitialsAvatar(displayName: item.username),
onTap: () {
Navigator.push(
context,
diff --git a/lib/src/views/new_message_view.dart b/lib/src/views/new_message_view.dart
index 3f3292f..063d2eb 100644
--- a/lib/src/views/new_message_view.dart
+++ b/lib/src/views/new_message_view.dart
@@ -1,7 +1,7 @@
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';
-import 'package:twonly/src/utils.dart';
import 'package:twonly/src/views/search_username_view.dart';
class NewMessageView extends StatefulWidget {
@@ -166,8 +166,7 @@ class UserList extends StatelessWidget {
...users.map((username) {
return ListTile(
title: Text(username),
- leading: createInitialsAvatar(
- username, Theme.of(context).brightness == Brightness.dark),
+ leading: InitialsAvatar(displayName: username),
onTap: () {
// Handle tap
},
diff --git a/lib/src/views/share_image_view.dart b/lib/src/views/share_image_view.dart
new file mode 100644
index 0000000..b840ec6
--- /dev/null
+++ b/lib/src/views/share_image_view.dart
@@ -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 createState() => _ShareImageView();
+}
+
+class _ShareImageView extends State {
+ List _knownUsers = [];
+
+ @override
+ void initState() {
+ super.initState();
+ _loadUsers();
+ }
+
+ Future _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 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 {
+ 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,
+ ),
+ ),
+ ],
+ );
+ }
+}