This commit is contained in:
otsmr 2025-04-13 15:12:25 +02:00
parent 8763313c41
commit 1bd33b2013
12 changed files with 272 additions and 137 deletions

View file

@ -5,10 +5,9 @@ import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
import 'package:twonly/src/views/contact/contact_verify_view.dart';
import 'package:twonly/src/views/home_view.dart';
class UserContextMenu extends StatefulWidget {
final Widget child;
@ -51,19 +50,18 @@ class _UserContextMenuState extends State<UserContextMenu> {
},
child: FaIcon(FontAwesomeIcons.boxOpen),
),
PieAction(
tooltip: Text(context.lang.contextMenuVerifyUser),
onSelect: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ContactVerifyView(widget.contact);
},
));
},
child: widget.contact.verified
? FaIcon(FontAwesomeIcons.shieldHeart)
: const Icon(Icons.gpp_maybe_rounded),
),
if (!widget.contact.verified)
PieAction(
tooltip: Text(context.lang.contextMenuVerifyUser),
onSelect: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ContactVerifyView(widget.contact);
},
));
},
child: const Icon(Icons.gpp_maybe_rounded),
),
PieAction(
tooltip: Text(context.lang.contextMenuOpenChat),
onSelect: () {
@ -78,8 +76,11 @@ class _UserContextMenuState extends State<UserContextMenu> {
PieAction(
tooltip: Text(context.lang.contextMenuSendImage),
onSelect: () {
globalSendNextMediaToUser = widget.contact;
globalUpdateOfHomeViewPageIndex(0);
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.contact);
},
));
},
child: const FaIcon(FontAwesomeIcons.camera),
),

View file

@ -23,6 +23,7 @@
"registerSubmitButton": "Jetzt registrieren!",
"newMessageTitle": "Neue Nachricht",
"chatsTapToSend": "Klicke, um dein erstes Bild zu teilen.",
"cameraPreviewSendTo": "Senden an",
"shareImageTitle": "Teilen mit",
"shareImageBestFriends": "Beste Freunde",
"shareImagedEditorSendImage": "Senden",

View file

@ -46,6 +46,8 @@
"@newMessageTitle": {},
"chatsTapToSend": "Click to send your first image",
"@chatsTapToSend": {},
"cameraPreviewSendTo": "Send to",
"@cameraPreviewSendTo": {},
"shareImageTitle": "Share with",
"@shareImageTitle": {},
"shareImageBestFriends": "Best friends",

View file

@ -7,43 +7,17 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/zoom_selector.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/components/permissions_view.dart';
import 'package:twonly/src/views/camera_to_share/share_image_editor_view.dart';
class CameraPreviewViewPermission extends StatefulWidget {
const CameraPreviewViewPermission({super.key});
@override
State<CameraPreviewViewPermission> createState() =>
_CameraPreviewViewPermission();
}
class _CameraPreviewViewPermission extends State<CameraPreviewViewPermission> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkPermissions(),
builder: (context, snap) {
if (snap.hasData) {
if (snap.data!) {
return CameraPreviewView();
} else {
return PermissionHandlerView(onSuccess: () {
setState(() {});
});
}
} else {
return Container();
}
});
}
}
class CameraPreviewView extends StatefulWidget {
const CameraPreviewView({super.key});
const CameraPreviewView({super.key, this.sendTo});
final Contact? sendTo;
@override
State<CameraPreviewView> createState() => _CameraPreviewViewState();
@ -171,12 +145,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
setState(() {
sharePreviewIsShown = true;
});
await Navigator.push(
bool? shoudReturn = await Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (context, a1, a2) =>
ShareImageEditorView(imageBytes: imageBytes),
ShareImageEditorView(imageBytes: imageBytes, sendTo: widget.sendTo),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child;
},
@ -184,6 +158,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
reverseTransitionDuration: Duration.zero,
),
);
if (shoudReturn != null && shoudReturn) {
if (!context.mounted) return;
return Navigator.pop(context);
}
// does not work??
//await controller.resumePreview();
selectCamera(0);
@ -201,6 +179,19 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
bool get isFront =>
controller.description.lensDirection == CameraLensDirection.front;
Future onPanUpdate(details) async {
if (isFront) {
return;
}
var diff = basePanY - details.localPosition.dy;
if (diff > 200) diff = 200;
if (diff < -200) diff = -200;
var tmp = (diff / 200 * (7 * 2)).toInt() / 2;
tmp = baseScaleFactor + tmp;
if (tmp < 1) tmp = 1;
updateScaleFactor(tmp);
}
@override
Widget build(BuildContext context) {
if (cameraId >= gCameras.length) {
@ -215,33 +206,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
borderRadius: BorderRadius.circular(22),
child: Stack(
children: [
(controller.value.isInitialized)
? Positioned.fill(
child: Screenshot(
controller: screenshotController,
child: AspectRatio(
aspectRatio: 9 / 16,
child: ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: controller.value.previewSize!.height,
height: controller.value.previewSize!.width,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
(isFront && Platform.isAndroid)
? 3.14
: 0),
child: CameraPreview(controller),
),
),
),
),
),
),
)
: Container(),
CameraPreviewWidget(
controller: controller,
screenshotController: screenshotController,
isFront: isFront,
),
Positioned.fill(
child: GestureDetector(
onPanStart: (details) async {
@ -253,23 +222,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
baseScaleFactor = scaleFactor;
});
},
onPanUpdate: (details) async {
if (isFront) {
return;
}
var diff = basePanY - details.localPosition.dy;
if (diff > 200) diff = 200;
if (diff < -200) diff = -200;
var tmp = (diff / 200 * (7 * 2)).toInt() / 2;
tmp = baseScaleFactor + tmp;
if (tmp < 1) tmp = 1;
updateScaleFactor(tmp);
},
onPanUpdate: onPanUpdate,
onDoubleTap: () async {
selectCamera((cameraId + 1) % 2);
},
),
),
if (!sharePreviewIsShown && widget.sendTo != null)
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
if (!sharePreviewIsShown)
Positioned(
right: 5,
@ -362,6 +322,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
],
),
),
if (!sharePreviewIsShown && widget.sendTo != null)
Positioned(
left: 5,
top: 10,
child: ActionButton(
FontAwesomeIcons.xmark,
tooltipText: context.lang.close,
onPressed: () async {
Navigator.pop(context);
},
),
),
if (showSelfieFlash)
Positioned.fill(
child: ClipRRect(
@ -376,3 +348,130 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
);
}
}
class SendToWidget extends StatelessWidget {
final String sendTo;
const SendToWidget({
super.key,
required this.sendTo,
});
@override
Widget build(BuildContext context) {
TextStyle textStyle = TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 24,
decoration: TextDecoration.none,
shadows: [
Shadow(
color: const Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0,
),
],
);
TextStyle boldTextStyle = textStyle.copyWith(
fontWeight: FontWeight.normal,
fontSize: 28,
);
return Positioned(
right: 0,
left: 0,
top: 50,
child: Column(
children: [
Text(
context.lang.cameraPreviewSendTo,
textAlign: TextAlign.center,
style: textStyle,
),
Text(
sendTo,
textAlign: TextAlign.center,
style: boldTextStyle, // Use the bold text style here
),
],
),
);
}
String getContactDisplayName(String contact) {
// Replace this with your actual logic to get the contact display name
return contact; // Placeholder implementation
}
}
class CameraPreviewWidget extends StatelessWidget {
final CameraController controller;
final ScreenshotController screenshotController;
final bool isFront;
const CameraPreviewWidget({
super.key,
required this.controller,
required this.screenshotController,
required this.isFront,
});
@override
Widget build(BuildContext context) {
return (controller.value.isInitialized)
? Positioned.fill(
child: Screenshot(
controller: screenshotController,
child: AspectRatio(
aspectRatio: 9 / 16,
child: ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: controller.value.previewSize!.height,
height: controller.value.previewSize!.width,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
(isFront && Platform.isAndroid) ? 3.14 : 0),
child: CameraPreview(controller),
),
),
),
),
),
),
)
: Container();
}
}
class CameraPreviewViewPermission extends StatefulWidget {
const CameraPreviewViewPermission({super.key, this.sendTo});
final Contact? sendTo;
@override
State<CameraPreviewViewPermission> createState() =>
_CameraPreviewViewPermission();
}
class _CameraPreviewViewPermission extends State<CameraPreviewViewPermission> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkPermissions(),
builder: (context, snap) {
if (snap.hasData) {
if (snap.data!) {
return CameraPreviewView(sendTo: widget.sendTo);
} else {
return PermissionHandlerView(onSuccess: () {
setState(() {});
});
}
} else {
return Container();
}
});
}
}

View file

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/views/camera_to_share/camera_preview_view.dart';
class CameraSendToView extends StatefulWidget {
const CameraSendToView(this.sendTo, {super.key});
final Contact sendTo;
@override
State<CameraSendToView> createState() => CameraSendToViewState();
}
class CameraSendToViewState extends State<CameraSendToView> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CameraPreviewViewPermission(sendTo: widget.sendTo),
);
}
}

View file

@ -17,15 +17,16 @@ import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/components/image_editor/layers_viewer.dart';
import 'package:twonly/src/components/image_editor/modules/all_emojis.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/src/views/home_view.dart';
List<Layer> layers = [];
List<Layer> undoLayers = [];
List<Layer> removedLayers = [];
class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView({super.key, required this.imageBytes});
const ShareImageEditorView(
{super.key, required this.imageBytes, this.sendTo});
final Future<Uint8List?> imageBytes;
final Contact? sendTo;
@override
State<ShareImageEditorView> createState() => _ShareImageEditorView();
}
@ -187,7 +188,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
ActionButton(
FontAwesomeIcons.rotateLeft,
tooltipText: context.lang.undo,
disable: layers.length <= 2 && removedLayers.isEmpty,
disable: layers.where((x) => x.isDeleted).length <= 2 &&
removedLayers.isEmpty,
onPressed: () {
if (removedLayers.isNotEmpty) {
layers.add(removedLayers.removeLast());
@ -261,9 +263,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Widget build(BuildContext context) {
pixelRatio = MediaQuery.of(context).devicePixelRatio;
if (globalSendNextMediaToUser != null) {
sendNextMediaToUserName =
getContactDisplayName(globalSendNextMediaToUser!);
if (widget.sendTo != null) {
sendNextMediaToUserName = getContactDisplayName(widget.sendTo!);
}
return Scaffold(
@ -309,8 +310,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
),
),
Positioned(
top: 5,
left: 0,
top: 10,
left: 5,
right: 0,
child: SafeArea(
child: Row(
@ -392,16 +393,20 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
),
onPressed: () async {
Future<Uint8List?> imageBytes = getMergedImage();
Navigator.push(
bool? wasSend = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShareImageView(
imageBytesFuture: imageBytes,
isRealTwonly: _isRealTwonly,
maxShowTime: maxShowTime,
preselectedUser: widget.sendTo,
),
),
);
if (wasSend != null && wasSend && context.mounted) {
Navigator.pop(context, true);
}
},
child: FaIcon(FontAwesomeIcons.userPlus),
),
@ -420,7 +425,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
: FaIcon(FontAwesomeIcons.solidPaperPlane),
onPressed: () async {
if (sendingImage) return;
if (globalSendNextMediaToUser != null) {
if (widget.sendTo != null) {
setState(() {
sendingImage = true;
});
@ -431,26 +436,29 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
return;
}
sendImage(
[globalSendNextMediaToUser!.userId],
[widget.sendTo!.userId],
imageBytes,
_isRealTwonly,
maxShowTime,
);
Navigator.popUntil(context, (route) => route.isFirst);
globalUpdateOfHomeViewPageIndex(1);
Navigator.pop(context, true);
return;
}
Future<Uint8List?> imageBytes = getMergedImage();
Navigator.push(
bool? wasSend = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShareImageView(
imageBytesFuture: imageBytes,
isRealTwonly: _isRealTwonly,
maxShowTime: maxShowTime,
preselectedUser: widget.sendTo,
),
),
);
if (wasSend != null && wasSend && context.mounted) {
Navigator.pop(context, true);
}
},
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(

View file

@ -15,17 +15,17 @@ import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/home_view.dart';
Contact? globalSendNextMediaToUser;
class ShareImageView extends StatefulWidget {
const ShareImageView(
{super.key,
required this.imageBytesFuture,
required this.isRealTwonly,
required this.maxShowTime});
required this.maxShowTime,
this.preselectedUser});
final Future<Uint8List?> imageBytesFuture;
final bool isRealTwonly;
final int maxShowTime;
final Contact? preselectedUser;
@override
State<ShareImageView> createState() => _ShareImageView();
@ -47,8 +47,8 @@ class _ShareImageView extends State<ShareImageView> {
void initState() {
super.initState();
if (globalSendNextMediaToUser != null) {
_selectedUserIds.add(globalSendNextMediaToUser!.userId);
if (widget.preselectedUser != null) {
_selectedUserIds.add(widget.preselectedUser!.userId);
}
Stream<List<Contact>> allContacts =
@ -233,8 +233,12 @@ class _ShareImageView extends State<ShareImageView> {
widget.maxShowTime,
);
if (context.mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
globalUpdateOfHomeViewPageIndex(1);
if (widget.preselectedUser != null) {
Navigator.pop(context, true);
} else {
Navigator.popUntil(context, (route) => route.isFirst);
globalUpdateOfHomeViewPageIndex(1);
}
}
},
style: ButtonStyle(

View file

@ -17,11 +17,10 @@ import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/media_viewer_view.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/contact/contact_view.dart';
import 'package:twonly/src/views/home_view.dart';
class ChatListEntry extends StatelessWidget {
const ChatListEntry(
@ -442,10 +441,11 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
: IconButton(
icon: FaIcon(FontAwesomeIcons.camera),
onPressed: () {
globalSendNextMediaToUser = user;
globalUpdateOfHomeViewPageIndex(0);
Navigator.popUntil(
context, (route) => route.isFirst);
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.contact);
},
));
},
)
],

View file

@ -16,11 +16,10 @@ import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
import 'package:twonly/src/views/chats/media_viewer_view.dart';
import 'package:twonly/src/views/chats/start_new_chat.dart';
import 'package:twonly/src/views/home_view.dart';
import 'package:twonly/src/views/settings/settings_main_view.dart';
import 'package:twonly/src/views/chats/search_username_view.dart';
import 'package:flutter/material.dart';
@ -297,10 +296,24 @@ class _UserListItem extends State<UserListItem> {
],
),
leading: ContactAvatar(contact: widget.user),
trailing: IconButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.user);
},
));
},
icon: FaIcon(FontAwesomeIcons.camera,
color: context.color.outline.withAlpha(150)),
),
onTap: () {
if (currentMessage == null) {
globalSendNextMediaToUser = widget.user;
globalUpdateOfHomeViewPageIndex(0);
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.user);
},
));
return;
}
List<Message> msgs = previewMessages

View file

@ -15,9 +15,8 @@ import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
import 'package:twonly/src/views/home_view.dart';
final _noScreenshot = NoScreenshot.instance;
@ -490,9 +489,11 @@ class _MediaViewerViewState extends State<MediaViewerView> {
IconButton.outlined(
icon: FaIcon(FontAwesomeIcons.camera),
onPressed: () async {
globalSendNextMediaToUser = widget.contact;
globalUpdateOfHomeViewPageIndex(0);
Navigator.popUntil(context, (route) => route.isFirst);
await Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.contact);
},
));
},
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(

View file

@ -5,7 +5,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/user_context_menu.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';

View file

@ -3,7 +3,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'camera_to_share/camera_preview_view.dart';
import 'chats/chat_list_view.dart';
import 'package:flutter/material.dart';
@ -31,9 +30,6 @@ class HomeViewState extends State<HomeView> {
activePageIdx = widget.initialPage;
homeViewPageController = PageController(initialPage: widget.initialPage);
globalUpdateOfHomeViewPageIndex = (index) {
if (index == 1) {
globalSendNextMediaToUser = null;
}
homeViewPageController.jumpToPage(index);
setState(() {
activePageIdx = index;
@ -61,8 +57,6 @@ class HomeViewState extends State<HomeView> {
@override
void dispose() {
selectNotificationStream.close();
// disable globalCallbacks to the flutter tree
globalUpdateOfHomeViewPageIndex = (a) {};
super.dispose();
}
@ -74,9 +68,6 @@ class HomeViewState extends State<HomeView> {
body: PageView(
controller: homeViewPageController,
onPageChanged: (index) {
if (index == 1) {
globalSendNextMediaToUser = null;
}
activePageIdx = index;
setState(() {});
},
@ -103,9 +94,6 @@ class HomeViewState extends State<HomeView> {
],
onTap: (int index) {
activePageIdx = index;
if (index == 1) {
globalSendNextMediaToUser = null;
}
setState(() {
homeViewPageController.animateToPage(
index,