some more bug fixes

This commit is contained in:
otsmr 2025-07-19 12:59:13 +02:00
parent d55572f8bc
commit da39cdbb81
13 changed files with 171 additions and 157 deletions

View file

@ -1,14 +1,12 @@
name: Publish (Android) name: Publish on Github
on: on:
workflow_dispatch: {} workflow_dispatch: {}
push: push:
branches: branches:
- main - main
# paths: paths:
# - lib/** - pubspec.yaml
# - pubspec.lock
# - pubspec.yaml
jobs: jobs:
build_and_publish: build_and_publish:

View file

@ -8,7 +8,6 @@ late ApiService apiService;
late TwonlyDatabase twonlyDB; late TwonlyDatabase twonlyDB;
List<CameraDescription> gCameras = <CameraDescription>[]; List<CameraDescription> gCameras = <CameraDescription>[];
bool gIsDemoUser = false;
// The following global function can be called from anywhere to update // The following global function can be called from anywhere to update
// the UI when something changed. The callbacks will be set by // the UI when something changed. The callbacks will be set by

View file

@ -40,7 +40,7 @@ Future<ErrorCode?> isAllowedToSend() async {
var todaysImageCounter = user.todaysImageCounter; var todaysImageCounter = user.todaysImageCounter;
if (user.lastImageSend != null && user.todaysImageCounter != null) { if (user.lastImageSend != null && user.todaysImageCounter != null) {
if (isToday(user.lastImageSend!)) { if (isToday(user.lastImageSend!)) {
if (user.todaysImageCounter == 3) { if (user.todaysImageCounter == 10) {
return ErrorCode.PlanLimitReached; return ErrorCode.PlanLimitReached;
} }
todaysImageCounter = user.todaysImageCounter! + 1; todaysImageCounter = user.todaysImageCounter! + 1;

View file

@ -157,10 +157,6 @@ Future<void> encryptAndSendMessageAsync(
MessageJson msg, { MessageJson msg, {
PushNotification? pushNotification, PushNotification? pushNotification,
}) async { }) async {
if (gIsDemoUser) {
return;
}
Uint8List? pushData; Uint8List? pushData;
if (pushNotification != null) { if (pushNotification != null) {
pushData = await getPushData(userId, pushNotification); pushData = await getPushData(userId, pushNotification);

View file

@ -246,13 +246,17 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
await widget.cameraController?.pausePreview(); await widget.cameraController?.pausePreview();
if (!mounted) return; if (!mounted) {
return;
}
await widget.cameraController?.setFlashMode( if (Platform.isIOS) {
widget.selectedCameraDetails.isFlashOn // android has a problem with this. Flash is turned off in the pausePreview function.
? FlashMode.always await widget.cameraController?.setFlashMode(FlashMode.off);
: FlashMode.off); }
if (!mounted) return; if (!mounted) {
return;
}
imageBytes = widget.screenshotController imageBytes = widget.screenshotController
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio); .capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
@ -260,6 +264,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (await pushMediaEditor(imageBytes, null)) { if (await pushMediaEditor(imageBytes, null)) {
return; return;
} }
setState(() {
sharePreviewIsShown = false;
});
} }
Future<bool> pushMediaEditor( Future<bool> pushMediaEditor(
@ -404,17 +411,22 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
videoRecordingTimer?.cancel(); videoRecordingTimer?.cancel();
videoRecordingTimer = null; videoRecordingTimer = null;
} }
setState(() {
videoRecordingStarted = null;
isVideoRecording = false;
});
if (widget.cameraController == null || if (widget.cameraController == null ||
!widget.cameraController!.value.isRecordingVideo) { !widget.cameraController!.value.isRecordingVideo) {
return; return;
} }
try {
setState(() { setState(() {
videoRecordingStarted = null;
isVideoRecording = false;
sharePreviewIsShown = true; sharePreviewIsShown = true;
}); });
try {
File? videoPathFile; File? videoPathFile;
final videoPath = await widget.cameraController?.stopVideoRecording(); final videoPath = await widget.cameraController?.stopVideoRecording();
if (videoPath != null) { if (videoPath != null) {
@ -553,22 +565,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
setState(() {}); setState(() {});
}, },
), ),
// if (!isFront)
// ActionButton(
// Icons.hd_rounded,
// tooltipText: context.lang.toggleHighQuality,
// color: useHighQuality
// ? Colors.white
// : Colors.white.withAlpha(160),
// onPressed: () async {
// useHighQuality = !useHighQuality;
// setState(() {});
// await updateUserdata((user) {
// user.useHighQuality = useHighQuality;
// return user;
// });
// },
// ),
if (!hasAudioPermission) if (!hasAudioPermission)
ActionButton( ActionButton(
Icons.mic_off_rounded, Icons.mic_off_rounded,

View file

@ -78,6 +78,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
initAsync(); initAsync();
initMediaFileUpload(); initMediaFileUpload();
layers.add(FilterLayerData()); layers.add(FilterLayerData());
if (widget.sendTo != null) {
selectedUserIds.add(widget.sendTo!.userId);
}
if (widget.imageBytes != null) { if (widget.imageBytes != null) {
loadImage(widget.imageBytes!); loadImage(widget.imageBytes!);
} else if (widget.videoFilePath != null) { } else if (widget.videoFilePath != null) {

View file

@ -18,7 +18,7 @@ import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/add_new_user.view.dart'; 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/feedback_btn.dart';
import 'package:twonly/src/views/chats/chat_list_components/last_message_time.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';
@ -29,7 +29,6 @@ import 'package:twonly/src/views/components/message_send_state_icon.dart';
import 'package:twonly/src/views/components/notification_badge.dart'; import 'package:twonly/src/views/components/notification_badge.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/views/settings/help/changelog.view.dart'; import 'package:twonly/src/views/settings/help/changelog.view.dart';
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
import 'package:twonly/src/views/settings/settings_main.view.dart'; import 'package:twonly/src/views/settings/settings_main.view.dart';
import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart';
@ -77,10 +76,6 @@ class _ChatListViewState extends State<ChatListView> {
final user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
setState(() {
showFeedbackShortcut = user.showFeedbackShortcut;
});
final changeLog = await rootBundle.loadString('CHANGELOG.md'); final changeLog = await rootBundle.loadString('CHANGELOG.md');
final changeLogHash = final changeLogHash =
(await compute(Sha256().hash, changeLog.codeUnits)).bytes; (await compute(Sha256().hash, changeLog.codeUnits)).bytes;
@ -91,6 +86,9 @@ class _ChatListViewState extends State<ChatListView> {
return u; return u;
}); });
if (!mounted) return; if (!mounted) return;
// only show changelog to people who already have contacts
// this prevents that this is shown directly after the user registered
if (_contacts.isNotEmpty) {
await Navigator.push(context, MaterialPageRoute(builder: (context) { await Navigator.push(context, MaterialPageRoute(builder: (context) {
return ChangeLogView( return ChangeLogView(
changeLog: changeLog, changeLog: changeLog,
@ -98,6 +96,7 @@ class _ChatListViewState extends State<ChatListView> {
})); }));
} }
} }
}
@override @override
void dispose() { void dispose() {
@ -139,20 +138,7 @@ class _ChatListViewState extends State<ChatListView> {
), ),
]), ]),
actions: [ actions: [
if (showFeedbackShortcut) const FeedbackIconButton(),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ContactUsView(),
),
);
},
color: Colors.grey,
tooltip: context.lang.feedbackTooltip,
icon: const FaIcon(FontAwesomeIcons.commentDots, size: 19),
),
StreamBuilder( StreamBuilder(
stream: twonlyDB.contactsDao.watchContactsRequested(), stream: twonlyDB.contactsDao.watchContactsRequested(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -226,7 +212,6 @@ class _ChatListViewState extends State<ChatListView> {
child: ListView.builder( child: ListView.builder(
itemCount: _pinnedContacts.length + itemCount: _pinnedContacts.length +
(_pinnedContacts.isNotEmpty ? 1 : 0) + (_pinnedContacts.isNotEmpty ? 1 : 0) +
(gIsDemoUser ? 1 : 0) +
_contacts.length + _contacts.length +
1, 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -234,20 +219,13 @@ class _ChatListViewState extends State<ChatListView> {
return const BackupNoticeCard(); return const BackupNoticeCard();
} }
index -= 1; index -= 1;
if (gIsDemoUser) {
if (index == 0) {
return const DemoUserCard();
}
index -= 1;
}
// Check if the index is for the pinned users // Check if the index is for the pinned users
if (index < _pinnedContacts.length) { if (index < _pinnedContacts.length) {
final contact = _pinnedContacts[index]; final contact = _pinnedContacts[index];
return UserListItem( return UserListItem(
key: ValueKey(contact.userId), key: ValueKey(contact.userId),
user: contact, user: contact,
firstUserListItemKey: (index == 0 && !gIsDemoUser || firstUserListItemKey: (index == 0 || index == 1)
index == 1 && gIsDemoUser)
? firstUserListItemKey ? firstUserListItemKey
: null, : null,
); );
@ -381,6 +359,45 @@ class _UserListItem extends State<UserListItem> {
}); });
} }
Future<void> onTap() async {
if (currentMessage == null) {
await Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.user);
},
));
return;
}
final msgs =
previewMessages.where((x) => x.kind == MessageKind.media).toList();
if (msgs.isNotEmpty &&
msgs.first.kind == MessageKind.media &&
msgs.first.messageOtherId != null &&
msgs.first.openedAt == null) {
switch (msgs.first.downloadState) {
case DownloadState.pending:
await startDownloadMedia(msgs.first, true);
return;
case DownloadState.downloaded:
await Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return MediaViewerView(widget.user);
}),
);
return;
case DownloadState.downloading:
return;
}
}
await Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return ChatMessagesView(widget.user);
}),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final flameCounter = getFlameCounterFromContact(widget.user); final flameCounter = getFlameCounterFromContact(widget.user);
@ -433,48 +450,12 @@ class _UserListItem extends State<UserListItem> {
}, },
)); ));
}, },
icon: FaIcon(FontAwesomeIcons.camera, icon: FaIcon(
color: context.color.outline.withAlpha(150)), FontAwesomeIcons.camera,
color: context.color.outline.withAlpha(150),
), ),
onTap: () { ),
if (currentMessage == null) { onTap: onTap,
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.user);
},
));
return;
}
final msgs = previewMessages
.where((x) => x.kind == MessageKind.media)
.toList();
if (msgs.isNotEmpty &&
msgs.first.kind == MessageKind.media &&
msgs.first.messageOtherId != null &&
msgs.first.openedAt == null) {
switch (msgs.first.downloadState) {
case DownloadState.pending:
startDownloadMedia(msgs.first, true);
return;
case DownloadState.downloaded:
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return MediaViewerView(widget.user);
}),
);
return;
case DownloadState.downloading:
return;
}
}
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return ChatMessagesView(widget.user);
}),
);
},
), ),
) )
], ],

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/settings/backup/backup.view.dart'; import 'package:twonly/src/views/settings/backup/backup.view.dart';
@ -26,12 +25,14 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
if (user != null && if (user != null &&
(user.nextTimeToShowBackupNotice == null || (user.nextTimeToShowBackupNotice == null ||
DateTime.now().isAfter(user.nextTimeToShowBackupNotice!))) { DateTime.now().isAfter(user.nextTimeToShowBackupNotice!))) {
if (!gIsDemoUser && (user.twonlySafeBackup == null)) { if (user.twonlySafeBackup == null) {
showBackupNotice = true; showBackupNotice = true;
} }
} }
if (mounted) {
setState(() {}); setState(() {});
} }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class ConnectionInfo extends StatefulWidget { class ConnectionInfo extends StatefulWidget {
@ -61,7 +60,7 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!showAnimation || gIsDemoUser) return Container(); if (!showAnimation) return Container();
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return SizedBox( return SizedBox(

View file

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
class FeedbackIconButton extends StatefulWidget {
const FeedbackIconButton({super.key});
@override
State<FeedbackIconButton> createState() => _FeedbackIconButtonState();
}
class _FeedbackIconButtonState extends State<FeedbackIconButton> {
bool showFeedbackShortcut = false;
@override
void initState() {
super.initState();
initAsync();
}
Future<void> initAsync() async {
final user = await getUser();
if (user == null || !mounted) return;
setState(() {
showFeedbackShortcut = user.showFeedbackShortcut;
});
}
@override
Widget build(BuildContext context) {
if (!showFeedbackShortcut) {
return const SizedBox.shrink();
}
return IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ContactUsView(),
),
);
},
color: Colors.grey,
tooltip: context.lang.feedbackTooltip,
icon: const FaIcon(FontAwesomeIcons.commentDots, size: 19),
);
}
}

View file

@ -51,6 +51,7 @@ class HomeViewState extends State<HomeView> {
double buttonDiameter = 100; double buttonDiameter = 100;
double offsetRatio = 0; double offsetRatio = 0;
double offsetFromOne = 0; double offsetFromOne = 0;
double lastChange = 0;
Timer? disableCameraTimer; Timer? disableCameraTimer;
bool initCameraStarted = true; bool initCameraStarted = true;
@ -62,6 +63,8 @@ class HomeViewState extends State<HomeView> {
bool onPageView(ScrollNotification notification) { bool onPageView(ScrollNotification notification) {
disableCameraTimer?.cancel(); disableCameraTimer?.cancel();
if (notification.depth == 0 && notification is ScrollUpdateNotification) { if (notification.depth == 0 && notification is ScrollUpdateNotification) {
final page = homeViewPageController.page ?? 0;
lastChange = page;
setState(() { setState(() {
offsetFromOne = 1.0 - (homeViewPageController.page ?? 0); offsetFromOne = 1.0 - (homeViewPageController.page ?? 0);
offsetRatio = offsetFromOne.abs(); offsetRatio = offsetFromOne.abs();

View file

@ -33,14 +33,14 @@ class _RegisterViewState extends State<RegisterView> {
bool _isValidUserName = false; bool _isValidUserName = false;
bool _showUserNameError = false; bool _showUserNameError = false;
Future<void> createNewUser({bool isDemoAccount = false}) async { Future<void> createNewUser() async {
if (!_isValidUserName) { if (!_isValidUserName) {
setState(() { setState(() {
_showUserNameError = true; _showUserNameError = true;
}); });
return; return;
} }
final username = isDemoAccount ? '<demo>' : usernameController.text; final username = usernameController.text;
final inviteCode = inviteCodeController.text; final inviteCode = inviteCodeController.text;
setState(() { setState(() {
@ -52,7 +52,6 @@ class _RegisterViewState extends State<RegisterView> {
var userId = 0; var userId = 0;
if (!isDemoAccount) {
final res = await apiService.register(username, inviteCode); final res = await apiService.register(username, inviteCode);
if (res.isSuccess) { if (res.isSuccess) {
Log.info('Got user_id ${res.value} from server'); Log.info('Got user_id ${res.value} from server');
@ -75,7 +74,6 @@ class _RegisterViewState extends State<RegisterView> {
} }
return; return;
} }
}
setState(() { setState(() {
_isTryingToRegister = false; _isTryingToRegister = false;
@ -86,18 +84,13 @@ class _RegisterViewState extends State<RegisterView> {
username: username, username: username,
displayName: username, displayName: username,
subscriptionPlan: 'Preview', subscriptionPlan: 'Preview',
isDemoUser: isDemoAccount, isDemoUser: false,
); );
await const FlutterSecureStorage() await const FlutterSecureStorage()
.write(key: SecureStorageKeys.userData, value: jsonEncode(userData)); .write(key: SecureStorageKeys.userData, value: jsonEncode(userData));
if (!isDemoAccount) {
await apiService.authenticate(); await apiService.authenticate();
} else {
gIsDemoUser = true;
await createFakeDemoData();
}
widget.callbackOnSuccess(); widget.callbackOnSuccess();
} }
@ -228,12 +221,6 @@ class _RegisterViewState extends State<RegisterView> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
// OutlinedButton.icon(
// onPressed: () {
// createNewUser(isDemoAccount: true);
// },
// label: const Text('Demo'),
// ),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: () { onPressed: () {
Navigator.push(context, MaterialPageRoute( Navigator.push(context, MaterialPageRoute(

View file

@ -30,8 +30,8 @@ class NotificationView extends StatelessWidget {
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc), subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
onTap: () async { onTap: () async {
await initFCMAfterAuthenticated(); await initFCMAfterAuthenticated();
final storedToken = const FlutterSecureStorage() final storedToken = await (const FlutterSecureStorage()
.read(key: SecureStorageKeys.googleFcm) as String?; .read(key: SecureStorageKeys.googleFcm));
await setupNotificationWithUsers(force: true); await setupNotificationWithUsers(force: true);
if (!context.mounted) return; if (!context.mounted) return;