mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 03:42:13 +00:00
split up the camera preview controller
This commit is contained in:
parent
1cee77cd97
commit
0c8bd0a7b4
5 changed files with 439 additions and 346 deletions
|
|
@ -0,0 +1,146 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/zoom_selector.dart';
|
||||||
|
|
||||||
|
class CameraBottomControls extends StatelessWidget {
|
||||||
|
const CameraBottomControls({
|
||||||
|
required this.mainController,
|
||||||
|
required this.isVideoRecording,
|
||||||
|
required this.isFront,
|
||||||
|
required this.keyTriggerButton,
|
||||||
|
required this.onTakePicture,
|
||||||
|
required this.onPressSideButtonLeft,
|
||||||
|
required this.onPressSideButtonRight,
|
||||||
|
required this.updateScaleFactor,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final MainCameraController mainController;
|
||||||
|
final bool isVideoRecording;
|
||||||
|
final bool isFront;
|
||||||
|
final GlobalKey keyTriggerButton;
|
||||||
|
final VoidCallback onTakePicture;
|
||||||
|
final VoidCallback onPressSideButtonLeft;
|
||||||
|
final VoidCallback onPressSideButtonRight;
|
||||||
|
final Future<void> Function(double) updateScaleFactor;
|
||||||
|
|
||||||
|
MainCameraController get mc => mainController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
bottom: 30,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (mc.cameraController!.value.isInitialized &&
|
||||||
|
mc.selectedCameraDetails.isZoomAble &&
|
||||||
|
!isVideoRecording)
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: CameraZoomButtons(
|
||||||
|
key: mc.zoomButtonKey,
|
||||||
|
scaleFactor: mc.selectedCameraDetails.scaleFactor,
|
||||||
|
updateScaleFactor: updateScaleFactor,
|
||||||
|
selectCamera: mc.selectCamera,
|
||||||
|
selectedCameraDetails: mc.selectedCameraDetails,
|
||||||
|
controller: mc.cameraController!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (!isVideoRecording) _buildSideButtonLeft(),
|
||||||
|
_buildShutterButton(),
|
||||||
|
if (!isVideoRecording)
|
||||||
|
if (isFront)
|
||||||
|
_buildSideButtonRight()
|
||||||
|
else
|
||||||
|
const SizedBox(width: 80),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSideButtonLeft() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPressSideButtonLeft,
|
||||||
|
child: Align(
|
||||||
|
child: Container(
|
||||||
|
height: 50,
|
||||||
|
width: 80,
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: Center(
|
||||||
|
child: FaIcon(
|
||||||
|
mc.isSelectingFaceFilters
|
||||||
|
? mc.currentFilterType.index == 1
|
||||||
|
? FontAwesomeIcons.xmark
|
||||||
|
: FontAwesomeIcons.arrowLeft
|
||||||
|
: FontAwesomeIcons.photoFilm,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildShutterButton() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTakePicture,
|
||||||
|
key: keyTriggerButton,
|
||||||
|
child: Align(
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
width: 7,
|
||||||
|
color: isVideoRecording ? Colors.red : Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: mc.currentFilterType.preview,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSideButtonRight() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPressSideButtonRight,
|
||||||
|
child: Align(
|
||||||
|
child: Container(
|
||||||
|
height: 50,
|
||||||
|
width: 80,
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: Center(
|
||||||
|
child: FaIcon(
|
||||||
|
mc.isSelectingFaceFilters
|
||||||
|
? mc.currentFilterType.index ==
|
||||||
|
FaceFilterType.values.length - 1
|
||||||
|
? FontAwesomeIcons.xmark
|
||||||
|
: FontAwesomeIcons.arrowRight
|
||||||
|
: FontAwesomeIcons.faceGrinTongueSquint,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/utils/qr.utils.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class CameraScannedOverlay extends StatelessWidget {
|
||||||
|
const CameraScannedOverlay({
|
||||||
|
required this.mainController,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final MainCameraController mainController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
right: 8,
|
||||||
|
top: 170,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
...mainController.scannedNewProfiles.values.map(
|
||||||
|
(c) => _buildScannedProfileTile(context, c),
|
||||||
|
),
|
||||||
|
...mainController.contactsVerified.values.map(
|
||||||
|
(c) => _buildVerifiedContactTile(context, c),
|
||||||
|
),
|
||||||
|
if (mainController.scannedUrl != null)
|
||||||
|
_buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildScannedProfileTile(BuildContext context, ScannedNewProfile c) {
|
||||||
|
if (c.isLoading) return Container();
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
c.isLoading = true;
|
||||||
|
mainController.setState();
|
||||||
|
if (await addNewContactFromPublicProfile(c.profile) &&
|
||||||
|
context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
context.lang.requestedUserToastText(c.profile.username),
|
||||||
|
),
|
||||||
|
duration: const Duration(seconds: 8),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: context.color.surfaceContainer,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(c.profile.username),
|
||||||
|
Expanded(child: Container()),
|
||||||
|
if (c.isLoading)
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ColoredBox(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: FaIcon(
|
||||||
|
FontAwesomeIcons.userPlus,
|
||||||
|
color: isDarkMode(context) ? Colors.white : Colors.black,
|
||||||
|
size: 17,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVerifiedContactTile(
|
||||||
|
BuildContext context,
|
||||||
|
ScannedVerifiedContact c,
|
||||||
|
) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: context.color.surfaceContainer,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AvatarIcon(
|
||||||
|
contactId: c.contact.userId,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
getContactDisplayName(c.contact, maxLength: 13),
|
||||||
|
),
|
||||||
|
Expanded(child: Container()),
|
||||||
|
ColoredBox(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 30,
|
||||||
|
child: Lottie.asset(
|
||||||
|
c.verificationOk
|
||||||
|
? 'assets/animations/success.lottie'
|
||||||
|
: 'assets/animations/failed.lottie',
|
||||||
|
repeat: false,
|
||||||
|
onLoaded: (p0) {
|
||||||
|
Future.delayed(const Duration(seconds: 4), () {
|
||||||
|
mainController.setState();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildScannedUrlTile(BuildContext context, String url) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString(url);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: context.color.surfaceContainer,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
substringBy(url, 25),
|
||||||
|
style: const TextStyle(fontSize: 8),
|
||||||
|
),
|
||||||
|
Expanded(child: Container()),
|
||||||
|
Expanded(child: Container()),
|
||||||
|
ColoredBox(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: FaIcon(
|
||||||
|
FontAwesomeIcons.shareFromSquare,
|
||||||
|
color: isDarkMode(context) ? Colors.white : Colors.black,
|
||||||
|
size: 17,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Full-screen white overlay used as a "flash" when taking selfies
|
||||||
|
/// with the front camera and flash enabled.
|
||||||
|
class CameraSelfieFlash extends StatelessWidget {
|
||||||
|
const CameraSelfieFlash({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned.fill(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||||
|
|
||||||
|
class CameraTopActions extends StatelessWidget {
|
||||||
|
const CameraTopActions({
|
||||||
|
required this.selectedCameraDetails,
|
||||||
|
required this.hasAudioPermission,
|
||||||
|
required this.onSwitchCamera,
|
||||||
|
required this.onToggleFlash,
|
||||||
|
required this.onRequestMicrophone,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SelectedCameraDetails selectedCameraDetails;
|
||||||
|
final bool hasAudioPermission;
|
||||||
|
final VoidCallback onSwitchCamera;
|
||||||
|
final VoidCallback onToggleFlash;
|
||||||
|
final VoidCallback onRequestMicrophone;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
right: 5,
|
||||||
|
top: 0,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
ActionButton(
|
||||||
|
Icons.repeat_rounded,
|
||||||
|
tooltipText: context.lang.switchFrontAndBackCamera,
|
||||||
|
onPressed: onSwitchCamera,
|
||||||
|
),
|
||||||
|
ActionButton(
|
||||||
|
selectedCameraDetails.isFlashOn
|
||||||
|
? Icons.flash_on_rounded
|
||||||
|
: Icons.flash_off_rounded,
|
||||||
|
tooltipText: context.lang.toggleFlashLight,
|
||||||
|
color: selectedCameraDetails.isFlashOn
|
||||||
|
? Colors.white
|
||||||
|
: Colors.white.withAlpha(160),
|
||||||
|
onPressed: onToggleFlash,
|
||||||
|
),
|
||||||
|
if (!hasAudioPermission)
|
||||||
|
ActionButton(
|
||||||
|
Icons.mic_off_rounded,
|
||||||
|
color: Colors.white.withAlpha(160),
|
||||||
|
tooltipText: 'Allow microphone access for video recording.',
|
||||||
|
onPressed: onRequestMicrophone,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,32 +10,30 @@ import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.da
|
||||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.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/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.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/utils/qr.utils.dart';
|
|
||||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
|
||||||
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
|
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
|
||||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||||
import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart';
|
import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_bottom_controls.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_scanned_overlay.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_selfie_flash.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/camera_top_actions.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/permissions_view.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/permissions_view.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/send_to.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/send_to.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/video_recording_time.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/video_recording_time.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/zoom_selector.dart';
|
|
||||||
import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart';
|
import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||||
import 'package:twonly/src/visual/views/home.view.dart';
|
import 'package:twonly/src/visual/views/home.view.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
int maxVideoRecordingTime = 60;
|
int maxVideoRecordingTime = 60;
|
||||||
|
|
||||||
|
|
@ -140,7 +138,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (widget.isVisible) {
|
if (widget.isVisible) {
|
||||||
initVolumeControl();
|
initVolumeControl();
|
||||||
} else {
|
} else {
|
||||||
deInitVolumeControl();
|
_deInitVolumeControl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +146,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_videoRecordingTimer?.cancel();
|
_videoRecordingTimer?.cancel();
|
||||||
deInitVolumeControl();
|
_deInitVolumeControl();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +158,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
FlutterVolumeController.addListener(
|
FlutterVolumeController.addListener(
|
||||||
(volume) async {
|
(volume) async {
|
||||||
if (!widget.isVisible) {
|
if (!widget.isVisible) {
|
||||||
await deInitVolumeControl();
|
await _deInitVolumeControl();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (startedVolume == null) {
|
if (startedVolume == null) {
|
||||||
|
|
@ -188,7 +186,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (widget.isVisible) {
|
if (widget.isVisible) {
|
||||||
takePicture();
|
takePicture();
|
||||||
} else {
|
} else {
|
||||||
deInitVolumeControl();
|
_deInitVolumeControl();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -196,7 +194,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deInitVolumeControl() async {
|
Future<void> _deInitVolumeControl() async {
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
await FlutterVolumeController.updateShowSystemUI(true);
|
await FlutterVolumeController.updateShowSystemUI(true);
|
||||||
FlutterVolumeController.removeListener();
|
FlutterVolumeController.removeListener();
|
||||||
|
|
@ -341,7 +339,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
// unawaited(mediaFileService.compressMedia());
|
// unawaited(mediaFileService.compressMedia());
|
||||||
}
|
}
|
||||||
|
|
||||||
await deInitVolumeControl();
|
await _deInitVolumeControl();
|
||||||
if (!mounted) return true;
|
if (!mounted) return true;
|
||||||
|
|
||||||
final shouldReturn =
|
final shouldReturn =
|
||||||
|
|
@ -599,6 +597,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mc.cameraController == null) {
|
mc.cameraController == null) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
// TODO: STREAM BUILDER FOR GlOBAL USER CHANGES
|
||||||
return MediaViewSizingHelper(
|
return MediaViewSizingHelper(
|
||||||
requiredHeight: 0,
|
requiredHeight: 0,
|
||||||
additionalPadding: 59,
|
additionalPadding: 59,
|
||||||
|
|
@ -670,169 +669,37 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (!mc.isSharePreviewIsShown &&
|
if (!mc.isSharePreviewIsShown &&
|
||||||
!mc.isVideoRecording &&
|
!mc.isVideoRecording &&
|
||||||
!widget.hideControllers)
|
!widget.hideControllers)
|
||||||
Positioned(
|
CameraTopActions(
|
||||||
right: 5,
|
selectedCameraDetails: mc.selectedCameraDetails,
|
||||||
top: 0,
|
hasAudioPermission: _hasAudioPermission,
|
||||||
child: Container(
|
onSwitchCamera: () async {
|
||||||
alignment: Alignment.bottomCenter,
|
await mc.selectCamera(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
(mc.selectedCameraDetails.cameraId + 1) % 2,
|
||||||
child: SafeArea(
|
false,
|
||||||
child: Column(
|
);
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
},
|
||||||
children: <Widget>[
|
onToggleFlash: () async {
|
||||||
ActionButton(
|
if (mc.selectedCameraDetails.isFlashOn) {
|
||||||
Icons.repeat_rounded,
|
await mc.cameraController?.setFlashMode(FlashMode.off);
|
||||||
tooltipText: context.lang.switchFrontAndBackCamera,
|
mc.selectedCameraDetails.isFlashOn = false;
|
||||||
onPressed: () async {
|
} else {
|
||||||
await mc.selectCamera(
|
await mc.cameraController?.setFlashMode(FlashMode.always);
|
||||||
(mc.selectedCameraDetails.cameraId + 1) % 2,
|
mc.selectedCameraDetails.isFlashOn = true;
|
||||||
false,
|
}
|
||||||
);
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
onRequestMicrophone: requestMicrophonePermission,
|
||||||
ActionButton(
|
|
||||||
mc.selectedCameraDetails.isFlashOn
|
|
||||||
? Icons.flash_on_rounded
|
|
||||||
: Icons.flash_off_rounded,
|
|
||||||
tooltipText: context.lang.toggleFlashLight,
|
|
||||||
color: mc.selectedCameraDetails.isFlashOn
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(160),
|
|
||||||
onPressed: () async {
|
|
||||||
if (mc.selectedCameraDetails.isFlashOn) {
|
|
||||||
await mc.cameraController?.setFlashMode(
|
|
||||||
FlashMode.off,
|
|
||||||
);
|
|
||||||
mc.selectedCameraDetails.isFlashOn = false;
|
|
||||||
} else {
|
|
||||||
await mc.cameraController?.setFlashMode(
|
|
||||||
FlashMode.always,
|
|
||||||
);
|
|
||||||
mc.selectedCameraDetails.isFlashOn = true;
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (!_hasAudioPermission)
|
|
||||||
ActionButton(
|
|
||||||
Icons.mic_off_rounded,
|
|
||||||
color: Colors.white.withAlpha(160),
|
|
||||||
tooltipText:
|
|
||||||
'Allow microphone access for video recording.',
|
|
||||||
onPressed: requestMicrophonePermission,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (!mc.isSharePreviewIsShown && !widget.hideControllers)
|
if (!mc.isSharePreviewIsShown && !widget.hideControllers)
|
||||||
Positioned(
|
CameraBottomControls(
|
||||||
bottom: 30,
|
mainController: mc,
|
||||||
left: 0,
|
isVideoRecording: mc.isVideoRecording,
|
||||||
right: 0,
|
isFront: isFront,
|
||||||
child: Align(
|
keyTriggerButton: keyTriggerButton,
|
||||||
alignment: Alignment.bottomCenter,
|
onTakePicture: takePicture,
|
||||||
child: Column(
|
onPressSideButtonLeft: pressSideButtonLeft,
|
||||||
children: [
|
onPressSideButtonRight: pressSideButtonRight,
|
||||||
if (mc.cameraController!.value.isInitialized &&
|
updateScaleFactor: updateScaleFactor,
|
||||||
mc.selectedCameraDetails.isZoomAble &&
|
|
||||||
!mc.isVideoRecording)
|
|
||||||
SizedBox(
|
|
||||||
width: 120,
|
|
||||||
child: CameraZoomButtons(
|
|
||||||
key: mc.zoomButtonKey,
|
|
||||||
scaleFactor: mc.selectedCameraDetails.scaleFactor,
|
|
||||||
updateScaleFactor: updateScaleFactor,
|
|
||||||
selectCamera: mc.selectCamera,
|
|
||||||
selectedCameraDetails: mc.selectedCameraDetails,
|
|
||||||
controller: mc.cameraController!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
if (!mc.isVideoRecording)
|
|
||||||
GestureDetector(
|
|
||||||
onTap: pressSideButtonLeft,
|
|
||||||
child: Align(
|
|
||||||
child: Container(
|
|
||||||
height: 50,
|
|
||||||
width: 80,
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: Center(
|
|
||||||
child: FaIcon(
|
|
||||||
mc.isSelectingFaceFilters
|
|
||||||
? mc.currentFilterType.index == 1
|
|
||||||
? FontAwesomeIcons.xmark
|
|
||||||
: FontAwesomeIcons.arrowLeft
|
|
||||||
: FontAwesomeIcons.photoFilm,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 25,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: takePicture,
|
|
||||||
// onLongPress: startVideoRecording,
|
|
||||||
key: keyTriggerButton,
|
|
||||||
child: Align(
|
|
||||||
child: Container(
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
width: 7,
|
|
||||||
color: mc.isVideoRecording
|
|
||||||
? Colors.red
|
|
||||||
: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: mc.currentFilterType.preview,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!mc.isVideoRecording)
|
|
||||||
if (isFront)
|
|
||||||
GestureDetector(
|
|
||||||
onTap: pressSideButtonRight,
|
|
||||||
child: Align(
|
|
||||||
child: Container(
|
|
||||||
height: 50,
|
|
||||||
width: 80,
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: Center(
|
|
||||||
child: FaIcon(
|
|
||||||
mc.isSelectingFaceFilters
|
|
||||||
? mc.currentFilterType.index ==
|
|
||||||
FaceFilterType
|
|
||||||
.values
|
|
||||||
.length -
|
|
||||||
1
|
|
||||||
? FontAwesomeIcons.xmark
|
|
||||||
: FontAwesomeIcons.arrowRight
|
|
||||||
: FontAwesomeIcons
|
|
||||||
.faceGrinTongueSquint,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 25,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
const SizedBox(width: 80),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
VideoRecordingTimer(
|
VideoRecordingTimer(
|
||||||
videoRecordingStarted: _videoRecordingStarted,
|
videoRecordingStarted: _videoRecordingStarted,
|
||||||
|
|
@ -851,179 +718,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_showSelfieFlash)
|
if (_showSelfieFlash) const CameraSelfieFlash(),
|
||||||
Positioned.fill(
|
CameraScannedOverlay(mainController: mc),
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(22),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 8,
|
|
||||||
top: 170,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 200,
|
|
||||||
width: 150,
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
...widget.mainCameraController.scannedNewProfiles.values
|
|
||||||
.map(
|
|
||||||
(c) {
|
|
||||||
if (c.isLoading) return Container();
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
c.isLoading = true;
|
|
||||||
widget.mainCameraController.setState();
|
|
||||||
if (await addNewContactFromPublicProfile(
|
|
||||||
c.profile,
|
|
||||||
) &&
|
|
||||||
context.mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
context.lang.requestedUserToastText(
|
|
||||||
c.profile.username,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 8),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: context.color.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(c.profile.username),
|
|
||||||
Expanded(child: Container()),
|
|
||||||
if (c.isLoading)
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: FaIcon(
|
|
||||||
FontAwesomeIcons.userPlus,
|
|
||||||
color: isDarkMode(context)
|
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
size: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
...widget.mainCameraController.contactsVerified.values.map(
|
|
||||||
(c) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: context.color.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
AvatarIcon(
|
|
||||||
contactId: c.contact.userId,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(
|
|
||||||
getContactDisplayName(
|
|
||||||
c.contact,
|
|
||||||
maxLength: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 30,
|
|
||||||
child: Lottie.asset(
|
|
||||||
c.verificationOk
|
|
||||||
? 'assets/animations/success.lottie'
|
|
||||||
: 'assets/animations/failed.lottie',
|
|
||||||
repeat: false,
|
|
||||||
onLoaded: (p0) {
|
|
||||||
Future.delayed(
|
|
||||||
const Duration(seconds: 4),
|
|
||||||
() {
|
|
||||||
widget.mainCameraController
|
|
||||||
.setState();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (widget.mainCameraController.scannedUrl != null)
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
launchUrlString(
|
|
||||||
widget.mainCameraController.scannedUrl!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: context.color.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
substringBy(
|
|
||||||
widget.mainCameraController.scannedUrl!,
|
|
||||||
25,
|
|
||||||
),
|
|
||||||
style: const TextStyle(fontSize: 8),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
Expanded(child: Container()),
|
|
||||||
ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: FaIcon(
|
|
||||||
FontAwesomeIcons.shareFromSquare,
|
|
||||||
color: isDarkMode(context)
|
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
size: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue