fix black screen

This commit is contained in:
otsmr 2026-07-29 14:34:36 +02:00
parent 79d668e004
commit e8e3254278
4 changed files with 82 additions and 3 deletions

View file

@ -168,6 +168,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
super.initState(); super.initState();
initVolumeControl(); initVolumeControl();
initAsync(); initAsync();
_checkAndInitCamera();
} }
@override @override
@ -176,12 +177,26 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (oldWidget.isVisible != widget.isVisible) { if (oldWidget.isVisible != widget.isVisible) {
if (widget.isVisible) { if (widget.isVisible) {
initVolumeControl(); initVolumeControl();
_checkAndInitCamera();
} else { } else {
_deInitVolumeControl(); _deInitVolumeControl();
} }
} }
} }
void _checkAndInitCamera() {
if (widget.isVisible &&
mc.cameraController == null &&
!mc.initCameraStarted) {
unawaited(
mc.selectCamera(
mc.selectedCameraDetails.cameraId,
false,
),
);
}
}
@override @override
void dispose() { void dispose() {
_videoRecordingTimer?.cancel(); _videoRecordingTimer?.cancel();
@ -686,7 +701,23 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length || if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length ||
mc.cameraController == null) { mc.cameraController == null) {
return Container(); if (widget.isVisible && !mc.initCameraStarted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkAndInitCamera();
});
}
// Show a loader instead of an empty container to prevent a black screen
// while the camera is initializing or if initialization failed.
return Center(
child: SizedBox(
height: 60,
width: 60,
child: ThreeRotatingDots(
size: 40,
color: context.color.primary,
),
),
);
} }
return StreamBuilder( return StreamBuilder(
stream: userService.onUserUpdated, stream: userService.onUserUpdated,

View file

@ -155,6 +155,7 @@ class MainCameraController {
} catch (e) { } catch (e) {
Log.error('Error querying available cameras: $e'); Log.error('Error querying available cameras: $e');
initCameraStarted = false; initCameraStarted = false;
setState?.call();
return; return;
} }
@ -164,6 +165,7 @@ class MainCameraController {
'Trying to select a non existing camera $cameraId >= ${AppEnvironment.cameras.length}', 'Trying to select a non existing camera $cameraId >= ${AppEnvironment.cameras.length}',
); );
initCameraStarted = false; initCameraStarted = false;
setState?.call();
return; return;
} }
@ -192,7 +194,7 @@ class MainCameraController {
final hasMic = await micPermissionFuture; final hasMic = await micPermissionFuture;
if (sessionId != _cameraSessionId) return; if (sessionId != _cameraSessionId) return;
final controller = CameraController( var controller = CameraController(
AppEnvironment.cameras[cameraId], AppEnvironment.cameras[cameraId],
ResolutionPreset.high, ResolutionPreset.high,
enableAudio: hasMic, enableAudio: hasMic,
@ -204,7 +206,21 @@ class MainCameraController {
var assignedToGlobal = false; var assignedToGlobal = false;
try { try {
_initializeFuture = controller.initialize(); _initializeFuture = controller.initialize();
await _initializeFuture; try {
await _initializeFuture;
} on CameraException catch (e) {
// If specific image format is unsupported on this hardware, fallback to default format
Log.warn('Initial camera format initialization failed ($e), trying fallback format...');
await controller.dispose();
controller = CameraController(
AppEnvironment.cameras[cameraId],
ResolutionPreset.high,
enableAudio: hasMic,
);
_initializeFuture = controller.initialize();
await _initializeFuture;
}
if (sessionId != _cameraSessionId) { if (sessionId != _cameraSessionId) {
unawaited(controller.dispose()); unawaited(controller.dispose());
return; return;
@ -256,6 +272,7 @@ class MainCameraController {
unawaited(controller.dispose()); unawaited(controller.dispose());
} }
initCameraStarted = false; initCameraStarted = false;
setState?.call();
return; return;
} }
} else { } else {

View file

@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/key_verification.service.dart'; import 'package:twonly/src/services/key_verification.service.dart';
@ -8,6 +10,7 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.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/share_image_editor_components/action_button.dart';
class QrCodeScannerView extends StatefulWidget { class QrCodeScannerView extends StatefulWidget {
const QrCodeScannerView({ const QrCodeScannerView({
@ -41,6 +44,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
} }
Permission.camera.isGranted.then((hasPermission) { Permission.camera.isGranted.then((hasPermission) {
if (hasPermission && mounted) { if (hasPermission && mounted) {
AppState.hasCameraPermissions = true;
unawaited(_mainCameraController.selectCamera(0, true)); unawaited(_mainCameraController.selectCamera(0, true));
} }
}); });
@ -100,6 +104,17 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
), ),
), ),
), ),
// Always-visible back button so the user can exit regardless of
// camera state (e.g. during init or after init failure).
Positioned(
left: 5,
top: MediaQuery.paddingOf(context).top + 10,
child: ActionButton(
FontAwesomeIcons.xmark,
tooltipText: context.lang.close,
onPressed: () => Navigator.pop(context),
),
),
], ],
), ),
); );

View file

@ -1,10 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.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/share_image_editor_components/action_button.dart';
class CameraSendToView extends StatefulWidget { class CameraSendToView extends StatefulWidget {
const CameraSendToView(this.sendToGroup, {super.key}); const CameraSendToView(this.sendToGroup, {super.key});
@ -24,6 +28,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
}; };
Permission.camera.isGranted.then((hasPermission) { Permission.camera.isGranted.then((hasPermission) {
if (hasPermission && mounted) { if (hasPermission && mounted) {
AppState.hasCameraPermissions = true;
unawaited(_mainCameraController.selectCamera(0, true)); unawaited(_mainCameraController.selectCamera(0, true));
} }
}); });
@ -58,6 +63,17 @@ class CameraSendToViewState extends State<CameraSendToView> {
isVisible: true, isVisible: true,
), ),
), ),
// Always-visible back button so the user can exit regardless of
// camera state (e.g. during init or after init failure).
Positioned(
left: 5,
top: MediaQuery.paddingOf(context).top + 10,
child: ActionButton(
FontAwesomeIcons.xmark,
tooltipText: context.lang.close,
onPressed: () => Navigator.pop(context),
),
),
], ],
), ),
); );