improved navigation page view

This commit is contained in:
otsmr 2025-05-29 15:46:42 +02:00
parent 024def010c
commit 8e1c6a8fab
13 changed files with 982 additions and 873 deletions

View file

@ -0,0 +1,54 @@
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/home_view.dart';
class CameraPreviewWidget extends StatefulWidget {
const CameraPreviewWidget({
super.key,
});
@override
State<CameraPreviewWidget> createState() => _CameraPreviewWidgetState();
}
class _CameraPreviewWidgetState extends State<CameraPreviewWidget> {
@override
Widget build(BuildContext context) {
if (HomeViewState.cameraController == null ||
!HomeViewState.cameraController!.value.isInitialized) {
return Container();
}
bool isFront = HomeViewState.cameraController?.description.lensDirection ==
CameraLensDirection.front;
return Positioned.fill(
child: MediaViewSizing(
child: Screenshot(
controller: HomeViewState.screenshotController,
child: AspectRatio(
aspectRatio: 9 / 16,
child: ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width:
HomeViewState.cameraController!.value.previewSize!.height,
height:
HomeViewState.cameraController!.value.previewSize!.width,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
(isFront && Platform.isAndroid) ? 3.14 : 0),
child: CameraPreview(HomeViewState.cameraController!),
),
),
),
),
),
),
),
);
}
}

View file

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
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
}
}

View file

@ -0,0 +1,693 @@
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logging/logging.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart';
import 'package:twonly/src/views/camera/camera_preview_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/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
import 'package:twonly/src/views/home_view.dart';
int maxVideoRecordingTime = 15;
class SelectedCameraDetails {
double maxAvailableZoom = 1;
double minAvailableZoom = 1;
int cameraId = 0;
bool isZoomAble = false;
bool isFlashOn = false;
double scaleFactor = 1;
bool cameraLoaded = false;
}
class CameraPreviewControllerView extends StatefulWidget {
const CameraPreviewControllerView({
super.key,
required this.selectCamera,
this.sendTo,
});
final Contact? sendTo;
final Function(int sCameraId, bool init, bool enableAudio) selectCamera;
@override
State<CameraPreviewControllerView> createState() =>
_CameraPreviewControllerView();
}
class _CameraPreviewControllerView extends State<CameraPreviewControllerView> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkPermissions(),
builder: (context, snap) {
if (snap.hasData) {
if (snap.data!) {
return CameraPreviewView(
sendTo: widget.sendTo,
selectCamera: widget.selectCamera,
);
} else {
return PermissionHandlerView(onSuccess: () {
setState(() {});
});
}
} else {
return Container();
}
},
);
}
}
class CameraPreviewView extends StatefulWidget {
const CameraPreviewView({
super.key,
this.sendTo,
required this.selectCamera,
});
final Contact? sendTo;
final Function(int sCameraId, bool init, bool enableAudio) selectCamera;
@override
State<CameraPreviewView> createState() => _CameraPreviewViewState();
}
class _CameraPreviewViewState extends State<CameraPreviewView> {
bool sharePreviewIsShown = false;
bool galleryLoadedImageIsShown = false;
bool showSelfieFlash = false;
double basePanY = 0;
double baseScaleFactor = 0;
bool cameraLoaded = false;
bool useHighQuality = false;
bool isVideoRecording = false;
bool hasAudioPermission = true;
bool videoWithAudio = true;
DateTime? videoRecordingStarted;
Timer? videoRecordingTimer;
DateTime currentTime = DateTime.now();
final GlobalKey keyTriggerButton = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
void initState() {
super.initState();
// selectCamera(0, init: true);
initAsync();
}
void initAsync() async {
final user = await getUser();
if (user == null) return;
if (user.useHighQuality != null) {
useHighQuality = user.useHighQuality!;
}
hasAudioPermission = await Permission.microphone.isGranted;
if (!mounted) return;
setState(() {});
}
@override
void dispose() {
videoRecordingTimer?.cancel();
super.dispose();
}
Future requestMicrophonePermission() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
].request();
if (statuses[Permission.microphone]!.isPermanentlyDenied) {
openAppSettings();
} else {
hasAudioPermission = await Permission.microphone.isGranted;
setState(() {});
}
}
Future<void> updateScaleFactor(double newScale) async {
if (HomeViewState.selectedCameraDetails.scaleFactor == newScale ||
HomeViewState.cameraController == null) return;
await HomeViewState.cameraController?.setZoomLevel(newScale.clamp(
HomeViewState.selectedCameraDetails.minAvailableZoom,
HomeViewState.selectedCameraDetails.maxAvailableZoom));
setState(() {
HomeViewState.selectedCameraDetails.scaleFactor = newScale;
});
}
Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async {
try {
// Load the image into bytes
final Uint8List imageBytes = await picture.readAsBytes();
// Remove the image file
await File(picture.path).delete();
return imageBytes;
} catch (e) {
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error loading picture: $e'),
duration: Duration(seconds: 3),
),
);
}
return null;
}
}
Future takePicture() async {
if (sharePreviewIsShown || isVideoRecording) return;
late Future<Uint8List?> imageBytes;
setState(() {
sharePreviewIsShown = true;
});
if (HomeViewState.selectedCameraDetails.isFlashOn) {
if (isFront) {
setState(() {
showSelfieFlash = true;
});
} else {
HomeViewState.cameraController?.setFlashMode(FlashMode.torch);
}
await Future.delayed(Duration(milliseconds: 1000));
}
await HomeViewState.cameraController?.pausePreview();
if (!context.mounted) return;
HomeViewState.cameraController?.setFlashMode(
HomeViewState.selectedCameraDetails.isFlashOn
? FlashMode.always
: FlashMode.off);
imageBytes = HomeViewState.screenshotController.capture(
pixelRatio:
(useHighQuality) ? MediaQuery.of(context).devicePixelRatio : 1);
if (await pushMediaEditor(imageBytes, null)) {
return;
}
}
Future<bool> pushMediaEditor(
Future<Uint8List?>? imageBytes, File? videoFilePath) async {
bool? shoudReturn = await Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (context, a1, a2) => ShareImageEditorView(
videoFilePath: videoFilePath,
imageBytes: imageBytes,
sendTo: widget.sendTo,
mirrorVideo: isFront && Platform.isAndroid,
useHighQuality: useHighQuality,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child;
},
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
if (!context.mounted) return true;
// shouldReturn is null when the user used the back button
if (shoudReturn != null && shoudReturn) {
// ignore: use_build_context_synchronously
if (widget.sendTo == null) {
globalUpdateOfHomeViewPageIndex(0);
} else {
Navigator.pop(context);
}
return true;
}
widget.selectCamera(
HomeViewState.selectedCameraDetails.cameraId, false, false);
if (context.mounted) {
setState(() {
sharePreviewIsShown = false;
showSelfieFlash = false;
});
}
return false;
}
bool get isFront =>
HomeViewState.cameraController?.description.lensDirection ==
CameraLensDirection.front;
Future onPanUpdate(details) async {
if (isFront) {
return;
}
if (HomeViewState.cameraController == null) return;
if (!HomeViewState.cameraController!.value.isInitialized) return;
HomeViewState.selectedCameraDetails.scaleFactor =
(baseScaleFactor + (basePanY - details.localPosition.dy) / 30)
.clamp(1, HomeViewState.selectedCameraDetails.maxAvailableZoom);
await HomeViewState.cameraController!
.setZoomLevel(HomeViewState.selectedCameraDetails.scaleFactor);
if (mounted) {
setState(() {});
}
}
Future pickImageFromGallery() async {
setState(() {
galleryLoadedImageIsShown = true;
sharePreviewIsShown = true;
});
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
File imageFile = File(pickedFile.path);
if (await pushMediaEditor(imageFile.readAsBytes(), null)) {
return;
}
}
setState(() {
galleryLoadedImageIsShown = false;
sharePreviewIsShown = false;
});
}
Future startVideoRecording() async {
if (HomeViewState.cameraController != null &&
HomeViewState.cameraController!.value.isRecordingVideo) return;
if (hasAudioPermission && videoWithAudio) {
await widget.selectCamera(
HomeViewState.selectedCameraDetails.cameraId,
false,
await Permission.microphone.isGranted && videoWithAudio,
);
}
setState(() {
isVideoRecording = true;
});
try {
await HomeViewState.cameraController?.startVideoRecording();
videoRecordingTimer = Timer.periodic(Duration(milliseconds: 15), (timer) {
setState(() {
currentTime = DateTime.now();
});
if (videoRecordingStarted != null &&
currentTime.difference(videoRecordingStarted!).inSeconds >=
maxVideoRecordingTime) {
timer.cancel();
videoRecordingTimer = null;
stopVideoRecording();
}
});
setState(() {
videoRecordingStarted = DateTime.now();
isVideoRecording = true;
});
} on CameraException catch (e) {
setState(() {
isVideoRecording = false;
});
_showCameraException(e);
return;
}
}
Future stopVideoRecording() async {
if (videoRecordingTimer != null) {
videoRecordingTimer?.cancel();
videoRecordingTimer = null;
}
if (HomeViewState.cameraController == null ||
!HomeViewState.cameraController!.value.isRecordingVideo) {
return null;
}
try {
setState(() {
videoRecordingStarted = null;
isVideoRecording = false;
sharePreviewIsShown = true;
});
File? videoPathFile;
XFile? videoPath =
await HomeViewState.cameraController?.stopVideoRecording();
if (videoPath != null) {
if (Platform.isAndroid) {
// see https://github.com/flutter/flutter/issues/148335
await File(videoPath.path).rename("${videoPath.path}.mp4");
videoPathFile = File("${videoPath.path}.mp4");
} else {
videoPathFile = File(videoPath.path);
}
}
await HomeViewState.cameraController?.pausePreview();
if (await pushMediaEditor(null, videoPathFile)) {
return;
}
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(dynamic e) {
Logger("ui.camera").shout("$e");
try {
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $e'),
duration: Duration(seconds: 3),
),
);
}
// ignore: empty_catches
} catch (e) {}
}
@override
Widget build(BuildContext context) {
if (HomeViewState.selectedCameraDetails.cameraId >= gCameras.length ||
HomeViewState.cameraController == null) {
return Container();
}
return MediaViewSizing(
child: GestureDetector(
onPanStart: (details) async {
if (isFront) {
return;
}
setState(() {
basePanY = details.localPosition.dy;
baseScaleFactor = HomeViewState.selectedCameraDetails.scaleFactor;
});
},
onLongPressMoveUpdate: onPanUpdate,
onLongPressStart: (details) {
setState(() {
basePanY = details.localPosition.dy;
baseScaleFactor = HomeViewState.selectedCameraDetails.scaleFactor;
});
// Get the position of the pointer
RenderBox renderBox =
keyTriggerButton.currentContext?.findRenderObject() as RenderBox;
Offset localPosition =
renderBox.globalToLocal(details.globalPosition);
final containerRect =
Rect.fromLTWH(0, 0, renderBox.size.width, renderBox.size.height);
if (containerRect.contains(localPosition)) {
startVideoRecording();
}
},
onLongPressEnd: (a) {
stopVideoRecording();
},
onPanEnd: (a) {
stopVideoRecording();
},
onPanUpdate: onPanUpdate,
child: Stack(
children: [
// if (!galleryLoadedImageIsShown)
// CameraPreviewWidget(
// controller: HomeViewState.cameraController,
// screenshotController: screenshotController,
// ),
if (galleryLoadedImageIsShown)
Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 1, color: context.color.primary),
),
),
// Positioned.fill(
// child: GestureDetector(),
// ),
if (!sharePreviewIsShown &&
widget.sendTo != null &&
!isVideoRecording)
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
if (!sharePreviewIsShown && !isVideoRecording)
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: () async {
widget.selectCamera(
(HomeViewState.selectedCameraDetails.cameraId +
1) %
2,
false,
false);
},
),
ActionButton(
HomeViewState.selectedCameraDetails.isFlashOn
? Icons.flash_on_rounded
: Icons.flash_off_rounded,
tooltipText: context.lang.toggleFlashLight,
color: HomeViewState.selectedCameraDetails.isFlashOn
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
if (HomeViewState.selectedCameraDetails.isFlashOn) {
HomeViewState.cameraController
?.setFlashMode(FlashMode.off);
HomeViewState.selectedCameraDetails.isFlashOn =
false;
} else {
HomeViewState.cameraController
?.setFlashMode(FlashMode.always);
HomeViewState.selectedCameraDetails.isFlashOn =
true;
}
setState(() {});
},
),
if (!isFront)
ActionButton(
Icons.hd_rounded,
tooltipText: context.lang.toggleHighQuality,
color: useHighQuality
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
useHighQuality = !useHighQuality;
setState(() {});
var user = await getUser();
if (user != null) {
user.useHighQuality = useHighQuality;
updateUser(user);
}
},
),
if (!hasAudioPermission)
ActionButton(
Icons.mic_off_rounded,
color: Colors.white.withAlpha(160),
tooltipText:
"Allow microphone access for video recording.",
onPressed: requestMicrophonePermission,
),
if (hasAudioPermission)
ActionButton(
(videoWithAudio)
? Icons.volume_up_rounded
: Icons.volume_off_rounded,
tooltipText: "Record video with audio.",
color: (videoWithAudio)
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
setState(() {
videoWithAudio = !videoWithAudio;
});
},
),
],
),
),
),
),
if (!sharePreviewIsShown)
Positioned(
bottom: 30,
left: 0,
right: 0,
child: Align(
alignment: Alignment.bottomCenter,
child: Column(
children: [
if (HomeViewState.cameraController!.value.isInitialized &&
HomeViewState.selectedCameraDetails.isZoomAble &&
!isFront &&
!isVideoRecording)
SizedBox(
width: 120,
child: CameraZoomButtons(
key: widget.key,
scaleFactor:
HomeViewState.selectedCameraDetails.scaleFactor,
updateScaleFactor: updateScaleFactor,
controller: HomeViewState.cameraController!,
),
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isVideoRecording)
GestureDetector(
onTap: pickImageFromGallery,
child: Align(
alignment: Alignment.center,
child: Container(
height: 50,
width: 80,
padding: const EdgeInsets.all(2),
child: Center(
child: FaIcon(
FontAwesomeIcons.photoFilm,
color: Colors.white,
size: 25,
),
),
),
),
),
GestureDetector(
onTap: takePicture,
// onLongPress: startVideoRecording,
key: keyTriggerButton,
child: Align(
alignment: Alignment.center,
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,
),
),
),
),
),
if (!isVideoRecording) SizedBox(width: 80)
],
),
],
),
),
),
if (videoRecordingStarted != null)
Positioned(
top: 50,
left: 0,
right: 0,
child: Center(
child: SizedBox(
width: 50,
height: 50,
child: Stack(
children: [
Center(
child: CircularProgressIndicator(
value:
(currentTime.difference(videoRecordingStarted!))
.inMilliseconds /
(maxVideoRecordingTime * 1000),
strokeWidth: 4,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.red),
backgroundColor: Colors.grey[300],
),
),
Center(
child: Text(
currentTime
.difference(videoRecordingStarted!)
.inSeconds
.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 17,
shadows: [
Shadow(
color: const Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0,
)
],
),
),
)
],
),
),
),
),
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(
borderRadius: BorderRadius.circular(22),
child: Container(
color: Colors.white,
),
),
),
],
),
),
);
}
}

View file

@ -1,844 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logging/logging.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/views/camera/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/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/permissions_view.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
import 'package:twonly/src/views/home_view.dart';
int maxVideoRecordingTime = 15;
class CameraPreviewView extends StatefulWidget {
const CameraPreviewView({super.key, this.sendTo});
final Contact? sendTo;
@override
State<CameraPreviewView> createState() => _CameraPreviewViewState();
}
class _CameraPreviewViewState extends State<CameraPreviewView> {
double scaleFactor = 1;
bool sharePreviewIsShown = false;
bool galleryLoadedImageIsShown = false;
bool isFlashOn = false;
bool showSelfieFlash = false;
int cameraId = 0;
bool isZoomAble = false;
double basePanY = 0;
double baseScaleFactor = 0;
bool cameraLoaded = false;
bool useHighQuality = false;
bool isVideoRecording = false;
bool hasAudioPermission = true;
bool videoWithAudio = true;
DateTime? videoRecordingStarted;
Timer? videoRecordingTimer;
double _minAvailableZoom = 1.0;
double _maxAvailableZoom = 1.0;
DateTime currentTime = DateTime.now();
final GlobalKey keyTriggerButton = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
CameraController? controller;
ScreenshotController screenshotController = ScreenshotController();
@override
void initState() {
super.initState();
selectCamera(0, init: true);
initAsync();
}
void initAsync() async {
final user = await getUser();
if (user == null) return;
if (user.useHighQuality != null) {
useHighQuality = user.useHighQuality!;
}
hasAudioPermission = await Permission.microphone.isGranted;
if (!mounted) return;
setState(() {});
}
@override
void dispose() {
if (cameraId < gCameras.length) {
controller?.dispose();
}
videoRecordingTimer?.cancel();
super.dispose();
}
Future requestMicrophonePermission() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
].request();
if (statuses[Permission.microphone]!.isPermanentlyDenied) {
openAppSettings();
} else {
hasAudioPermission = await Permission.microphone.isGranted;
if (hasAudioPermission) {
selectCamera(cameraId);
}
}
}
Future selectCamera(
int sCameraId, {
bool init = false,
bool enableAudio = false,
}) async {
if (sCameraId >= gCameras.length) return;
if (init) {
for (; sCameraId < gCameras.length; sCameraId++) {
if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) {
break;
}
}
}
setState(() {
isZoomAble = false;
if (cameraId != sCameraId) {
// switch between front and back
scaleFactor = 1;
}
});
controller = CameraController(
gCameras[sCameraId],
ResolutionPreset.high,
enableAudio: enableAudio,
);
await controller?.initialize().then((_) async {
if (!mounted) {
return;
}
await controller!.setZoomLevel(scaleFactor);
await controller?.lockCaptureOrientation(DeviceOrientation.portraitUp);
controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
controller
?.getMaxZoomLevel()
.then((double value) => _maxAvailableZoom = value);
controller
?.getMinZoomLevel()
.then((double value) => _minAvailableZoom = value);
isZoomAble = await controller?.getMinZoomLevel() !=
await controller?.getMaxZoomLevel();
setState(() {
cameraLoaded = true;
});
}).catchError((Object e) {
if (e is CameraException) {
switch (e.code) {
case 'CameraAccessDenied':
// Handle access errors here.
break;
default:
// Handle other errors here.
break;
}
}
});
if (!mounted) {
return;
}
setState(() {
cameraId = sCameraId;
});
}
Future<void> updateScaleFactor(double newScale) async {
if (scaleFactor == newScale || controller == null) return;
await controller
?.setZoomLevel(newScale.clamp(_minAvailableZoom, _maxAvailableZoom));
setState(() {
scaleFactor = newScale;
});
}
Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async {
try {
// Load the image into bytes
final Uint8List imageBytes = await picture.readAsBytes();
// Remove the image file
await File(picture.path).delete();
return imageBytes;
} catch (e) {
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error loading picture: $e'),
duration: Duration(seconds: 3),
),
);
}
return null;
}
}
Future takePicture() async {
if (sharePreviewIsShown || isVideoRecording) return;
late Future<Uint8List?> imageBytes;
setState(() {
sharePreviewIsShown = true;
});
// if (useHighQuality && !isFront) {
// if (Platform.isIOS) {
// await controller?.pausePreview();
// if (!context.mounted) return;
// }
// try {
// // Take the picture
// final XFile? picture = await controller?.takePicture();
// if (picture == null) return;
// imageBytes = loadAndDeletePictureFromFile(picture);
// } catch (e) {
// _showCameraException(e);
// return;
// }
// } else {
if (isFlashOn) {
if (isFront) {
setState(() {
showSelfieFlash = true;
});
} else {
controller?.setFlashMode(FlashMode.torch);
}
await Future.delayed(Duration(milliseconds: 1000));
}
await controller?.pausePreview();
if (!context.mounted) return;
controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
imageBytes = screenshotController.capture(
pixelRatio:
(useHighQuality) ? MediaQuery.of(context).devicePixelRatio : 1);
// }
if (await pushMediaEditor(imageBytes, null)) {
return;
}
}
Future<bool> pushMediaEditor(
Future<Uint8List?>? imageBytes, File? videoFilePath) async {
bool? shoudReturn = await Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (context, a1, a2) => ShareImageEditorView(
videoFilePath: videoFilePath,
imageBytes: imageBytes,
sendTo: widget.sendTo,
mirrorVideo: isFront && Platform.isAndroid,
useHighQuality: useHighQuality,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child;
},
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
if (!context.mounted) return true;
// shouldReturn is null when the user used the back button
if (shoudReturn != null && shoudReturn) {
// ignore: use_build_context_synchronously
if (widget.sendTo == null) {
globalUpdateOfHomeViewPageIndex(0);
} else {
Navigator.pop(context);
}
return true;
}
selectCamera(cameraId);
if (context.mounted) {
setState(() {
sharePreviewIsShown = false;
showSelfieFlash = false;
});
}
return false;
}
bool get isFront =>
controller?.description.lensDirection == CameraLensDirection.front;
Future onPanUpdate(details) async {
if (isFront) {
return;
}
if (controller == null) return;
if (!controller!.value.isInitialized) return;
scaleFactor = (baseScaleFactor + (basePanY - details.localPosition.dy) / 30)
.clamp(1, _maxAvailableZoom);
await controller!.setZoomLevel(scaleFactor);
if (mounted) {
setState(() {});
}
}
Future pickImageFromGallery() async {
setState(() {
galleryLoadedImageIsShown = true;
sharePreviewIsShown = true;
});
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
File imageFile = File(pickedFile.path);
if (await pushMediaEditor(imageFile.readAsBytes(), null)) {
return;
}
}
setState(() {
galleryLoadedImageIsShown = false;
sharePreviewIsShown = false;
});
}
Future startVideoRecording() async {
if (controller != null && controller!.value.isRecordingVideo) return;
if (hasAudioPermission && videoWithAudio) {
await selectCamera(cameraId,
enableAudio: await Permission.microphone.isGranted && videoWithAudio);
}
try {
await controller?.startVideoRecording();
videoRecordingTimer = Timer.periodic(Duration(milliseconds: 15), (timer) {
setState(() {
currentTime = DateTime.now();
});
if (videoRecordingStarted != null &&
currentTime.difference(videoRecordingStarted!).inSeconds >=
maxVideoRecordingTime) {
timer.cancel();
videoRecordingTimer = null;
stopVideoRecording();
}
});
setState(() {
videoRecordingStarted = DateTime.now();
isVideoRecording = true;
});
} on CameraException catch (e) {
_showCameraException(e);
return;
}
}
Future stopVideoRecording() async {
if (videoRecordingTimer != null) {
videoRecordingTimer?.cancel();
videoRecordingTimer = null;
}
if (controller == null || !controller!.value.isRecordingVideo) {
return null;
}
try {
setState(() {
videoRecordingStarted = null;
isVideoRecording = false;
sharePreviewIsShown = true;
});
File? videoPathFile;
XFile? videoPath = await controller?.stopVideoRecording();
if (videoPath != null) {
if (Platform.isAndroid) {
// see https://github.com/flutter/flutter/issues/148335
await File(videoPath.path).rename("${videoPath.path}.mp4");
videoPathFile = File("${videoPath.path}.mp4");
} else {
videoPathFile = File(videoPath.path);
}
}
await controller?.pausePreview();
if (await pushMediaEditor(null, videoPathFile)) {
return;
}
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(dynamic e) {
Logger("ui.camera").shout("$e");
try {
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $e'),
duration: Duration(seconds: 3),
),
);
}
// ignore: empty_catches
} catch (e) {}
}
@override
Widget build(BuildContext context) {
if (cameraId >= gCameras.length || controller == null) {
return Container();
}
return MediaViewSizing(
child: GestureDetector(
onPanStart: (details) async {
if (isFront) {
return;
}
setState(() {
basePanY = details.localPosition.dy;
baseScaleFactor = scaleFactor;
});
},
onDoubleTap: () async {
selectCamera((cameraId + 1) % 2);
},
onLongPressMoveUpdate: onPanUpdate,
onLongPressStart: (details) {
setState(() {
basePanY = details.localPosition.dy;
baseScaleFactor = scaleFactor;
});
// Get the position of the pointer
RenderBox renderBox =
keyTriggerButton.currentContext?.findRenderObject() as RenderBox;
Offset localPosition =
renderBox.globalToLocal(details.globalPosition);
final containerRect =
Rect.fromLTWH(0, 0, renderBox.size.width, renderBox.size.height);
if (containerRect.contains(localPosition)) {
startVideoRecording();
}
},
onLongPressEnd: (a) {
stopVideoRecording();
},
onPanEnd: (a) {
stopVideoRecording();
},
onPanUpdate: onPanUpdate,
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(22),
child: Stack(
children: [
if (!galleryLoadedImageIsShown)
CameraPreviewWidget(
controller: controller!,
screenshotController: screenshotController,
isFront: isFront,
),
if (galleryLoadedImageIsShown)
Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 1, color: context.color.primary),
),
),
// Positioned.fill(
// child: GestureDetector(),
// ),
if (!sharePreviewIsShown &&
widget.sendTo != null &&
!isVideoRecording)
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
if (!sharePreviewIsShown && !isVideoRecording)
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: () async {
selectCamera((cameraId + 1) % 2);
},
),
ActionButton(
isFlashOn
? Icons.flash_on_rounded
: Icons.flash_off_rounded,
tooltipText: context.lang.toggleFlashLight,
color: isFlashOn
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
if (isFlashOn) {
controller?.setFlashMode(FlashMode.off);
isFlashOn = false;
} else {
controller?.setFlashMode(FlashMode.always);
isFlashOn = true;
}
setState(() {});
},
),
if (!isFront)
ActionButton(
Icons.hd_rounded,
tooltipText: context.lang.toggleHighQuality,
color: useHighQuality
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
useHighQuality = !useHighQuality;
setState(() {});
var user = await getUser();
if (user != null) {
user.useHighQuality = useHighQuality;
updateUser(user);
}
},
),
if (!hasAudioPermission)
ActionButton(
Icons.mic_off_rounded,
color: Colors.white.withAlpha(160),
tooltipText:
"Allow microphone access for video recording.",
onPressed: requestMicrophonePermission,
),
if (hasAudioPermission)
ActionButton(
(videoWithAudio)
? Icons.volume_up_rounded
: Icons.volume_off_rounded,
tooltipText: "Record video with audio.",
color: (videoWithAudio)
? Colors.white
: Colors.white.withAlpha(160),
onPressed: () async {
setState(() {
videoWithAudio = !videoWithAudio;
});
selectCamera(cameraId);
},
),
],
),
),
),
),
if (!sharePreviewIsShown)
Positioned(
bottom: 30,
left: 0,
right: 0,
child: Align(
alignment: Alignment.bottomCenter,
child: Column(
children: [
if (controller!.value.isInitialized &&
isZoomAble &&
!isFront &&
!isVideoRecording)
SizedBox(
width: 120,
child: CameraZoomButtons(
key: widget.key,
scaleFactor: scaleFactor,
updateScaleFactor: updateScaleFactor,
controller: controller!,
),
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isVideoRecording)
GestureDetector(
onTap: pickImageFromGallery,
child: Align(
alignment: Alignment.center,
child: Container(
height: 50,
width: 80,
padding: const EdgeInsets.all(2),
child: Center(
child: FaIcon(
FontAwesomeIcons.photoFilm,
color: Colors.white,
size: 25,
),
),
),
),
),
GestureDetector(
onTap: takePicture,
// onLongPress: startVideoRecording,
key: keyTriggerButton,
child: Align(
alignment: Alignment.center,
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,
),
),
),
),
),
if (!isVideoRecording) SizedBox(width: 80)
],
),
],
),
),
),
],
),
),
if (videoRecordingStarted != null)
Positioned(
top: 50,
left: 0,
right: 0,
child: Center(
child: SizedBox(
width: 50,
height: 50,
child: Stack(
children: [
Center(
child: CircularProgressIndicator(
value:
(currentTime.difference(videoRecordingStarted!))
.inMilliseconds /
(maxVideoRecordingTime * 1000),
strokeWidth: 4,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.red),
backgroundColor: Colors.grey[300],
),
),
Center(
child: Text(
currentTime
.difference(videoRecordingStarted!)
.inSeconds
.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 17,
shadows: [
Shadow(
color: const Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0,
)
],
),
),
)
],
),
),
),
),
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(
borderRadius: BorderRadius.circular(22),
child: Container(
color: Colors.white,
),
),
),
],
),
),
);
}
}
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

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/views/camera/camera_preview_view.dart'; import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
class CameraSendToView extends StatefulWidget { class CameraSendToView extends StatefulWidget {
const CameraSendToView(this.sendTo, {super.key}); const CameraSendToView(this.sendTo, {super.key});
@ -12,8 +12,9 @@ class CameraSendToView extends StatefulWidget {
class CameraSendToViewState extends State<CameraSendToView> { class CameraSendToViewState extends State<CameraSendToView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold();
body: CameraPreviewViewPermission(sendTo: widget.sendTo), // return Scaffold(
); // body: CameraPreviewControllerView(sendTo: widget.sendTo),
// );
} }
} }

View file

@ -7,7 +7,7 @@ import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart' show ErrorCode; import 'package:twonly/src/model/protobuf/api/error.pb.dart' show ErrorCode;
import 'package:twonly/src/providers/api/media_send.dart'; import 'package:twonly/src/providers/api/media_send.dart';
import 'package:twonly/src/views/camera/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/image_editor/action_button.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';

View file

@ -6,7 +6,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/providers/api/media_send.dart'; import 'package:twonly/src/providers/api/media_send.dart';
import 'package:twonly/src/views/camera/components/best_friends_selector.dart'; import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.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';
import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/initialsavatar.dart';

View file

@ -128,6 +128,7 @@ class GalleryMainViewState extends State<GalleryMainView> {
List<GalleryItem> galleryItems = []; List<GalleryItem> galleryItems = [];
Map<String, List<int>> orderedByMonth = {}; Map<String, List<int>> orderedByMonth = {};
List<String> months = []; List<String> months = [];
bool mounted = true;
@override @override
void initState() { void initState() {
@ -135,6 +136,12 @@ class GalleryMainViewState extends State<GalleryMainView> {
initAsync(); initAsync();
} }
@override
void dispose() {
mounted = false;
super.dispose();
}
Future<List<GalleryItem>> loadMemoriesDirectory() async { Future<List<GalleryItem>> loadMemoriesDirectory() async {
final directoryPath = await send.getMediaBaseFilePath("memories"); final directoryPath = await send.getMediaBaseFilePath("memories");
final directory = Directory(directoryPath); final directory = Directory(directoryPath);
@ -219,8 +226,10 @@ class GalleryMainViewState extends State<GalleryMainView> {
} }
orderedByMonth.putIfAbsent(month, () => []).add(i); orderedByMonth.putIfAbsent(month, () => []).add(i);
} }
if (mounted) {
setState(() {}); setState(() {});
} }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -1,10 +1,19 @@
import 'dart:async';
import 'package:camera/camera.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:logging/logging.dart';
import 'package:pie_menu/pie_menu.dart'; import 'package:pie_menu/pie_menu.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
import 'package:twonly/src/views/components/user_context_menu.dart'; import 'package:twonly/src/views/components/user_context_menu.dart';
import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/views/gallery/gallery_main_view.dart'; import 'package:twonly/src/views/gallery/gallery_main_view.dart';
import 'camera/camera_preview_view.dart'; import 'camera/camera_preview_controller_view.dart';
import 'chats/chat_list_view.dart'; import 'chats/chat_list_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -21,29 +30,133 @@ class HomeView extends StatefulWidget {
State<HomeView> createState() => HomeViewState(); State<HomeView> createState() => HomeViewState();
} }
class Shade extends StatelessWidget {
const Shade({super.key, required this.opacity});
final double opacity;
@override
Widget build(BuildContext context) {
return Positioned.fill(
child: Opacity(
opacity: opacity,
child: Container(
color: context.color.surface,
),
),
);
}
}
class HomeViewState extends State<HomeView> { class HomeViewState extends State<HomeView> {
int activePageIdx = 0; int activePageIdx = 0;
late PageController homeViewPageController;
final PageController homeViewPageController =
PageController(keepPage: true, initialPage: 1);
double buttonDiameter = 100.0;
double offsetRatio = 0.0;
double offsetFromOne = 0.0;
Timer? disableCameraTimer;
static CameraController? cameraController;
static ScreenshotController screenshotController = ScreenshotController();
static SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
bool onPageView(ScrollNotification notification) {
disableCameraTimer?.cancel();
if (notification.depth == 0 && notification is ScrollUpdateNotification) {
setState(() {
offsetFromOne = 1.0 - (homeViewPageController.page ?? 0);
offsetRatio = offsetFromOne.abs();
});
}
if (cameraController == null) {
selectCamera(selectedCameraDetails.cameraId, false, false);
}
if (offsetRatio == 1) {
disableCameraTimer = Timer(Duration(seconds: 2), () {
cameraController?.dispose();
cameraController = null;
disableCameraTimer = null;
});
}
return false;
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
activePageIdx = widget.initialPage; activePageIdx = widget.initialPage;
homeViewPageController = PageController(initialPage: widget.initialPage);
globalUpdateOfHomeViewPageIndex = (index) { globalUpdateOfHomeViewPageIndex = (index) {
homeViewPageController.jumpToPage(index); homeViewPageController.jumpToPage(index);
setState(() { setState(() {
activePageIdx = index; activePageIdx = index;
}); });
}; };
selectNotificationStream.stream selectNotificationStream.stream
.listen((NotificationResponse? response) async { .listen((NotificationResponse? response) async {
globalUpdateOfHomeViewPageIndex(0); globalUpdateOfHomeViewPageIndex(0);
}); });
selectCamera(0, true, false);
initAsync(); initAsync();
} }
@override
void dispose() {
selectNotificationStream.close();
disableCameraTimer?.cancel();
super.dispose();
}
Future selectCamera(int sCameraId, bool init, bool enableAudio) async {
if (sCameraId >= gCameras.length) return;
if (init) {
for (; sCameraId < gCameras.length; sCameraId++) {
if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) {
break;
}
}
}
selectedCameraDetails.isZoomAble = false;
if (selectedCameraDetails.cameraId != sCameraId) {
// switch between front and back
selectedCameraDetails.scaleFactor = 1;
}
cameraController = CameraController(
gCameras[sCameraId],
ResolutionPreset.high,
enableAudio: enableAudio,
);
await cameraController?.initialize().then((_) async {
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
await cameraController
?.lockCaptureOrientation(DeviceOrientation.portraitUp);
cameraController?.setFlashMode(
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off);
await cameraController?.getMaxZoomLevel().then(
(double value) => selectedCameraDetails.maxAvailableZoom = value);
await cameraController?.getMinZoomLevel().then(
(double value) => selectedCameraDetails.minAvailableZoom = value);
selectedCameraDetails.isZoomAble =
selectedCameraDetails.maxAvailableZoom !=
selectedCameraDetails.minAvailableZoom;
setState(() {
selectedCameraDetails.cameraLoaded = true;
selectedCameraDetails.cameraId = sCameraId;
});
}).catchError((Object e) {
Logger("home_view.dart").shout("$e");
});
setState(() {});
}
Future toggleSelectedCamera() async {
selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
}
Future initAsync() async { Future initAsync() async {
var notificationAppLaunchDetails = var notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
@ -55,29 +168,53 @@ class HomeViewState extends State<HomeView> {
} }
} }
@override
void dispose() {
selectNotificationStream.close();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PieCanvas( return PieCanvas(
theme: getPieCanvasTheme(context), theme: getPieCanvasTheme(context),
child: Scaffold( child: Scaffold(
body: PageView( body: GestureDetector(
onDoubleTap: offsetRatio == 0 ? toggleSelectedCamera : null,
child: Stack(
children: <Widget>[
CameraPreviewWidget(),
Shade(
opacity: offsetRatio,
),
NotificationListener<ScrollNotification>(
onNotification: onPageView,
child: Positioned.fill(
child: PageView(
controller: homeViewPageController, controller: homeViewPageController,
onPageChanged: (index) { onPageChanged: (index) {
setState(() {
activePageIdx = index; activePageIdx = index;
setState(() {}); });
}, },
children: [ children: [
ChatListView(), ChatListView(),
CameraPreviewViewPermission(), Container(),
GalleryMainView() GalleryMainView(),
], ],
), ),
),
),
Positioned(
left: 0,
top: 0,
right: 0,
bottom: (offsetRatio > 0.25)
? MediaQuery.sizeOf(context).height * 2
: 0,
child: Opacity(
opacity: (1 - (offsetRatio * 4) % 1),
child: CameraPreviewControllerView(
selectCamera: selectCamera,
),
)),
],
),
),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
showSelectedLabels: false, showSelectedLabels: false,
showUnselectedLabels: false, showUnselectedLabels: false,
@ -88,7 +225,9 @@ class HomeViewState extends State<HomeView> {
color: Theme.of(context).colorScheme.inverseSurface), color: Theme.of(context).colorScheme.inverseSurface),
items: [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.solidComments), label: ""), icon: FaIcon(FontAwesomeIcons.solidComments),
label: "",
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: FaIcon(FontAwesomeIcons.camera), icon: FaIcon(FontAwesomeIcons.camera),
label: "", label: "",