mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 02:12:13 +00:00
Fix: Issues with the camera initialization
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
d6432677df
commit
fae5ca3d25
21 changed files with 228 additions and 162 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## 0.2.17
|
||||
## 0.2.18
|
||||
|
||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
||||
- New: Adds security profiles.
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
- Fix: Issue with receiving messages when user closed app while decrypting
|
||||
- Fix: Background message fetching reliability.
|
||||
- Fix: Issue with focus changing when taking a picture
|
||||
- Fix: Issues with the camera initialization
|
||||
|
||||
## 0.2.16
|
||||
|
||||
|
|
|
|||
2
fastlane/Appfile
Normal file
2
fastlane/Appfile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
json_key_file(ENV["GOOGLE_PLAY_JSON_KEY_PATH"] || "../../local_data/accesskeys/upload_track_releases_google_play.json")
|
||||
package_name("eu.twonly") # Your application ID
|
||||
15
fastlane/Fastfile
Normal file
15
fastlane/Fastfile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Submit a new App Bundle to the Google Play Internal Track"
|
||||
lane :internal do
|
||||
# This lane assumes that `flutter build appbundle` has already been run from the flutter root.
|
||||
upload_to_play_store(
|
||||
track: 'internal',
|
||||
aab: 'build/app/outputs/bundle/release/app-release.aab',
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true
|
||||
)
|
||||
end
|
||||
end
|
||||
32
fastlane/README.md
Normal file
32
fastlane/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android internal
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android internal
|
||||
```
|
||||
|
||||
Submit a new App Bundle to the Google Play Internal Track
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
18
fastlane/report.xml
Normal file
18
fastlane/report.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites>
|
||||
<testsuite name="fastlane.lanes">
|
||||
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000214">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: upload_to_play_store" time="160.411287">
|
||||
|
||||
</testcase>
|
||||
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
|
@ -33,4 +33,5 @@ class AppState {
|
|||
static bool allowErrorTrackingViaSentry = false;
|
||||
static bool gotMessageFromServer = false;
|
||||
static int latestAppVersionId = 116;
|
||||
static bool hasCameraPermissions = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,24 +46,43 @@ class ScreenshotController {
|
|||
}
|
||||
late GlobalKey _containerKey;
|
||||
|
||||
Future<ScreenshotImageHelper?> capture({double? pixelRatio}) async {
|
||||
Future<ScreenshotImageHelper?> capture({
|
||||
double? pixelRatio,
|
||||
int retries = 20,
|
||||
}) async {
|
||||
try {
|
||||
final findRenderObject = _containerKey.currentContext?.findRenderObject();
|
||||
if (findRenderObject == null) {
|
||||
return null;
|
||||
}
|
||||
final boundary = findRenderObject as RenderRepaintBoundary;
|
||||
|
||||
final context = _containerKey.currentContext;
|
||||
var tmpPixelRatio = pixelRatio;
|
||||
if (tmpPixelRatio == null) {
|
||||
if (context != null && context.mounted) {
|
||||
tmpPixelRatio =
|
||||
tmpPixelRatio ?? MediaQuery.of(context).devicePixelRatio;
|
||||
tmpPixelRatio = tmpPixelRatio ?? MediaQuery.of(context).devicePixelRatio;
|
||||
}
|
||||
}
|
||||
final image = await boundary.toImage(pixelRatio: tmpPixelRatio ?? 1);
|
||||
return ScreenshotImageHelper(image: image);
|
||||
} catch (e) {
|
||||
if (retries > 0) {
|
||||
final completer = Completer<ScreenshotImageHelper?>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final result = await capture(
|
||||
pixelRatio: pixelRatio,
|
||||
retries: retries - 1,
|
||||
);
|
||||
completer.complete(result);
|
||||
});
|
||||
Timer(const Duration(milliseconds: 50), () {
|
||||
if (!completer.isCompleted) {
|
||||
WidgetsBinding.instance.scheduleFrame();
|
||||
}
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
Log.error(e);
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ class _StartNewChatView extends State<AddNewShortcutView> {
|
|||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
onPressed: (_selectedGroups.isEmpty || shortcutEmoji == null)
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class SelectedCameraDetails {
|
|||
bool cameraLoaded = false;
|
||||
}
|
||||
|
||||
class CameraPreviewControllerView extends StatelessWidget {
|
||||
class CameraPreviewControllerView extends StatefulWidget {
|
||||
const CameraPreviewControllerView({
|
||||
required this.mainController,
|
||||
required this.isVisible,
|
||||
|
|
@ -62,23 +62,52 @@ class CameraPreviewControllerView extends StatelessWidget {
|
|||
final bool isVisible;
|
||||
final bool hideControllers;
|
||||
|
||||
@override
|
||||
State<CameraPreviewControllerView> createState() => _CameraPreviewControllerViewState();
|
||||
}
|
||||
|
||||
class _CameraPreviewControllerViewState extends State<CameraPreviewControllerView> {
|
||||
Future<bool>? _permissionsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!AppState.hasCameraPermissions) {
|
||||
_permissionsFuture = checkPermissions().then((hasPermission) {
|
||||
if (hasPermission) {
|
||||
AppState.hasCameraPermissions = true;
|
||||
}
|
||||
return hasPermission;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: checkPermissions(),
|
||||
if (AppState.hasCameraPermissions) {
|
||||
return CameraPreviewView(
|
||||
sendToGroup: widget.sendToGroup,
|
||||
mainCameraController: widget.mainController,
|
||||
isVisible: widget.isVisible,
|
||||
hideControllers: widget.hideControllers,
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder<bool>(
|
||||
future: _permissionsFuture,
|
||||
builder: (context, snap) {
|
||||
if (snap.hasData) {
|
||||
if (snap.data!) {
|
||||
return CameraPreviewView(
|
||||
sendToGroup: sendToGroup,
|
||||
mainCameraController: mainController,
|
||||
isVisible: isVisible,
|
||||
hideControllers: hideControllers,
|
||||
sendToGroup: widget.sendToGroup,
|
||||
mainCameraController: widget.mainController,
|
||||
isVisible: widget.isVisible,
|
||||
hideControllers: widget.hideControllers,
|
||||
);
|
||||
} else {
|
||||
return PermissionHandlerView(
|
||||
onSuccess: () {
|
||||
mainController.selectCamera(0, true);
|
||||
widget.mainController.selectCamera(0, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -210,8 +239,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
Future<void> initAsync() async {
|
||||
_hasAudioPermission = await Permission.microphone.isGranted;
|
||||
|
||||
if (!_hasAudioPermission &&
|
||||
!userService.currentUser.requestedAudioPermission) {
|
||||
if (!_hasAudioPermission && !userService.currentUser.requestedAudioPermission) {
|
||||
await UserService.update((u) => u.requestedAudioPermission = true);
|
||||
await requestMicrophonePermission();
|
||||
}
|
||||
|
|
@ -232,8 +260,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
Future<void> updateScaleFactor(double newScale) async {
|
||||
if (mc.selectedCameraDetails.scaleFactor == newScale ||
|
||||
mc.cameraController == null) {
|
||||
if (mc.selectedCameraDetails.scaleFactor == newScale || mc.cameraController == null) {
|
||||
return;
|
||||
}
|
||||
await mc.cameraController?.setZoomLevel(
|
||||
|
|
@ -316,9 +343,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
bool sharedFromGallery = false,
|
||||
MediaType? mediaType,
|
||||
}) async {
|
||||
final type =
|
||||
mediaType ??
|
||||
((videoFilePath != null) ? MediaType.video : MediaType.image);
|
||||
final type = mediaType ?? ((videoFilePath != null) ? MediaType.video : MediaType.image);
|
||||
final mediaFileService = await initializeMediaUpload(
|
||||
type,
|
||||
userService.currentUser.defaultShowTime,
|
||||
|
|
@ -359,10 +384,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
mainCameraController: mc,
|
||||
previewLink: mc.sharedLinkForPreview,
|
||||
),
|
||||
transitionsBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
|
|
@ -392,16 +416,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool get isFront =>
|
||||
mc.cameraController?.description.lensDirection ==
|
||||
CameraLensDirection.front;
|
||||
bool get isFront => mc.cameraController?.description.lensDirection == CameraLensDirection.front;
|
||||
|
||||
Future<void> onPanUpdate(dynamic details) async {
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
if (mc.cameraController == null ||
|
||||
!mc.cameraController!.value.isInitialized) {
|
||||
if (mc.cameraController == null || !mc.cameraController!.value.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -530,8 +551,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
Future<void> startVideoRecording() async {
|
||||
if (mc.cameraController != null &&
|
||||
mc.cameraController!.value.isRecordingVideo) {
|
||||
if (mc.cameraController != null && mc.cameraController!.value.isRecordingVideo) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
|
|
@ -551,8 +571,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
_currentTime = clock.now();
|
||||
});
|
||||
if (_videoRecordingStarted != null &&
|
||||
_currentTime.difference(_videoRecordingStarted!).inSeconds >=
|
||||
maxVideoRecordingTime) {
|
||||
_currentTime.difference(_videoRecordingStarted!).inSeconds >= maxVideoRecordingTime) {
|
||||
timer.cancel();
|
||||
_videoRecordingTimer = null;
|
||||
stopVideoRecording();
|
||||
|
|
@ -589,8 +608,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
_videoRecordingLocked = false;
|
||||
});
|
||||
|
||||
if (mc.cameraController == null ||
|
||||
!mc.cameraController!.value.isRecordingVideo) {
|
||||
if (mc.cameraController == null || !mc.cameraController!.value.isRecordingVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -618,8 +636,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length ||
|
||||
mc.cameraController == null) {
|
||||
if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length || mc.cameraController == null) {
|
||||
return Container();
|
||||
}
|
||||
return StreamBuilder(
|
||||
|
|
@ -643,9 +660,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
_baseScaleFactor = mc.selectedCameraDetails.scaleFactor;
|
||||
});
|
||||
// Get the position of the pointer
|
||||
final renderBox =
|
||||
keyTriggerButton.currentContext!.findRenderObject()!
|
||||
as RenderBox;
|
||||
final renderBox = keyTriggerButton.currentContext!.findRenderObject()! as RenderBox;
|
||||
final localPosition = renderBox.globalToLocal(
|
||||
details.globalPosition,
|
||||
);
|
||||
|
|
@ -681,24 +696,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (!mc.isSharePreviewIsShown &&
|
||||
widget.sendToGroup != null &&
|
||||
!mc.isVideoRecording)
|
||||
if (!mc.isSharePreviewIsShown && widget.sendToGroup != null && !mc.isVideoRecording)
|
||||
ShowTitleText(
|
||||
title: widget.sendToGroup!.groupName,
|
||||
desc: context.lang.cameraPreviewSendTo,
|
||||
),
|
||||
if (!mc.isSharePreviewIsShown &&
|
||||
mc.sharedLinkForPreview != null &&
|
||||
!mc.isVideoRecording)
|
||||
if (!mc.isSharePreviewIsShown && mc.sharedLinkForPreview != null && !mc.isVideoRecording)
|
||||
ShowTitleText(
|
||||
title: mc.sharedLinkForPreview?.host ?? '',
|
||||
desc: 'Link',
|
||||
isLink: true,
|
||||
),
|
||||
if (!mc.isSharePreviewIsShown &&
|
||||
!mc.isVideoRecording &&
|
||||
!widget.hideControllers)
|
||||
if (!mc.isSharePreviewIsShown && !mc.isVideoRecording && !widget.hideControllers)
|
||||
CameraTopActions(
|
||||
selectedCameraDetails: mc.selectedCameraDetails,
|
||||
hasAudioPermission: _hasAudioPermission,
|
||||
|
|
@ -742,8 +751,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
videoRecordingStarted: _videoRecordingStarted,
|
||||
maxVideoRecordingTime: maxVideoRecordingTime,
|
||||
),
|
||||
if (!mc.isSharePreviewIsShown && widget.sendToGroup != null ||
|
||||
widget.hideControllers)
|
||||
if (!mc.isSharePreviewIsShown && widget.sendToGroup != null || widget.hideControllers)
|
||||
Positioned(
|
||||
left: 5,
|
||||
top: 10,
|
||||
|
|
|
|||
|
|
@ -87,8 +87,10 @@ class MainCameraController {
|
|||
|
||||
Future<void>? _initializeFuture;
|
||||
Future<void>? _pendingDisposal;
|
||||
int _cameraSessionId = 0;
|
||||
|
||||
Future<void> closeCamera() async {
|
||||
_cameraSessionId++;
|
||||
contactsVerified = {};
|
||||
scannedNewProfiles = {};
|
||||
scannedUrl = null;
|
||||
|
|
@ -119,11 +121,14 @@ class MainCameraController {
|
|||
}
|
||||
|
||||
Future<void> selectCamera(int sCameraId, bool init) async {
|
||||
await _pendingDisposal;
|
||||
initCameraStarted = true;
|
||||
final sessionId = ++_cameraSessionId;
|
||||
await _pendingDisposal;
|
||||
if (sessionId != _cameraSessionId) return;
|
||||
|
||||
if (AppEnvironment.cameras.isEmpty) {
|
||||
AppEnvironment.cameras = await availableCameras();
|
||||
if (sessionId != _cameraSessionId) return;
|
||||
}
|
||||
|
||||
var cameraId = sCameraId;
|
||||
|
|
@ -145,10 +150,13 @@ class MainCameraController {
|
|||
selectedCameraDetails.isZoomAble = false;
|
||||
|
||||
if (cameraController == null) {
|
||||
final hasMic = await Permission.microphone.isGranted;
|
||||
if (sessionId != _cameraSessionId) return;
|
||||
|
||||
cameraController = CameraController(
|
||||
AppEnvironment.cameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: await Permission.microphone.isGranted,
|
||||
enableAudio: hasMic,
|
||||
imageFormatGroup: Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888,
|
||||
);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ import 'package:flutter/material.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||
|
||||
class CameraZoomButtons extends StatefulWidget {
|
||||
String beautifulZoomScale(double scale) {
|
||||
var tmp = scale.toStringAsFixed(1);
|
||||
if (tmp[0] == '0') {
|
||||
tmp = tmp.substring(1, tmp.length);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
class CameraZoomButtons extends StatelessWidget {
|
||||
const CameraZoomButtons({
|
||||
required this.controller,
|
||||
required this.updateScaleFactor,
|
||||
|
|
@ -25,32 +33,10 @@ class CameraZoomButtons extends StatefulWidget {
|
|||
final Future<void> Function(int sCameraId, bool init) selectCamera;
|
||||
|
||||
@override
|
||||
State<CameraZoomButtons> createState() => _CameraZoomButtonsState();
|
||||
}
|
||||
|
||||
String beautifulZoomScale(double scale) {
|
||||
var tmp = scale.toStringAsFixed(1);
|
||||
if (tmp[0] == '0') {
|
||||
tmp = tmp.substring(1, tmp.length);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||
bool showWideAngleZoom = false;
|
||||
bool showWideAngleZoomIOS = false;
|
||||
bool _isDisposed = false;
|
||||
int? _wideCameraIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(initAsync());
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
|
||||
Widget build(BuildContext context) {
|
||||
final showWideAngleZoom = selectedCameraDetails.minAvailableZoom < 1;
|
||||
|
||||
int? wideCameraIndex;
|
||||
var index = AppEnvironment.cameras.indexWhere(
|
||||
(t) => t.lensType == CameraLensType.ultraWide,
|
||||
);
|
||||
|
|
@ -60,33 +46,13 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
);
|
||||
}
|
||||
if (index != -1) {
|
||||
_wideCameraIndex = index;
|
||||
wideCameraIndex = index;
|
||||
}
|
||||
|
||||
final isFront =
|
||||
widget.controller.description.lensDirection ==
|
||||
CameraLensDirection.front;
|
||||
final isFront = controller.description.lensDirection == CameraLensDirection.front;
|
||||
|
||||
if (!showWideAngleZoom &&
|
||||
Platform.isIOS &&
|
||||
_wideCameraIndex != null &&
|
||||
!isFront) {
|
||||
showWideAngleZoomIOS = true;
|
||||
} else {
|
||||
showWideAngleZoomIOS = false;
|
||||
}
|
||||
if (_isDisposed) return;
|
||||
setState(() {});
|
||||
}
|
||||
final showWideAngleZoomIOS = !showWideAngleZoom && Platform.isIOS && wideCameraIndex != null && !isFront;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true; // Set the flag to true when disposing
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final zoomButtonStyle = TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
foregroundColor: Colors.white,
|
||||
|
|
@ -97,24 +63,21 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
|
||||
const zoomTextStyle = TextStyle(fontSize: 13);
|
||||
final isSmallerFocused =
|
||||
widget.scaleFactor < 1 ||
|
||||
(showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||
scaleFactor < 1 || (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex);
|
||||
final isMiddleFocused =
|
||||
widget.scaleFactor >= 1 &&
|
||||
widget.scaleFactor < 2 &&
|
||||
!(showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||
scaleFactor >= 1 &&
|
||||
scaleFactor < 2 &&
|
||||
!(showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex);
|
||||
|
||||
final maxLevel = max(
|
||||
min(widget.selectedCameraDetails.maxAvailableZoom, 2),
|
||||
widget.scaleFactor,
|
||||
min(selectedCameraDetails.maxAvailableZoom, 2),
|
||||
scaleFactor,
|
||||
);
|
||||
|
||||
final minLevel = beautifulZoomScale(
|
||||
widget.selectedCameraDetails.minAvailableZoom,
|
||||
selectedCameraDetails.minAvailableZoom,
|
||||
);
|
||||
final currentLevel = beautifulZoomScale(widget.scaleFactor);
|
||||
final currentLevel = beautifulZoomScale(scaleFactor);
|
||||
return Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
|
|
@ -132,20 +95,18 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
),
|
||||
onPressed: () async {
|
||||
if (showWideAngleZoomIOS) {
|
||||
if (_wideCameraIndex != null) {
|
||||
await widget.selectCamera(_wideCameraIndex!, true);
|
||||
if (wideCameraIndex != null) {
|
||||
await selectCamera(wideCameraIndex, true);
|
||||
}
|
||||
} else {
|
||||
final level = await widget.controller.getMinZoomLevel();
|
||||
widget.updateScaleFactor(level);
|
||||
final level = await controller.getMinZoomLevel();
|
||||
updateScaleFactor(level);
|
||||
}
|
||||
},
|
||||
child: showWideAngleZoomIOS
|
||||
? const Text('0.5')
|
||||
: Text(
|
||||
widget.scaleFactor < 1
|
||||
? '${currentLevel}x'
|
||||
: '${minLevel}x',
|
||||
scaleFactor < 1 ? '${currentLevel}x' : '${minLevel}x',
|
||||
style: zoomTextStyle,
|
||||
),
|
||||
),
|
||||
|
|
@ -156,39 +117,33 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId ==
|
||||
_wideCameraIndex) {
|
||||
await widget.selectCamera(0, true);
|
||||
if (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex) {
|
||||
await selectCamera(0, true);
|
||||
} else {
|
||||
widget.updateScaleFactor(1.0);
|
||||
updateScaleFactor(1.0);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
isMiddleFocused
|
||||
? '${beautifulZoomScale(widget.scaleFactor)}x'
|
||||
: '1.0x',
|
||||
isMiddleFocused ? '${beautifulZoomScale(scaleFactor)}x' : '1.0x',
|
||||
style: zoomTextStyle,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: zoomButtonStyle.copyWith(
|
||||
foregroundColor: WidgetStateProperty.all(
|
||||
(widget.scaleFactor >= 2) ? Colors.yellow : Colors.white,
|
||||
(scaleFactor >= 2) ? Colors.yellow : Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final level = min(
|
||||
await widget.controller.getMaxZoomLevel(),
|
||||
await controller.getMaxZoomLevel(),
|
||||
2,
|
||||
).toDouble();
|
||||
|
||||
if (showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId ==
|
||||
_wideCameraIndex) {
|
||||
await widget.selectCamera(0, true);
|
||||
if (showWideAngleZoomIOS && selectedCameraDetails.cameraId == wideCameraIndex) {
|
||||
await selectCamera(0, true);
|
||||
}
|
||||
widget.updateScaleFactor(level);
|
||||
updateScaleFactor(level);
|
||||
},
|
||||
child: Text(
|
||||
'${beautifulZoomScale(maxLevel.toDouble())}x',
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: _allGroups.isEmpty
|
||||
? null
|
||||
: SizedBox(
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class UserCheckbox extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
Text(
|
||||
substringBy(group.groupName, 12),
|
||||
substringBy(group.groupName, 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -214,8 +214,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
|
||||
List<Widget> get actionsAtTheRight {
|
||||
if (layers.isNotEmpty &&
|
||||
(layers.first.isEditing ||
|
||||
(layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
||||
(layers.first.isEditing || (layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
||||
return [];
|
||||
}
|
||||
return <Widget>[
|
||||
|
|
@ -291,13 +290,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
if (media.type == MediaType.video) ...[
|
||||
const SizedBox(height: 8),
|
||||
ActionButton(
|
||||
(mediaService.removeAudio)
|
||||
? Icons.volume_off_rounded
|
||||
: Icons.volume_up_rounded,
|
||||
(mediaService.removeAudio) ? Icons.volume_off_rounded : Icons.volume_up_rounded,
|
||||
tooltipText: 'Enable Audio in Video',
|
||||
color: (mediaService.removeAudio)
|
||||
? Colors.white.withAlpha(160)
|
||||
: Colors.white,
|
||||
color: (mediaService.removeAudio) ? Colors.white.withAlpha(160) : Colors.white,
|
||||
onPressed: () async {
|
||||
await mediaService.toggleRemoveAudio();
|
||||
if (mediaService.removeAudio) {
|
||||
|
|
@ -335,9 +330,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
ActionButton(
|
||||
FontAwesomeIcons.shieldHeart,
|
||||
tooltipText: context.lang.protectAsARealTwonly,
|
||||
color: media.requiresAuthentication
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white,
|
||||
color: media.requiresAuthentication ? Theme.of(context).colorScheme.primary : Colors.white,
|
||||
onPressed: () async {
|
||||
await mediaService.setRequiresAuth(!media.requiresAuthentication);
|
||||
selectedGroupIds = HashSet();
|
||||
|
|
@ -383,8 +376,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
|
||||
List<Widget> get actionsAtTheTop {
|
||||
if (layers.isNotEmpty &&
|
||||
(layers.first.isEditing ||
|
||||
(layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
||||
(layers.first.isEditing || (layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
|
|
@ -474,6 +466,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
return (layers.first as BackgroundLayerData).image.image;
|
||||
}
|
||||
}
|
||||
if (layers.length == 2) {
|
||||
final filterLayer = layers[1];
|
||||
if (layers.first is BackgroundLayerData && filterLayer is FilterLayerData) {
|
||||
if (filterLayer.page == 1) {
|
||||
return (layers.first as BackgroundLayerData).image.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = false;
|
||||
|
|
@ -513,15 +513,15 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
}
|
||||
}
|
||||
ScreenshotImageHelper? image;
|
||||
var bytes = await widget.screenshotImage?.getBytes();
|
||||
if (media.type == MediaType.gif) {
|
||||
final bytes = await widget.screenshotImage?.getBytes();
|
||||
if (bytes != null) {
|
||||
mediaService.originalPath.writeAsBytesSync(bytes.toList());
|
||||
}
|
||||
} else {
|
||||
image = await getEditedImageBytes();
|
||||
if (image == null) return null;
|
||||
bytes = await image.getBytes();
|
||||
final bytes = await image.getBytes();
|
||||
if (bytes == null) {
|
||||
Log.error('imageBytes are empty');
|
||||
return null;
|
||||
|
|
@ -657,9 +657,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
await askToCloseThenClose();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: widget.sharedFromGallery
|
||||
? null
|
||||
: Colors.white.withAlpha(0),
|
||||
backgroundColor: widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: !_hasContacts
|
||||
? null
|
||||
: Padding(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class _GroupCreateSelectGroupNameViewState
|
|||
appBar: AppBar(
|
||||
title: Text(context.lang.selectGroupName),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
onPressed: (textFieldGroupName.text.isEmpty || _isLoading)
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
|
|||
: context.lang.addMember,
|
||||
),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
onPressed: selectedUsers.isEmpty ? null : submitChanges,
|
||||
label: Text(
|
||||
|
|
|
|||
|
|
@ -267,15 +267,17 @@ class HomeViewState extends State<HomeView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_offsetRatio == 0)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||
onTapDown: _mainCameraController.onTapDown,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: _offsetRatio == 0
|
||||
? GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||
onTapDown: _mainCameraController.onTapDown,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Positioned(
|
||||
key: const ValueKey('camera_controls'),
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class _SelectAdditionalUsers extends State<SelectAdditionalUsers> {
|
|||
appBar: AppBar(
|
||||
title: Text(context.lang.additionalUserSelectTitle),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
onPressed: selectedUsers.isEmpty
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ class _SelectAdditionalUsers extends State<SelectContactsView> {
|
|||
appBar: AppBar(
|
||||
title: Text(widget.text.title),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
onPressed: selectedUsers.isEmpty
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.2.16+125
|
||||
version: 0.2.17+126
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue