diff --git a/android/app/src/main/kotlin/eu/twonly/MainActivity.kt b/android/app/src/main/kotlin/eu/twonly/MainActivity.kt index ce8739d..bb90f69 100644 --- a/android/app/src/main/kotlin/eu/twonly/MainActivity.kt +++ b/android/app/src/main/kotlin/eu/twonly/MainActivity.kt @@ -1,5 +1,21 @@ package eu.twonly import io.flutter.embedding.android.FlutterFragmentActivity +import android.view.KeyEvent +import dev.darttools.flutter_android_volume_keydown.FlutterAndroidVolumeKeydownPlugin.eventSink +import android.view.KeyEvent.KEYCODE_VOLUME_DOWN +import android.view.KeyEvent.KEYCODE_VOLUME_UP -class MainActivity: FlutterFragmentActivity() +class MainActivity : FlutterFragmentActivity() { + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KEYCODE_VOLUME_DOWN && eventSink != null) { + eventSink!!.success(true) + return true + } + if (keyCode == KEYCODE_VOLUME_UP && eventSink != null) { + eventSink!!.success(false) + return true + } + return super.onKeyDown(keyCode, event) + } +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 440ffc5..3c00a85 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,6 +9,8 @@ PODS: - Flutter - device_info_plus (0.0.1): - Flutter + - emoji_picker_flutter (0.0.1): + - Flutter - ffmpeg_kit_flutter_new (1.0.0): - ffmpeg_kit_flutter_new/full-gpl (= 1.0.0) - Flutter @@ -82,6 +84,8 @@ PODS: - flutter_secure_storage_darwin (10.0.0): - Flutter - FlutterMacOS + - flutter_volume_controller (0.0.1): + - Flutter - flutter_zxing (0.0.1): - Flutter - gal (1.0.0): @@ -248,6 +252,7 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - cryptography_flutter_plus (from `.symlinks/plugins/cryptography_flutter_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) - ffmpeg_kit_flutter_new (from `.symlinks/plugins/ffmpeg_kit_flutter_new/ios`) - Firebase - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -260,6 +265,7 @@ DEPENDENCIES: - flutter_keyboard_visibility_temp_fork (from `.symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - flutter_zxing (from `.symlinks/plugins/flutter_zxing/ios`) - gal (from `.symlinks/plugins/gal/darwin`) - GoogleUtilities @@ -311,6 +317,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cryptography_flutter_plus/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" + emoji_picker_flutter: + :path: ".symlinks/plugins/emoji_picker_flutter/ios" ffmpeg_kit_flutter_new: :path: ".symlinks/plugins/ffmpeg_kit_flutter_new/ios" firebase_core: @@ -327,6 +335,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage_darwin: :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" + flutter_volume_controller: + :path: ".symlinks/plugins/flutter_volume_controller/ios" flutter_zxing: :path: ".symlinks/plugins/flutter_zxing/ios" gal: @@ -364,6 +374,7 @@ SPEC CHECKSUMS: connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e firebase_core: 744984dbbed8b3036abf34f0b98d80f130a7e464 @@ -378,6 +389,7 @@ SPEC CHECKSUMS: flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 + flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93 gal: baecd024ebfd13c441269ca7404792a7152fde89 GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1 diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 1d85e4f..58c37f8 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -32,19 +32,19 @@ Future handleServerMessage(server.ServerToClient msg) async { final ok = client.Response_Ok()..none = true; var response = client.Response()..ok = ok; - // try { - if (msg.v0.hasRequestNewPreKeys()) { - response = await handleRequestNewPreKey(); - } else if (msg.v0.hasNewMessage()) { - final body = Uint8List.fromList(msg.v0.newMessage.body); - final fromUserId = msg.v0.newMessage.fromUserId.toInt(); - await handleClient2ClientMessage(fromUserId, body); - } else { - Log.error('Unknown server message: $msg'); + try { + if (msg.v0.hasRequestNewPreKeys()) { + response = await handleRequestNewPreKey(); + } else if (msg.v0.hasNewMessage()) { + final body = Uint8List.fromList(msg.v0.newMessage.body); + final fromUserId = msg.v0.newMessage.fromUserId.toInt(); + await handleClient2ClientMessage(fromUserId, body); + } else { + Log.error('Unknown server message: $msg'); + } + } catch (e) { + Log.error(e); } - // } catch (e) { - // Log.error(e); - // } final v0 = client.V0() ..seq = msg.v0.seq diff --git a/lib/src/views/camera/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_controller_view.dart index 176d6b3..5a4a93c 100644 --- a/lib/src/views/camera/camera_preview_controller_view.dart +++ b/lib/src/views/camera/camera_preview_controller_view.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.dart'; +import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -88,6 +90,7 @@ class CameraPreviewControllerView extends StatelessWidget { required this.selectCamera, required this.selectedCameraDetails, required this.screenshotController, + required this.isVisible, super.key, this.sendToGroup, }); @@ -97,6 +100,7 @@ class CameraPreviewControllerView extends StatelessWidget { final CameraController? cameraController; final SelectedCameraDetails selectedCameraDetails; final ScreenshotController screenshotController; + final bool isVisible; @override Widget build(BuildContext context) { @@ -111,6 +115,7 @@ class CameraPreviewControllerView extends StatelessWidget { cameraController: cameraController, selectedCameraDetails: selectedCameraDetails, screenshotController: screenshotController, + isVisible: isVisible, ); } else { return PermissionHandlerView( @@ -133,6 +138,7 @@ class CameraPreviewView extends StatefulWidget { required this.cameraController, required this.selectedCameraDetails, required this.screenshotController, + required this.isVisible, super.key, this.sendToGroup, }); @@ -144,6 +150,7 @@ class CameraPreviewView extends StatefulWidget { final CameraController? cameraController; final SelectedCameraDetails selectedCameraDetails; final ScreenshotController screenshotController; + final bool isVisible; @override State createState() => _CameraPreviewViewState(); @@ -164,12 +171,71 @@ class _CameraPreviewViewState extends State { final GlobalKey keyTriggerButton = GlobalKey(); final GlobalKey navigatorKey = GlobalKey(); + StreamSubscription? androidVolumeDownSub; + @override void initState() { super.initState(); + initVolumeControl(); initAsync(); } + @override + void didUpdateWidget(covariant CameraPreviewView oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.isVisible != widget.isVisible) { + if (widget.isVisible) { + initVolumeControl(); + } else { + deInitVolumeControl(); + } + } + } + + @override + void dispose() { + _videoRecordingTimer?.cancel(); + deInitVolumeControl(); + super.dispose(); + } + + Future initVolumeControl() async { + if (Platform.isIOS) { + await FlutterVolumeController.updateShowSystemUI(false); + double? startedVolume; + + FlutterVolumeController.addListener( + (volume) async { + if (startedVolume == null) { + startedVolume = volume; + return; + } + if (startedVolume == volume) { + return; + } + // reset the volume back to the original value + await FlutterVolumeController.setVolume(startedVolume!); + await takePicture(); + }, + ); + } + if (Platform.isAndroid) { + androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((event) { + takePicture(); + }); + } + } + + Future deInitVolumeControl() async { + if (Platform.isIOS) { + await FlutterVolumeController.updateShowSystemUI(true); + FlutterVolumeController.removeListener(); + } + if (Platform.isAndroid) { + await androidVolumeDownSub?.cancel(); + } + } + Future initAsync() async { _hasAudioPermission = await Permission.microphone.isGranted; @@ -184,12 +250,6 @@ class _CameraPreviewViewState extends State { setState(() {}); } - @override - void dispose() { - _videoRecordingTimer?.cancel(); - super.dispose(); - } - Future requestMicrophonePermission() async { final statuses = await [ Permission.microphone, @@ -309,6 +369,9 @@ class _CameraPreviewViewState extends State { // unawaited(mediaFileService.compressMedia()); } + await deInitVolumeControl(); + if (!mounted) return true; + final shouldReturn = await Navigator.push( context, PageRouteBuilder( @@ -333,11 +396,12 @@ class _CameraPreviewViewState extends State { }); } if (!mounted) return true; + await initVolumeControl(); // shouldReturn is null when the user used the back button if (shouldReturn != null && shouldReturn) { if (widget.sendToGroup == null) { globalUpdateOfHomeViewPageIndex(0); - } else { + } else if (mounted) { Navigator.pop(context); } return true; diff --git a/lib/src/views/camera/camera_send_to_view.dart b/lib/src/views/camera/camera_send_to_view.dart index cb36407..578c955 100644 --- a/lib/src/views/camera/camera_send_to_view.dart +++ b/lib/src/views/camera/camera_send_to_view.dart @@ -79,6 +79,7 @@ class CameraSendToViewState extends State { cameraController: cameraController, selectedCameraDetails: selectedCameraDetails, screenshotController: screenshotController, + isVisible: true, ), ], ), diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index f6e9593..da3bc96 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -474,20 +474,63 @@ class _ChatMessagesViewState extends State { child: Row( children: [ Expanded( - child: TextField( - controller: newMessageController, - focusNode: textFieldFocus, - keyboardType: TextInputType.multiline, - maxLines: 4, - minLines: 1, - onChanged: (value) { - currentInputText = value; - setState(() {}); - }, - onSubmitted: (_) { - _sendMessage(); - }, - decoration: inputTextMessageDeco(context), + child: Container( + color: Colors.grey, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + ), + child: Row( + children: [ + const FaIcon(FontAwesomeIcons.faceSmile), + Expanded( + child: TextField( + controller: newMessageController, + focusNode: textFieldFocus, + keyboardType: TextInputType.multiline, + maxLines: 4, + minLines: 1, + onChanged: (value) { + currentInputText = value; + setState(() {}); + }, + onSubmitted: (_) { + _sendMessage(); + }, + decoration: InputDecoration( + hintText: context.lang.chatListDetailInput, + // contentPadding: const EdgeInsets.symmetric( + // horizontal: 20, + // vertical: 10, + // ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: Theme.of(context) + .colorScheme + .primary, + width: 2, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide( + color: Colors.grey, + width: 2, + ), + ), + ), + ), + ), + ], + ), ), ), if (currentInputText != '') diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 7571421..ee2907d 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -193,6 +193,8 @@ class HomeViewState extends State { screenshotController: screenshotController, selectedCameraDetails: selectedCameraDetails, selectCamera: selectCamera, + isVisible: + ((1 - (offsetRatio * 4) % 1) == 1) && activePageIdx == 1, ), ), ), diff --git a/pubspec.lock b/pubspec.lock index eb37e53..b1d0c98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -386,6 +386,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.1" + emoji_picker_flutter: + dependency: "direct main" + description: + name: emoji_picker_flutter + sha256: "9a44c102079891ea5877f78c70f2e3c6e9df7b7fe0a01757d31f1046eeaa016d" + url: "https://pub.dev" + source: hosted + version: "4.3.0" fake_async: dependency: transitive description: @@ -519,6 +527,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_android_volume_keydown: + dependency: "direct main" + description: + name: flutter_android_volume_keydown + sha256: bf7fed0be85541b939d9deb97b375cb12e6e703aa013754441318b0b9014e711 + url: "https://pub.dev" + source: hosted + version: "1.0.1" flutter_cache_manager: dependency: transitive description: @@ -738,6 +754,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_volume_controller: + dependency: "direct main" + description: + name: flutter_volume_controller + sha256: "22edb0993ad03ecbc8d1164daeb5b39d798d409625db692675a86889403b1532" + url: "https://pub.dev" + source: hosted + version: "1.3.4" flutter_web_plugins: dependency: transitive description: flutter @@ -1675,6 +1699,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2984317..352eb50 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,12 +20,14 @@ dependencies: device_info_plus: ^12.1.0 drift: ^2.25.1 drift_flutter: ^0.2.4 + emoji_picker_flutter: ^4.3.0 ffmpeg_kit_flutter_new: ^4.1.0 firebase_core: ^4.2.0 firebase_messaging: ^16.0.3 fixnum: ^1.1.1 flutter: sdk: flutter + flutter_android_volume_keydown: ^1.0.1 flutter_image_compress: ^2.4.0 flutter_local_notifications: ^19.1.0 flutter_localizations: @@ -36,6 +38,7 @@ dependencies: ref: 71b75a36f35f2ce945998e20c6c6aa1820babfc6 # from develop path: flutter_secure_storage/ flutter_svg: ^2.0.17 + flutter_volume_controller: ^1.3.4 flutter_zxing: path: ./dependencies/flutter_zxing font_awesome_flutter: ^10.10.0