improve image editor

This commit is contained in:
otsmr 2026-01-19 23:15:28 +01:00
parent cd5deca6b6
commit 2ef4566d69
44 changed files with 465 additions and 1040 deletions

View file

@ -5,6 +5,8 @@
- Adds option to manual focus in the camera - Adds option to manual focus in the camera
- Adds support to switch between front and back camera during video recording - Adds support to switch between front and back camera during video recording
- Adds basic face filters - Adds basic face filters
- Improves image editor like emojies or text under a drawing can be moved
- Fixes issue with emojis disappearing in the image editor
## 0.0.86 ## 0.0.86

View file

@ -22,7 +22,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
String emoji, String emoji,
bool remove, bool remove,
) async { ) async {
if (!isEmoji(emoji)) { if (!isOneEmoji(emoji)) {
Log.error('Did not update reaction as it is not an emoji!'); Log.error('Did not update reaction as it is not an emoji!');
return; return;
} }
@ -59,7 +59,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
String emoji, String emoji,
bool remove, bool remove,
) async { ) async {
if (!isEmoji(emoji)) { if (!isOneEmoji(emoji)) {
Log.error('Did not update reaction as it is not an emoji!'); Log.error('Did not update reaction as it is not an emoji!');
return; return;
} }

View file

@ -13,7 +13,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/camera/share_image_editor.view.dart';
import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart';
import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/contact/contact.view.dart'; import 'package:twonly/src/views/contact/contact.view.dart';

View file

@ -23,32 +23,54 @@ class MainCameraPreview extends StatelessWidget {
requiredHeight: 0, requiredHeight: 0,
additionalPadding: 59, additionalPadding: 59,
bottomNavigation: Container(), bottomNavigation: Container(),
child: Screenshot( child: Stack(
controller: mainCameraController.screenshotController, children: [
child: AspectRatio( Screenshot(
aspectRatio: 9 / 16, controller: mainCameraController.screenshotController,
child: ClipRect( child: AspectRatio(
child: FittedBox( aspectRatio: 9 / 16,
fit: BoxFit.cover, child: ClipRect(
child: SizedBox( child: FittedBox(
width: mainCameraController fit: BoxFit.cover,
.cameraController!.value.previewSize!.height, child: SizedBox(
height: mainCameraController width: mainCameraController
.cameraController!.value.previewSize!.width, .cameraController!.value.previewSize!.height,
child: CameraPreview( height: mainCameraController
key: mainCameraController.cameraPreviewKey, .cameraController!.value.previewSize!.width,
mainCameraController.cameraController!, child: CameraPreview(
child: Stack( key: mainCameraController.cameraPreviewKey,
children: [ mainCameraController.cameraController!,
if (mainCameraController.customPaint != null) child: Stack(
Positioned.fill( children: [
child: mainCameraController.customPaint!, if (mainCameraController.customPaint != null)
), Positioned.fill(
if (mainCameraController.facePaint != null) child: mainCameraController.customPaint!,
Positioned.fill( ),
child: mainCameraController.facePaint!, if (mainCameraController.facePaint != null)
), Positioned.fill(
if (mainCameraController.focusPointOffset != null) child: mainCameraController.facePaint!,
),
],
),
),
),
),
),
),
),
if (mainCameraController.focusPointOffset != null)
AspectRatio(
aspectRatio: 9 / 16,
child: ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: mainCameraController
.cameraController!.value.previewSize!.height,
height: mainCameraController
.cameraController!.value.previewSize!.width,
child: Stack(
children: [
Positioned( Positioned(
top: mainCameraController.focusPointOffset!.dy - 40, top: mainCameraController.focusPointOffset!.dy - 40,
left: left:
@ -65,14 +87,14 @@ class MainCameraPreview extends StatelessWidget {
), ),
), ),
), ),
) ),
], ],
),
), ),
), ),
), ),
), ),
), ],
),
), ),
), ),
); );

View file

@ -27,8 +27,8 @@ import 'package:twonly/src/views/camera/camera_preview_components/permissions_vi
import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart'; import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart';
import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart'; import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart';
import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart'; import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/camera/share_image_editor.view.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/loader.dart'; import 'package:twonly/src/views/components/loader.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/painters/face_filters/beard_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart';
import 'package:twonly/src/views/camera/painters/face_filters/dog_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart';
enum FaceFilterType { enum FaceFilterType {
none, none,

View file

@ -19,10 +19,10 @@ import 'package:twonly/src/utils/qr.dart';
import 'package:twonly/src/utils/screenshot.dart'; import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart';
import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart';
import 'package:twonly/src/views/camera/painters/face_filters/beard_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart';
import 'package:twonly/src/views/camera/painters/face_filters/dog_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart';
import 'package:twonly/src/views/camera/painters/face_filters/face_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart';
class ScannedVerifiedContact { class ScannedVerifiedContact {
ScannedVerifiedContact({ ScannedVerifiedContact({

View file

@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/painters/coordinates_translator.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart';
import 'package:twonly/src/views/camera/painters/face_filters/face_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart';
class BeardFilterPainter extends FaceFilterPainter { class BeardFilterPainter extends FaceFilterPainter {
BeardFilterPainter( BeardFilterPainter(

View file

@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/painters/coordinates_translator.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart';
import 'package:twonly/src/views/camera/painters/face_filters/face_filter_painter.dart'; import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart';
class DogFilterPainter extends FaceFilterPainter { class DogFilterPainter extends FaceFilterPainter {
DogFilterPainter( DogFilterPainter(

View file

@ -1,710 +0,0 @@
Map<String, String> emojiWeights = {};
List<String> emojis = [
'😀',
'😁',
'😂',
'🤣',
'😃',
'😄',
'😅',
'😆',
'😉',
'😊',
'😋',
'😎',
'😍',
'😘',
'🥰',
'😗',
'😙',
'😚',
'🙂️',
'🤗',
'🤩',
'🤔',
'🤔',
'🤨',
'😐',
'😑',
'😶',
'🙄',
'😏',
'😣',
'😥',
'😮',
'🤐',
'😯',
'😪',
'😫',
'😴',
'😌',
'😛',
'😜',
'😝',
'🤤',
'😒',
'😓',
'😔',
'😕',
'🙃',
'🤑',
'😲',
'🙁',
'😖',
'😞',
'😟',
'😤',
'😢',
'😭',
'😦',
'😧',
'😨',
'😩',
'🤯',
'😬',
'😰',
'😱',
'🥵',
'🥶',
'😳',
'🤪',
'😵',
'😡',
'😠',
'🤬',
'😷',
'🤒',
'🤕',
'🤢',
'🤮',
'🤧',
'😇',
'🤠',
'🤡',
'🥳',
'🥴',
'🤥',
'🤫',
'🤭',
'🤭',
'🧐',
'🤓',
'😈',
'👿',
'👹',
'👺',
'💀',
'👻',
'👽',
'🤖',
'💩',
'😺',
'😸',
'😹',
'😻',
'😼',
'😽',
'🙀',
'😿',
'😾',
'😾',
/// People and Fantasy
'👶',
'👧',
'🧒',
'👩',
'🧑',
'👨',
'👵',
'👴',
'👲',
'👳‍♀️‍️',
'👳‍♂️️‍️',
'🧕️️‍️',
'🧔‍',
'👱‍♂️️‍',
'👱‍♀️️️‍',
'👨‍🦰️️️‍',
'👩‍🦰‍',
'👨‍🦱‍‍',
'👨‍🦲‍‍',
'👩‍🦲‍‍',
'👨‍🦳‍‍',
'👩‍🦳‍‍',
'🦸‍♀️‍‍',
'🦸‍♂️️‍‍',
'🦹‍♀️️️‍‍',
'🦹‍♂️️️️‍‍',
'👮‍♀️‍‍',
'👮‍♂️️‍‍',
'👷‍♀️️️‍‍',
'👷‍♂️️️️‍‍',
'💂‍♀️️️️️‍‍',
'💂‍♂️️️️️️‍‍',
'🕵️‍♀️️️️️️️‍‍',
'🕵️‍♂️️️️️️️️‍‍',
'👩‍⚕️️️️️️️️️‍‍',
'👨‍⚕️️️️️️️️️️‍‍',
'👩‍🌾️️️️️️️️️️‍‍',
'👨‍🌾‍‍',
'👩‍🍳‍‍',
'👨‍🍳‍‍',
'👩‍🎓‍‍',
'👨‍🎓‍‍',
'👩‍🎤‍‍',
'👨‍🎤‍‍',
'👩‍🏫‍‍',
'👨‍🏫‍‍',
'👩‍🏭‍‍',
'👨‍🏭‍‍',
'👩‍💻‍‍',
'👨‍💻‍‍',
'👩‍💼‍‍',
'👨‍💼‍‍',
'👩‍🔧‍‍',
'👨‍🔧‍‍',
'👩‍🔬‍‍',
'👨‍🔬‍‍',
'👩‍🎨‍‍',
'👨‍🎨‍‍',
'👩‍🚒‍‍',
'👨‍🚒‍‍',
'👩‍✈️‍‍',
'👨‍✈️️‍‍',
'👩‍🚀‍‍',
'👨‍🚀‍‍',
'👩‍⚖️‍‍',
'👨‍⚖️️‍‍',
'👰‍‍',
'🤵‍‍',
'👸‍‍',
'🤴‍‍',
'🤶‍‍',
'🎅‍‍',
'🧙‍♀️‍‍',
'🧙‍♂️️‍‍',
'🧝‍♀️️️‍‍',
'🧝‍♂️‍‍',
'🧛‍♀️️‍‍',
'🧛‍♂️️️‍‍',
'🧟‍♀️️️️‍‍',
'🧟‍♂️️️️️‍‍',
'🧞‍♀️️️️️️‍‍',
'🧞‍♂️️️️️️️‍‍',
'🧜‍♀️️️️️️️️‍‍',
'🧜‍♂️️️️️️️️️‍‍',
'🧚‍♀️️️️️️️️️️‍‍',
'🧚‍♂️️️️️️️️️️️‍‍',
'👼️️️️️️️️️️️‍‍',
'🤰‍‍',
'🤱‍‍',
'🙇‍♀️‍‍',
'🙇‍♂️‍‍',
'💁‍♀️️‍‍',
'💁‍♂️️️‍‍',
'🙅‍♀️️️️‍‍',
'🙅‍♂️‍‍',
'🙆‍♀️️‍‍',
'🙆‍♂️️️‍‍',
'🙋‍♀️️️️‍‍',
'🙋‍♂️‍‍',
'🤦‍♀️️‍‍',
'🤦‍♂️️️‍‍',
'🤷‍♀️️️️‍‍',
'🤷‍♂️️️️️‍‍',
'🙎‍♀️️️️️️‍‍',
'🙎‍♂️️️️️️️‍‍',
'🙍‍♀️️️️️️️️‍‍',
'🙍‍♂️️️️️️️️️‍‍',
'💇‍♀️️️️️️️️️️‍‍',
'💇‍♂️️️️️️️️️️️‍‍',
'💆‍♀️️️️️️️️️️️️‍‍',
'💆‍♂️️️️️️️️️️️️️‍‍',
'🧖‍♀️️️️️️️️️️️️️️‍‍',
'🧖‍♂️️️️️️️️️️️️️️️‍‍',
'💅️️️️️️️️️️️️️️️‍‍',
'🤳️️️️️️️️️️️️️️‍‍',
'💃️️️️️️️️️️️️️‍‍',
'🕺️️️️️️️️️️️️‍‍',
'👯‍♀️‍‍',
'👯‍♂️️‍‍',
'🕴️️‍‍',
'🚶‍♀️️‍‍',
'🚶‍♂️️️‍‍',
'🏃‍♀️️️️‍‍',
'🏃‍♂️‍‍',
'👫️‍‍',
'👭‍‍',
'👬‍‍',
'💑‍‍',
'👩‍❤️‍👩‍‍',
'👨‍❤️‍👨‍‍',
'💏‍‍',
'👩‍❤️‍💋‍👩‍‍',
'👨‍❤️‍💋‍👨‍‍',
'👪‍‍',
'👨‍👩‍👧‍‍',
'👨‍👩‍👧‍👦‍‍',
'👨‍👩‍👦‍👦‍‍',
'👨‍👩‍👧‍👧‍‍',
'👩‍👩‍👦‍‍',
'👩‍👩‍👧‍‍',
'👩‍👩‍👧‍👦‍‍',
'👩‍👩‍👦‍👦‍‍',
'👩‍👩‍👧‍👧‍‍',
'👨‍👨‍👦‍‍',
'👨‍👨‍👧‍‍',
'👨‍👨‍👧‍👦‍‍',
'👨‍👨‍👦‍👦‍‍',
'👨‍👨‍👧‍👧‍‍',
'👩‍👦‍‍',
'👩‍👧‍‍',
'👩‍👧‍👦‍‍',
'👩‍👦‍👦‍‍',
'👩‍👧‍👧‍‍',
'👨‍👦‍‍',
'👨‍👧‍‍',
'👨‍👧‍👦‍‍',
'👨‍👦‍👦‍‍',
'👨‍👧‍👧‍‍',
'🤲‍‍',
'👐‍‍',
'🙌‍‍',
'👏‍‍',
'🤝‍‍',
'👍‍‍',
'👎‍‍',
'👊‍‍',
'✊‍‍',
'🤛‍‍',
'🤜‍‍',
'🤞‍‍',
'✌️‍‍',
'🤟️‍‍',
'🤘‍‍',
'👌‍‍',
'👈‍‍',
'👉‍‍',
'👆‍‍',
'👇‍‍',
'☝️‍‍',
'✋️‍‍',
'🤚️‍‍',
'🤚️‍‍',
'🖐‍‍',
'🖖‍‍',
'👋‍‍',
'🤙‍‍',
'💪‍‍',
'🦵‍‍',
'🦶‍‍',
'🖕‍‍',
'✍️‍‍',
'🙏️‍‍',
'💍‍‍',
'💄‍‍',
'💋‍‍',
'👄‍‍',
'👅‍‍',
'👂‍‍',
'👃‍‍',
'👣‍‍',
'👁‍‍',
'👀‍‍',
'🧠‍‍',
'🦴‍‍',
'🦷‍‍',
'🗣‍‍',
'👤‍‍',
'👥‍‍',
'🧥‍‍',
'👚‍‍',
'👕‍‍',
'👖‍‍',
'👔‍‍',
'👗‍‍',
'👙‍‍',
'👘‍‍',
'👠‍‍',
'👡‍‍',
'👢‍‍',
'👞‍‍',
'👟‍‍',
'🥾‍‍',
'🥿‍‍',
'🧦‍‍',
'🧤‍‍',
'🧣‍‍',
'🎩‍‍',
'🧢‍‍',
'👒‍‍',
'🎓‍‍',
'⛑‍‍',
'👑‍‍',
'👝‍‍',
'👛‍‍',
'👜‍‍',
'💼‍‍',
'🎒‍‍',
'👓‍‍',
'🕶‍‍',
'🥽‍‍',
'🥼‍‍',
'🌂‍‍',
'🧵‍‍',
'🧶‍‍',
/// Animals
'🐶‍‍',
'🐱‍‍',
'🐭‍‍',
'🐰‍‍',
'🦊‍‍',
'🦝‍‍',
'🐻‍‍',
'🦘‍‍',
'🦡‍‍',
'🐨‍‍',
'🐯‍‍',
'🦁‍‍',
'🐼‍‍',
'🐼‍‍',
'🐮‍‍',
'🐷‍‍',
'🐽‍‍',
'🐸‍‍',
'🐵‍‍',
'🙈‍‍',
'🙉‍‍',
'🙊‍‍',
'🐒‍‍',
'🐔‍‍',
'🐧‍‍',
'🐦‍‍',
'🐤‍‍',
'🐣‍‍',
'🐥‍‍',
'🦆‍‍',
'🦢‍‍',
'🦅‍‍',
'🦉‍‍',
'🦚‍‍',
'🦜‍‍',
'🦇‍‍',
'🐺‍‍',
'🐗‍‍',
'🐴‍‍',
'🦄‍‍',
'🐝‍‍',
'🐛‍‍',
'🦋‍‍',
'🐌‍‍',
'🐚‍‍',
'🐞‍‍',
'🐜‍‍',
'🦗‍‍',
'🕷‍‍',
'🕸‍‍',
'🦂‍‍',
'🦟‍‍',
'🦠‍‍',
'🐢‍‍',
'🐍‍‍',
'🦎‍‍',
'🦖‍‍',
'🦕‍‍',
'🐙‍‍',
'🦑‍‍',
'🦐‍‍',
'🦀‍‍',
'🐡‍‍',
'🐠‍‍',
'🐟‍‍',
'🐬‍‍',
'🐳‍‍',
'🐋‍‍',
'🦈‍‍',
'🐊‍‍',
'🐅‍‍',
'🐆‍‍',
'🦓‍‍',
'🦍‍‍',
'🐘‍‍',
'🦏‍‍',
'🦛‍‍',
'🐪‍‍',
'🐫‍‍',
'🦙‍‍',
'🦒‍‍',
'🐃‍‍',
'🐂‍‍',
'🐄‍‍',
'🐎‍‍',
'🐖‍‍',
'🐏‍‍',
'🐐‍‍',
'🦌‍‍',
'🐕‍‍',
'🐩‍‍',
'🐈‍‍',
'🐓‍‍',
'🦃‍‍',
'🕊‍‍',
'🐇‍‍',
'🐁‍‍',
'🐀‍‍',
'🐿‍‍',
'🦔‍‍',
'🐾‍',
'🐉‍',
'🐲‍',
'🌵‍',
'🎄‍',
'🌲‍',
'🌳‍',
'🌴‍',
'🌱‍',
'🌿‍',
'☘️‍',
'🎍️‍',
'🎋️‍',
'🍃‍',
'🍂‍',
'🍁‍',
'🍄‍',
'🌾️‍',
'💐️‍',
'🌷️‍',
'🌹‍',
'🥀‍',
'🌺‍',
'🌸‍',
'🌼‍',
'🌻️‍',
'🌞‍',
'🌝‍',
'🌛‍',
'🌜‍',
'🌚‍',
'🌕‍',
'🌖‍',
'🌗‍',
'🌘‍',
'🌑‍',
'🌒‍',
'🌔‍',
'🌙‍',
'🌎‍',
'🌍‍',
'🌏‍',
'💫‍',
'⭐️‍',
'🌟️‍',
'✨️‍',
'⚡️️‍',
'☄️️️‍',
'💥️️️‍',
'🔥‍',
'🌪‍',
'🌈‍',
'☀️‍',
'🌤️‍',
'⛅️️‍',
'🌥️️‍',
'☁️️‍',
'🌦️️‍',
'🌧️‍',
'⛈‍',
'🌩‍',
'🌨‍',
'❄️‍',
'☃️️‍',
'⛄️️️‍',
'🌬️️️‍',
'💨️️️‍',
'💧️️️‍',
'💦️️️‍',
'☔️️️️‍',
'☂️️️️️‍',
'🌊️️️️️‍',
'🌫️️️️‍',
/// Foods
'🍏‍',
'🍎‍',
'🍐‍',
'🍊‍',
'🍋‍',
'🍌‍',
'🍉‍',
'🍇‍',
'🍓‍',
'🍈‍',
'🍒‍',
'🍑‍',
'🍍‍',
'🥭‍',
'🥥‍',
'🥝‍',
'🍅‍',
'🍆‍',
'🥑‍',
'🥦‍',
'🥒‍',
'🥬‍',
'🌶‍',
'🌽‍',
'🥕‍',
'🥔‍',
'🍠‍',
'🥐‍',
'🍞‍',
'🥖‍',
'🥨‍',
'🥯‍',
'🧀‍',
'🥚‍',
'🍳‍',
'🥞‍',
'🥓‍',
'🥩‍',
'🍗‍',
'🍖‍',
'🌭‍',
'🍔‍',
'🍟‍',
'🍕‍',
'🥪‍',
'🥙‍',
'🌮‍',
'🌯‍',
'🥗‍',
'🥘‍',
'🥫‍',
'🍝‍',
'🍜‍',
'🍲‍',
'🍛‍',
'🍣‍',
'🍱‍',
'🥟‍',
'🍤‍',
'🍙‍',
'🍚‍',
'🍘‍',
'🍥‍',
'🥮‍',
'🥠‍',
'🍢‍',
'🍧‍',
'🍨‍',
'🍦‍',
'🥧‍',
'🍰‍',
'🎂‍',
'🍮‍',
'🍭‍',
'🍬‍',
'🍫‍',
'🍿‍',
'🧂‍',
'🍩‍',
'🍪‍',
'🌰‍',
'🥜‍',
'🍯‍',
'🥛‍',
'🍼‍',
'☕️‍',
'🍵️‍',
'🥤️‍',
'🍶‍',
'🍺‍',
'🍻‍',
'🥂‍',
'🍷‍',
'🍸‍',
'🍹‍',
'🍾‍',
'🥄‍',
'🍴‍',
'🍽‍',
'🥣‍',
'🥡‍',
'🥢‍',
/// Activity and Sports
'⚽️‍',
'🏀️‍',
'🏈‍',
'⚾️‍',
'🥎️‍',
'🏐️‍',
'🏉‍',
'🎾‍',
'🥏‍',
'🎱‍',
'🏓‍',
'🏸‍',
'🥅‍',
'🏒‍',
'🏑‍',
'🥍‍',
'🏏‍',
'⛳️‍',
'🏹️‍',
'🎣️‍',
'🥊‍',
'🥋‍',
'🎽‍',
'⛸‍',
'🥌‍',
'🛷‍',
'🛹‍',
'🎿‍',
'⛷‍',
'🏂‍',
'🏋️‍♀️‍',
'🏋🏼‍♀️‍',
'🏋🏽‍♀️️‍',
'🏋🏾‍♀️️️‍',
'🏋🏿‍♀️️️️‍',
'🏋️‍♂️️️️‍',
'🏋🏻‍♂️️️️‍',
'🏋🏼‍♂️️️️‍',
'🏋🏽‍♂️️️️‍',
'🏋🏾‍♂️️️️‍',
'🏋🏿‍♂️️️️‍',
'🤼‍♀️️️️‍',
'🤼‍♂️️️️‍',
'🤸‍♀️️️️‍',
'🤸🏻‍♀️️️️‍',
'🤸🏼‍♀️️️️‍',
'🤸🏽‍♀️️️️‍',
'🤸🏿‍♀️️️️️‍',
'🤸‍♂️️️️‍',
'🤸🏻‍♂️️️️‍',
'🤸🏼‍♂️️️️️‍',
'🤸🏽‍♂️️️️️️‍',
'🤸🏾‍♂️️️️️️‍',
'🤸🏿‍♂️️️️️️‍',
'⛹️‍♀️️️️️️‍',
'⛹🏻‍♀️️️️️️️‍',
'⛹🏼‍♀️️️️️️️️‍',
'⛹🏽‍♀️️️️️️️️️‍',
'⛹🏾‍♀️️️️️️️️️️‍',
'⛹🏿‍♀️️️️️️️️️️️‍',
'⛹️‍♂️️️️️️️️️️️️‍',
'⛹🏻‍♂️️️️️️️️️️️️️‍',
'⛹🏼‍♂️️️️️️️️️️️️️️‍',
'⛹🏽‍♂️️️️️️️️️️️️️️️‍',
'⛹🏾‍♂️️️️️️️️️️️️️️️️‍',
'⛹🏿‍♂️‍',
'🤺️‍',
'🤾‍♀️‍',
'🤾🏻‍♀️️‍',
'🤾🏼‍♀️️️‍',
'🤾🏾‍♀️️️️‍',
];

View file

@ -1,173 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Emoji layer
class EmojiLayer extends StatefulWidget {
const EmojiLayer({
required this.layerData,
super.key,
this.onUpdate,
});
final EmojiLayerData layerData;
final VoidCallback? onUpdate;
@override
State<EmojiLayer> createState() => _EmojiLayerState();
}
class _EmojiLayerState extends State<EmojiLayer> {
double initialRotation = 0;
Offset initialOffset = Offset.zero;
Offset initialFocalPoint = Offset.zero;
double initialScale = 1;
bool deleteLayer = false;
bool twoPointerWhereDown = false;
final GlobalKey outlineKey = GlobalKey();
final GlobalKey emojiKey = GlobalKey();
int pointers = 0;
bool display = false;
@override
void initState() {
super.initState();
if (widget.layerData.offset.dy == 0) {
// Set the initial offset to the center of the screen
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
widget.layerData.offset = Offset(
MediaQuery.of(context).size.width / 2 - (153 / 2),
MediaQuery.of(context).size.height / 2 - (153 / 2) - 100,
);
});
display = true;
});
} else {
display = true;
}
}
@override
Widget build(BuildContext context) {
if (!display) return Container();
if (widget.layerData.isDeleted) return Container();
return Stack(
key: outlineKey,
children: [
Positioned(
left: widget.layerData.offset.dx,
top: widget.layerData.offset.dy,
child: Listener(
onPointerUp: (details) {
setState(() {
pointers--;
if (pointers == 0) {
twoPointerWhereDown = false;
}
if (deleteLayer) {
widget.layerData.isDeleted = true;
widget.onUpdate!();
}
});
},
onPointerDown: (details) {
setState(() {
pointers++;
});
},
child: GestureDetector(
onScaleStart: (details) {
initialScale = widget.layerData.size;
initialRotation = widget.layerData.rotation;
initialOffset = widget.layerData.offset;
initialFocalPoint =
Offset(details.focalPoint.dx, details.focalPoint.dy);
setState(() {});
},
onScaleUpdate: (details) async {
if (twoPointerWhereDown && details.pointerCount != 2) {
return;
}
final outlineBox =
outlineKey.currentContext!.findRenderObject()! as RenderBox;
final emojiBox =
emojiKey.currentContext!.findRenderObject()! as RenderBox;
final isAtTheBottom =
(widget.layerData.offset.dy + emojiBox.size.height / 2) >
outlineBox.size.height - 80;
final isInTheCenter = MediaQuery.of(context).size.width / 2 -
30 <
(widget.layerData.offset.dx +
emojiBox.size.width / 2) &&
MediaQuery.of(context).size.width / 2 + 20 >
(widget.layerData.offset.dx + emojiBox.size.width / 2);
if (isAtTheBottom && isInTheCenter) {
if (!deleteLayer) {
await HapticFeedback.heavyImpact();
}
deleteLayer = true;
} else {
deleteLayer = false;
}
setState(() {
twoPointerWhereDown = details.pointerCount >= 2;
widget.layerData.size = initialScale * details.scale;
if (widget.layerData.size > 96) {
// https://github.com/twonlyapp/twonly-app/issues/349
widget.layerData.size = 96;
}
// print(widget.layerData.size);
widget.layerData.rotation =
initialRotation + details.rotation;
// Update the position based on the translation
final dx = (initialOffset.dx) +
(details.focalPoint.dx - initialFocalPoint.dx);
final dy = (initialOffset.dy) +
(details.focalPoint.dy - initialFocalPoint.dy);
widget.layerData.offset = Offset(dx, dy);
});
},
child: Transform.rotate(
angle: widget.layerData.rotation,
key: emojiKey,
child: Container(
padding: const EdgeInsets.all(44),
color: Colors.transparent,
child: Text(
widget.layerData.text,
style: TextStyle(
fontSize: widget.layerData.size,
),
),
),
),
),
),
),
if (pointers > 0)
Positioned(
left: 0,
right: 0,
bottom: 20,
child: Center(
child: GestureDetector(
child: ActionButton(
FontAwesomeIcons.trashCan,
tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white,
),
),
),
),
],
);
}
}

View file

@ -13,7 +13,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart'; import 'package:twonly/src/views/camera/share_image_contact_selection/best_friends_selector.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/headline.dart'; import 'package:twonly/src/views/components/headline.dart';

View file

@ -2,6 +2,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:drift/drift.dart' show Value; import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -18,13 +19,13 @@ import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/share_image_contact_selection.view.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart'; import 'package:twonly/src/views/camera/share_image_contact_selection/select_show_time.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/image_item.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/share_image_components/select_show_time.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers_viewer.dart';
import 'package:twonly/src/views/camera/share_image_view.dart'; import 'package:twonly/src/views/components/emoji_picker.bottom.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/notification_badge.dart'; import 'package:twonly/src/views/components/notification_badge.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';

View file

@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart'; import 'package:hand_signature/signature.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/image_item.dart';
/// Layer class with some common properties /// Layer class with some common properties
class Layer { class Layer {
@ -51,7 +51,7 @@ class EmojiLayerData extends Layer {
EmojiLayerData({ EmojiLayerData({
required super.key, required super.key,
this.text = '', this.text = '',
this.size = 64, this.size = 94,
super.offset, super.offset,
super.opacity, super.opacity,
super.rotation, super.rotation,

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
class BackgroundLayer extends StatefulWidget { class BackgroundLayer extends StatefulWidget {
const BackgroundLayer({ const BackgroundLayer({

View file

@ -1,12 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hand_signature/signature.dart'; import 'package:hand_signature/signature.dart';
// ignore: implementation_imports
import 'package:hand_signature/src/utils.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/screenshot.dart'; import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart';
class DrawLayer extends StatefulWidget { class DrawLayer extends StatefulWidget {
const DrawLayer({ const DrawLayer({
@ -23,8 +22,6 @@ class DrawLayer extends StatefulWidget {
class _DrawLayerState extends State<DrawLayer> { class _DrawLayerState extends State<DrawLayer> {
Color currentColor = Colors.red; Color currentColor = Colors.red;
ScreenshotController screenshotController = ScreenshotController();
List<CubicPath> undoList = []; List<CubicPath> undoList = [];
bool skipNextEvent = false; bool skipNextEvent = false;
bool showMagnifyingGlass = false; bool showMagnifyingGlass = false;
@ -85,17 +82,11 @@ class _DrawLayerState extends State<DrawLayer> {
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Positioned.fill( Positioned.fill(
child: Container( child: CustomHandSignature(
decoration: const BoxDecoration( control: widget.layerData.control,
color: Colors.transparent, isModificationEnabled: widget.layerData.isEditing,
), currentColor: currentColor,
child: Screenshot( width: 7,
controller: screenshotController,
child: HandSignature(
control: widget.layerData.control,
drawer: CustomSignatureDrawer(color: currentColor, width: 7),
),
),
), ),
), ),
if (widget.layerData.isEditing && widget.layerData.showCustomButtons) if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
@ -211,12 +202,12 @@ class _DrawLayerState extends State<DrawLayer> {
top: 50 + (185 * _sliderValue), top: 50 + (185 * _sliderValue),
child: MagnifyingGlass(color: currentColor), child: MagnifyingGlass(color: currentColor),
), ),
if (!widget.layerData.isEditing) // if (!widget.layerData.isEditing)
Positioned.fill( // Positioned.fill(
child: Container( // child: Container(
color: Colors.transparent, // color: Colors.transparent,
), // ),
), // ),
], ],
); );
} }
@ -244,33 +235,3 @@ class MagnifyingGlass extends StatelessWidget {
); );
} }
} }
class CustomSignatureDrawer extends HandSignatureDrawer {
const CustomSignatureDrawer({
this.width = 1.0,
this.color = Colors.black,
});
final Color color;
final double width;
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
for (final path in paths) {
var lineColor = color;
if (path.setup.args!['color'] != null) {
lineColor = path.setup.args!['color'] as Color;
} else {
path.setup.args!['color'] = color;
}
final paint = Paint()
..color = lineColor
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = width;
if (path.isFilled) {
canvas.drawPath(PathUtil.toLinePath(path.lines), paint);
}
}
}
}

View file

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart';
// ignore: implementation_imports
import 'package:hand_signature/src/utils.dart';
class CustomHandSignature extends StatelessWidget {
const CustomHandSignature({
required this.control,
required this.isModificationEnabled,
required this.currentColor,
required this.width,
super.key,
});
/// The controller that manages the creation and manipulation of signature paths.
final HandSignatureControl control;
final bool isModificationEnabled;
final Color currentColor;
final double width;
@override
Widget build(BuildContext context) {
control.params = SignaturePaintParams(
color: currentColor,
strokeWidth: 7,
);
final drawer = CustomSignatureDrawer(color: currentColor, width: width);
if (isModificationEnabled) {
return HandSignature(
control: control,
drawer: drawer,
);
}
return IgnorePointer(
child: ClipRRect(
child: HandSignaturePaint(
control: control,
drawer: drawer,
onSize: control.notifyDimension,
),
),
);
}
}
class CustomSignatureDrawer extends HandSignatureDrawer {
const CustomSignatureDrawer({
this.width = 1.0,
this.color = Colors.black,
});
final Color color;
final double width;
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
for (final path in paths) {
var lineColor = color;
if (path.setup.args!['color'] != null) {
lineColor = path.setup.args!['color'] as Color;
} else {
path.setup.args!['color'] = color;
}
final paint = Paint()
..color = lineColor
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = width;
if (path.isFilled) {
canvas.drawPath(PathUtil.toLinePath(path.lines), paint);
}
}
}
}

View file

@ -0,0 +1,248 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
/// Emoji layer
class EmojiLayer extends StatefulWidget {
const EmojiLayer({
required this.layerData,
super.key,
this.onUpdate,
});
final EmojiLayerData layerData;
final VoidCallback? onUpdate;
@override
State<EmojiLayer> createState() => _EmojiLayerState();
}
class _EmojiLayerState extends State<EmojiLayer> {
double initialRotation = 0;
Offset initialOffset = Offset.zero;
Offset initialFocalPoint = Offset.zero;
double initialScale = 1;
bool deleteLayer = false;
bool twoPointerWhereDown = false;
final GlobalKey outlineKey = GlobalKey();
final GlobalKey emojiKey = GlobalKey();
int pointers = 0;
bool display = false;
@override
void initState() {
super.initState();
if (widget.layerData.offset.dy == 0) {
// Set the initial offset to the center of the screen
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
widget.layerData.offset = Offset(
MediaQuery.of(context).size.width / 2 - (153 / 2),
MediaQuery.of(context).size.height / 2 - (153 / 2) - 100,
);
});
display = true;
});
} else {
display = true;
}
}
@override
Widget build(BuildContext context) {
if (!display) return Container();
if (widget.layerData.isDeleted) return Container();
return Stack(
key: outlineKey,
children: [
Positioned(
left: widget.layerData.offset.dx,
top: widget.layerData.offset.dy,
child: PhysicalModel(
color: Colors.transparent,
borderRadius: BorderRadius.circular(180),
clipBehavior: Clip.antiAlias,
child: Listener(
onPointerUp: (details) {
setState(() {
pointers--;
if (pointers == 0) {
twoPointerWhereDown = false;
}
if (deleteLayer) {
widget.layerData.isDeleted = true;
widget.onUpdate!();
}
});
},
onPointerDown: (details) {
setState(() {
pointers++;
});
},
child: GestureDetector(
onScaleStart: (details) {
initialScale = widget.layerData.size;
initialRotation = widget.layerData.rotation;
initialOffset = widget.layerData.offset;
initialFocalPoint =
Offset(details.focalPoint.dx, details.focalPoint.dy);
setState(() {});
},
onScaleUpdate: (details) async {
if (twoPointerWhereDown && details.pointerCount != 2) {
return;
}
final outlineBox = outlineKey.currentContext!
.findRenderObject()! as RenderBox;
final emojiBox =
emojiKey.currentContext!.findRenderObject()! as RenderBox;
final isAtTheBottom =
(widget.layerData.offset.dy + emojiBox.size.height / 2) >
outlineBox.size.height - 80;
final isInTheCenter =
MediaQuery.of(context).size.width / 2 - 30 <
(widget.layerData.offset.dx +
emojiBox.size.width / 2) &&
MediaQuery.of(context).size.width / 2 + 20 >
(widget.layerData.offset.dx +
emojiBox.size.width / 2);
if (isAtTheBottom && isInTheCenter) {
if (!deleteLayer) {
await HapticFeedback.heavyImpact();
}
deleteLayer = true;
} else {
deleteLayer = false;
}
setState(() {
twoPointerWhereDown = details.pointerCount >= 2;
widget.layerData.size = initialScale * details.scale;
// print(widget.layerData.size);
widget.layerData.rotation =
initialRotation + details.rotation;
// Update the position based on the translation
final dx = (initialOffset.dx) +
(details.focalPoint.dx - initialFocalPoint.dx);
final dy = (initialOffset.dy) +
(details.focalPoint.dy - initialFocalPoint.dy);
widget.layerData.offset = Offset(dx, dy);
});
},
child: Transform.rotate(
angle: widget.layerData.rotation,
key: emojiKey,
child: Container(
padding: const EdgeInsets.all(44),
color: Colors.transparent,
child: ScreenshotEmoji(
emoji: widget.layerData.text,
displaySize: widget.layerData.size,
),
),
),
),
),
),
),
if (pointers > 0)
Positioned(
left: 0,
right: 0,
bottom: 20,
child: Center(
child: GestureDetector(
child: ActionButton(
FontAwesomeIcons.trashCan,
tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white,
),
),
),
),
],
);
}
}
// Workaround: https://github.com/twonlyapp/twonly-app/issues/349
class ScreenshotEmoji extends StatefulWidget {
const ScreenshotEmoji({
required this.emoji,
required this.displaySize,
super.key,
});
final String emoji;
final double displaySize;
@override
State<ScreenshotEmoji> createState() => _ScreenshotEmojiState();
}
class _ScreenshotEmojiState extends State<ScreenshotEmoji> {
final GlobalKey _boundaryKey = GlobalKey();
ui.Image? _capturedImage;
@override
void initState() {
super.initState();
// Capture the emoji immediately after the first frame
WidgetsBinding.instance.addPostFrameCallback((_) => _captureEmoji());
}
Future<void> _captureEmoji() async {
try {
final boundary = _boundaryKey.currentContext?.findRenderObject()
as RenderRepaintBoundary?;
if (boundary == null) return;
final image = await boundary.toImage(pixelRatio: 4);
setState(() {
_capturedImage = image;
});
} catch (e) {
Log.error('Error capturing emoji: $e');
}
}
@override
Widget build(BuildContext context) {
if (_capturedImage != null) {
return SizedBox(
width: widget.displaySize,
height: widget.displaySize,
child: RawImage(
image: _capturedImage,
fit: BoxFit.contain,
),
);
}
return Stack(
children: [
Positioned(
top: -200, // hide from the user as the size changes with the image
child: RepaintBoundary(
key: _boundaryKey,
child: Text(
widget.emoji,
style: const TextStyle(fontSize: 94),
),
),
),
SizedBox(width: widget.displaySize, height: widget.displaySize),
],
);
}
}

View file

@ -2,10 +2,10 @@ import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/image_filter.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filters/image_filter.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/location_filter.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filters/location_filter.dart';
/// Main layer /// Main layer
class FilterLayer extends StatefulWidget { class FilterLayer extends StatefulWidget {

View file

@ -1,7 +1,7 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart';
class DateTimeFilter extends StatelessWidget { class DateTimeFilter extends StatelessWidget {
const DateTimeFilter({super.key, this.color = Colors.white}); const DateTimeFilter({super.key, this.color = Colors.white});

View file

@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart';
class ImageFilter extends StatelessWidget { class ImageFilter extends StatelessWidget {
const ImageFilter({required this.imagePath, super.key}); const ImageFilter({required this.imagePath, super.key});

View file

@ -11,8 +11,8 @@ import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart';
class LocationFilter extends StatefulWidget { class LocationFilter extends StatefulWidget {
const LocationFilter({super.key}); const LocationFilter({super.key});

View file

@ -5,8 +5,8 @@ import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/providers/image_editor.provider.dart'; import 'package:twonly/src/providers/image_editor.provider.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
/// Text layer /// Text layer
class TextLayer extends StatefulWidget { class TextLayer extends StatefulWidget {

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/background_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/background.layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/draw_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/draw.layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/emoji_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/emoji.layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/text_layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/text.layer.dart';
/// View stacked layers (unbounded height, width) /// View stacked layers (unbounded height, width)
class LayersViewer extends StatelessWidget { class LayersViewer extends StatelessWidget {
@ -37,7 +37,9 @@ class LayersViewer extends StatelessWidget {
...layers ...layers
.where( .where(
(layerItem) => (layerItem) =>
layerItem is EmojiLayerData || layerItem is DrawLayerData, layerItem is EmojiLayerData ||
layerItem is DrawLayerData ||
layerItem is TextLayerData,
) )
.map((layerItem) { .map((layerItem) {
if (layerItem is EmojiLayerData) { if (layerItem is EmojiLayerData) {
@ -52,16 +54,15 @@ class LayersViewer extends StatelessWidget {
layerData: layerItem, layerData: layerItem,
onUpdate: onUpdate, onUpdate: onUpdate,
); );
} else if (layerItem is TextLayerData) {
return TextLayer(
key: layerItem.key,
layerData: layerItem,
onUpdate: onUpdate,
);
} }
return Container(); return Container();
}), }),
...layers.whereType<TextLayerData>().map((layerItem) {
return TextLayer(
key: layerItem.key,
layerData: layerItem,
onUpdate: onUpdate,
);
}),
], ],
); );
} }

View file

@ -9,7 +9,7 @@ import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/services/api/mediafiles/download.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/camera/camera_send_to.view.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/chat_messages_components/message_send_state_icon.dart';

View file

@ -14,8 +14,8 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dar
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; import 'package:twonly/src/views/components/emoji_picker.bottom.dart';
import 'package:twonly/src/views/chats/message_info.view.dart'; import 'package:twonly/src/views/chats/message_info.view.dart';
import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/components/context_menu.component.dart'; import 'package:twonly/src/views/components/context_menu.component.dart';

View file

@ -14,7 +14,7 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/camera/camera_send_to.view.dart';
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
class MessageInput extends StatefulWidget { class MessageInput extends StatefulWidget {

View file

@ -112,7 +112,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
case MessageSendState.receivedOpened: case MessageSendState.receivedOpened:
icon = Icon(Icons.crop_square, size: 14, color: color); icon = Icon(Icons.crop_square, size: 14, color: color);
if (message.content != null) { if (message.content != null) {
if (isEmoji(message.content!)) { if (isOneEmoji(message.content!)) {
icon = Text( icon = Text(
message.content!, message.content!,
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),

View file

@ -20,7 +20,7 @@ import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/camera/camera_send_to.view.dart';
import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart'; import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/loader.dart'; import 'package:twonly/src/views/components/loader.dart';

View file

@ -5,8 +5,8 @@ import 'package:flutter/scheduler.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; import 'package:twonly/src/views/components/emoji_picker.bottom.dart';
import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart'; import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/share_image_editor/data/layer.dart';
class EmojiPickerBottom extends StatelessWidget { class EmojiPickerBottom extends StatelessWidget {
const EmojiPickerBottom({super.key}); const EmojiPickerBottom({super.key});

View file

@ -14,7 +14,7 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/camera/share_image_editor.view.dart';
import 'package:twonly/src/views/chats/chat_list.view.dart'; import 'package:twonly/src/views/chats/chat_list.view.dart';
import 'package:twonly/src/views/memories/memories.view.dart'; import 'package:twonly/src/views/memories/memories.view.dart';

View file

@ -9,7 +9,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/camera/share_image_editor.view.dart';
import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/video_player_wrapper.dart'; import 'package:twonly/src/views/components/video_player_wrapper.dart';

View file

@ -4,7 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/location_filter.dart'; import 'package:twonly/src/views/camera/share_image_editor/layers/filters/location_filter.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class UrlListTitle extends StatelessWidget { class UrlListTitle extends StatelessWidget {

View file

@ -8,10 +8,10 @@ import 'package:twonly/src/views/components/animate_icon.dart';
void main() { void main() {
group('testing utils', () { group('testing utils', () {
test('test isEmoji function', () { test('test isEmoji function', () {
expect(isEmoji('Hallo'), false); expect(isOneEmoji('Hallo'), false);
expect(isEmoji('😂'), true); expect(isOneEmoji('😂'), true);
expect(isEmoji('😂😂'), false); expect(isOneEmoji('😂😂'), false);
expect(isEmoji('Hallo 😂'), false); expect(isOneEmoji('Hallo 😂'), false);
}); });
test('test proof-of-work simple', () async { test('test proof-of-work simple', () async {