multiple bug fixes

This commit is contained in:
otsmr 2025-07-19 02:31:07 +02:00
parent 0ec9e18046
commit 5209825604
15 changed files with 125 additions and 88 deletions

View file

@ -4,7 +4,7 @@ on:
workflow_dispatch: {} workflow_dispatch: {}
push: push:
branches: branches:
- main - main_ignore
# paths: # paths:
# - lib/** # - lib/**
# - pubspec.lock # - pubspec.lock

11
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"configurations": [
{
"name": "Flutter",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "profile"
}
]
}

View file

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 77; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -401,14 +401,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@ -497,14 +493,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";

View file

@ -220,7 +220,7 @@ String getContactDisplayName(Contact user) {
name = applyStrikethrough(name); name = applyStrikethrough(name);
} }
if (name.length > 12) { if (name.length > 12) {
return '${name.substring(0, 12)} ...'; return '${name.substring(0, 12)}...';
} }
return name; return name;
} }

View file

@ -81,9 +81,13 @@ Future<void> initFileDownloader() async {
await FileDownloader().start(); await FileDownloader().start();
await FileDownloader().configure(androidConfig: [ try {
(Config.bypassTLSCertificateValidation, kDebugMode), await FileDownloader().configure(androidConfig: [
]); (Config.bypassTLSCertificateValidation, kDebugMode),
]);
} catch (e) {
Log.error(e);
}
if (kDebugMode) { if (kDebugMode) {
FileDownloader().configureNotification( FileDownloader().configureNotification(

View file

@ -10,6 +10,8 @@ import 'package:twonly/src/constants/secure_storage_keys.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart'
show gMediaShowInfinite;
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
@ -30,7 +32,7 @@ Future<void> customLocalPushNotification(String title, String msg) async {
); );
await flutterLocalNotificationsPlugin.show( await flutterLocalNotificationsPlugin.show(
999999 + Random.secure().nextInt(9999), gMediaShowInfinite + Random.secure().nextInt(9999),
title, title,
msg, msg,
notificationDetails, notificationDetails,

View file

@ -22,7 +22,8 @@ class CameraZoomButtons extends StatefulWidget {
final double scaleFactor; final double scaleFactor;
final Function updateScaleFactor; final Function updateScaleFactor;
final SelectedCameraDetails selectedCameraDetails; final SelectedCameraDetails selectedCameraDetails;
final void Function(int sCameraId, bool init, bool enableAudio) selectCamera; final Future<void> Function(int sCameraId, bool init, bool enableAudio)
selectCamera;
@override @override
State<CameraZoomButtons> createState() => _CameraZoomButtonsState(); State<CameraZoomButtons> createState() => _CameraZoomButtonsState();
@ -78,6 +79,15 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
final isMiddleFocused = widget.scaleFactor >= 1 && final isMiddleFocused = widget.scaleFactor >= 1 &&
widget.scaleFactor < 2 && widget.scaleFactor < 2 &&
!(showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == 2); !(showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == 2);
final maxLevel = max(
min(widget.selectedCameraDetails.maxAvailableZoom, 2),
widget.scaleFactor,
);
final minLevel =
beautifulZoomScale(widget.selectedCameraDetails.minAvailableZoom);
final currentLevel = beautifulZoomScale(widget.scaleFactor);
return Center( return Center(
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),
@ -95,7 +105,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
), ),
onPressed: () async { onPressed: () async {
if (showWideAngleZoomIOS) { if (showWideAngleZoomIOS) {
widget.selectCamera(2, true, false); await widget.selectCamera(2, true, false);
} else { } else {
final level = await widget.controller.getMinZoomLevel(); final level = await widget.controller.getMinZoomLevel();
widget.updateScaleFactor(level); widget.updateScaleFactor(level);
@ -103,23 +113,11 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
}, },
child: showWideAngleZoomIOS child: showWideAngleZoomIOS
? const Text('0.5') ? const Text('0.5')
: FutureBuilder( : Text(
future: widget.controller.getMinZoomLevel(), widget.scaleFactor < 1
builder: (context, snap) { ? '${currentLevel}x'
if (snap.hasData) { : '${minLevel}x',
final minLevel = beautifulZoomScale(snap.data!); style: zoomTextStyle,
final currentLevel =
beautifulZoomScale(widget.scaleFactor);
return Text(
widget.scaleFactor < 1
? '${currentLevel}x'
: '${minLevel}x',
style: zoomTextStyle,
);
} else {
return const Text('');
}
},
), ),
), ),
TextButton( TextButton(
@ -128,10 +126,10 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
isMiddleFocused ? Colors.yellow : Colors.white, isMiddleFocused ? Colors.yellow : Colors.white,
), ),
), ),
onPressed: () { onPressed: () async {
if (showWideAngleZoomIOS && if (showWideAngleZoomIOS &&
widget.selectedCameraDetails.cameraId == 2) { widget.selectedCameraDetails.cameraId == 2) {
widget.selectCamera(0, true, false); await widget.selectCamera(0, true, false);
} else { } else {
widget.updateScaleFactor(1.0); widget.updateScaleFactor(1.0);
} }
@ -154,21 +152,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
.toDouble(); .toDouble();
widget.updateScaleFactor(level); widget.updateScaleFactor(level);
}, },
child: FutureBuilder( child: Text('${beautifulZoomScale(maxLevel.toDouble())}x',
future: widget.controller.getMaxZoomLevel(), style: zoomTextStyle),
builder: (context, snap) {
if (snap.hasData) {
final maxLevel = max(
min((snap.data?.toInt())!, 2),
widget.scaleFactor,
);
return Text(
'${beautifulZoomScale(maxLevel.toDouble())}x',
style: zoomTextStyle);
} else {
return const Text('');
}
}),
) )
], ],
), ),

View file

@ -92,7 +92,8 @@ class CameraPreviewControllerView extends StatelessWidget {
this.sendTo, this.sendTo,
}); });
final Contact? sendTo; final Contact? sendTo;
final void Function(int sCameraId, bool init, bool enableAudio) selectCamera; final Future<CameraController?> Function(
int sCameraId, bool init, bool enableAudio) selectCamera;
final CameraController? cameraController; final CameraController? cameraController;
final SelectedCameraDetails selectedCameraDetails; final SelectedCameraDetails selectedCameraDetails;
final ScreenshotController screenshotController; final ScreenshotController screenshotController;
@ -135,7 +136,8 @@ class CameraPreviewView extends StatefulWidget {
this.sendTo, this.sendTo,
}); });
final Contact? sendTo; final Contact? sendTo;
final void Function(int sCameraId, bool init, bool enableAudio) selectCamera; final Future<CameraController?> Function(
int sCameraId, bool init, bool enableAudio) selectCamera;
final CameraController? cameraController; final CameraController? cameraController;
final SelectedCameraDetails selectedCameraDetails; final SelectedCameraDetails selectedCameraDetails;
final ScreenshotController screenshotController; final ScreenshotController screenshotController;
@ -299,7 +301,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
return true; return true;
} }
widget.selectCamera(widget.selectedCameraDetails.cameraId, false, false); await widget.selectCamera(
widget.selectedCameraDetails.cameraId, false, false);
return false; return false;
} }
@ -355,8 +358,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
widget.cameraController!.value.isRecordingVideo) { widget.cameraController!.value.isRecordingVideo) {
return; return;
} }
var cameraController = widget.cameraController;
if (hasAudioPermission && videoWithAudio) { if (hasAudioPermission && videoWithAudio) {
widget.selectCamera( cameraController = await widget.selectCamera(
widget.selectedCameraDetails.cameraId, widget.selectedCameraDetails.cameraId,
false, false,
await Permission.microphone.isGranted && videoWithAudio, await Permission.microphone.isGranted && videoWithAudio,
@ -368,7 +372,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
}); });
try { try {
await widget.cameraController?.startVideoRecording(); await cameraController?.startVideoRecording();
videoRecordingTimer = videoRecordingTimer =
Timer.periodic(const Duration(milliseconds: 15), (timer) { Timer.periodic(const Duration(milliseconds: 15), (timer) {
setState(() { setState(() {
@ -522,7 +526,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Icons.repeat_rounded, Icons.repeat_rounded,
tooltipText: context.lang.switchFrontAndBackCamera, tooltipText: context.lang.switchFrontAndBackCamera,
onPressed: () async { onPressed: () async {
widget.selectCamera( await widget.selectCamera(
(widget.selectedCameraDetails.cameraId + 1) % 2, (widget.selectedCameraDetails.cameraId + 1) % 2,
false, false,
false); false);

View file

@ -31,7 +31,8 @@ class CameraSendToViewState extends State<CameraSendToView> {
super.dispose(); super.dispose();
} }
Future<void> selectCamera(int sCameraId, bool init, bool enableAudio) async { Future<CameraController?> selectCamera(
int sCameraId, bool init, bool enableAudio) async {
final opts = await initializeCameraController( final opts = await initializeCameraController(
selectedCameraDetails, sCameraId, init, enableAudio); selectedCameraDetails, sCameraId, init, enableAudio);
if (opts != null) { if (opts != null) {
@ -39,6 +40,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
cameraController = opts.$2; cameraController = opts.$2;
} }
setState(() {}); setState(() {});
return cameraController;
} }
Future<void> toggleSelectedCamera() async { Future<void> toggleSelectedCamera() async {

View file

@ -193,12 +193,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
NotificationBadge( NotificationBadge(
count: (widget.videoFilePath != null) count: (widget.videoFilePath != null)
? '0' ? '0'
: maxShowTime == 999999 : maxShowTime == gMediaShowInfinite
? '' ? ''
: maxShowTime.toString(), : maxShowTime.toString(),
child: ActionButton( child: ActionButton(
(widget.videoFilePath != null) (widget.videoFilePath != null)
? maxShowTime == 999999 ? maxShowTime == gMediaShowInfinite
? Icons.repeat_rounded ? Icons.repeat_rounded
: Icons.repeat_one_rounded : Icons.repeat_one_rounded
: Icons.timer_outlined, : Icons.timer_outlined,

View file

@ -19,6 +19,7 @@ import 'package:twonly/src/views/chats/add_new_user.view.dart';
import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart'; import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart';
import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart'; import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart';
import 'package:twonly/src/views/chats/chat_list_components/demo_user.card.dart'; import 'package:twonly/src/views/chats/chat_list_components/demo_user.card.dart';
import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart';
import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart';
import 'package:twonly/src/views/chats/media_viewer.view.dart'; import 'package:twonly/src/views/chats/media_viewer.view.dart';
import 'package:twonly/src/views/chats/start_new_chat.view.dart'; import 'package:twonly/src/views/chats/start_new_chat.view.dart';
@ -309,7 +310,6 @@ class UserListItem extends StatefulWidget {
} }
class _UserListItem extends State<UserListItem> { class _UserListItem extends State<UserListItem> {
int lastMessageInSeconds = 0;
MessageSendState state = MessageSendState.send; MessageSendState state = MessageSendState.send;
Message? currentMessage; Message? currentMessage;
@ -321,18 +321,14 @@ class _UserListItem extends State<UserListItem> {
List<Message> previewMessages = []; List<Message> previewMessages = [];
Timer? updateTime;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initStreams(); initStreams();
lastUpdateTime();
} }
@override @override
void dispose() { void dispose() {
updateTime?.cancel();
messagesNotOpenedStream.cancel(); messagesNotOpenedStream.cancel();
lastMessageStream.cancel(); lastMessageStream.cancel();
super.dispose(); super.dispose();
@ -385,22 +381,6 @@ class _UserListItem extends State<UserListItem> {
}); });
} }
void lastUpdateTime() {
// Change the color every 200 milliseconds
updateTime = Timer.periodic(const Duration(milliseconds: 200), (timer) {
setState(() {
if (currentMessage != null) {
lastMessageInSeconds = DateTime.now()
.difference(currentMessage!.openedAt ?? currentMessage!.sendAt)
.inSeconds;
if (lastMessageInSeconds < 0) {
lastMessageInSeconds = 0;
}
}
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final flameCounter = getFlameCounterFromContact(widget.user); final flameCounter = getFlameCounterFromContact(widget.user);
@ -432,10 +412,8 @@ class _UserListItem extends State<UserListItem> {
MessageSendStateIcon(previewMessages), MessageSendStateIcon(previewMessages),
const Text(''), const Text(''),
const SizedBox(width: 5), const SizedBox(width: 5),
Text( if (currentMessage != null)
formatDuration(lastMessageInSeconds), LastMessageTime(message: currentMessage!),
style: const TextStyle(fontSize: 12),
),
if (flameCounter > 0) if (flameCounter > 0)
FlameCounterWidget( FlameCounterWidget(
widget.user, widget.user,

View file

@ -0,0 +1,49 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
class LastMessageTime extends StatefulWidget {
const LastMessageTime({required this.message, super.key});
final Message message;
@override
State<LastMessageTime> createState() => _LastMessageTimeState();
}
class _LastMessageTimeState extends State<LastMessageTime> {
Timer? updateTime;
int lastMessageInSeconds = 0;
@override
void initState() {
super.initState();
// Change the color every 200 milliseconds
updateTime = Timer.periodic(const Duration(milliseconds: 500), (timer) {
setState(() {
lastMessageInSeconds = DateTime.now()
.difference(widget.message.openedAt ?? widget.message.sendAt)
.inSeconds;
if (lastMessageInSeconds < 0) {
lastMessageInSeconds = 0;
}
});
});
}
@override
void dispose() {
updateTime?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text(
formatDuration(lastMessageInSeconds),
style: const TextStyle(fontSize: 12),
);
}
}

View file

@ -8,6 +8,8 @@ import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart'; import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
import 'package:twonly/src/services/api/media_download.dart' as received; import 'package:twonly/src/services/api/media_download.dart' as received;
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart'; import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
import 'package:twonly/src/views/chats/media_viewer.view.dart'; import 'package:twonly/src/views/chats/media_viewer.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart';
@ -46,6 +48,12 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
widget.message.mediaStored) { widget.message.mediaStored) {
return; return;
} }
final content = getMediaContent(widget.message);
if (content == null ||
content.isRealTwonly ||
content.maxShowTime != gMediaShowInfinite) {
return;
}
if (await received.existsMediaFile(widget.message.messageId, 'png')) { if (await received.existsMediaFile(widget.message.messageId, 'png')) {
if (mounted) { if (mounted) {
setState(() { setState(() {

View file

@ -53,7 +53,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
VideoPlayerController? videoController; VideoPlayerController? videoController;
DateTime? canBeSeenUntil; DateTime? canBeSeenUntil;
int maxShowTime = 999999; int maxShowTime = gMediaShowInfinite;
double progress = 0; double progress = 0;
bool isRealTwonly = false; bool isRealTwonly = false;
bool mirrorVideo = false; bool mirrorVideo = false;
@ -157,7 +157,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
videoController = null; videoController = null;
imageBytes = null; imageBytes = null;
canBeSeenUntil = null; canBeSeenUntil = null;
maxShowTime = 999999; maxShowTime = gMediaShowInfinite;
imageSaving = false; imageSaving = false;
imageSaved = false; imageSaved = false;
mirrorVideo = false; mirrorVideo = false;

View file

@ -108,7 +108,8 @@ class HomeViewState extends State<HomeView> {
super.dispose(); super.dispose();
} }
Future<void> selectCamera(int sCameraId, bool init, bool enableAudio) async { Future<CameraController?> selectCamera(
int sCameraId, bool init, bool enableAudio) async {
final opts = await initializeCameraController( final opts = await initializeCameraController(
selectedCameraDetails, sCameraId, init, enableAudio); selectedCameraDetails, sCameraId, init, enableAudio);
if (opts != null) { if (opts != null) {
@ -117,6 +118,7 @@ class HomeViewState extends State<HomeView> {
initCameraStarted = false; initCameraStarted = false;
} }
setState(() {}); setState(() {});
return cameraController;
} }
Future<void> toggleSelectedCamera() async { Future<void> toggleSelectedCamera() async {