split up the camera preview controller

This commit is contained in:
otsmr 2026-04-22 20:31:09 +02:00
parent 1cee77cd97
commit 0c8bd0a7b4
5 changed files with 439 additions and 346 deletions

View file

@ -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,
),
),
),
),
);
}
}

View file

@ -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,
),
),
],
),
),
);
}
}

View file

@ -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,
),
),
);
}
}

View file

@ -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,
),
],
),
),
),
);
}
}

View file

@ -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,
padding: const EdgeInsets.symmetric(vertical: 16),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ActionButton(
Icons.repeat_rounded,
tooltipText: context.lang.switchFrontAndBackCamera,
onPressed: () async {
await mc.selectCamera( await mc.selectCamera(
(mc.selectedCameraDetails.cameraId + 1) % 2, (mc.selectedCameraDetails.cameraId + 1) % 2,
false, false,
); );
}, },
), onToggleFlash: () async {
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) { if (mc.selectedCameraDetails.isFlashOn) {
await mc.cameraController?.setFlashMode( await mc.cameraController?.setFlashMode(FlashMode.off);
FlashMode.off,
);
mc.selectedCameraDetails.isFlashOn = false; mc.selectedCameraDetails.isFlashOn = false;
} else { } else {
await mc.cameraController?.setFlashMode( await mc.cameraController?.setFlashMode(FlashMode.always);
FlashMode.always,
);
mc.selectedCameraDetails.isFlashOn = true; mc.selectedCameraDetails.isFlashOn = true;
} }
setState(() {}); setState(() {});
}, },
), onRequestMicrophone: requestMicrophonePermission,
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 &&
mc.selectedCameraDetails.isZoomAble &&
!mc.isVideoRecording)
SizedBox(
width: 120,
child: CameraZoomButtons(
key: mc.zoomButtonKey,
scaleFactor: mc.selectedCameraDetails.scaleFactor,
updateScaleFactor: updateScaleFactor, 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,
),
),
],
),
),
),
],
),
),
),
], ],
), ),
), ),