fix multiple issues

This commit is contained in:
otsmr 2025-11-07 17:44:51 +01:00
parent 95c9db86d6
commit da4917ad46
33 changed files with 125 additions and 290 deletions

View file

@ -55,6 +55,9 @@ android {
debug {
applicationIdSuffix ".testing"
}
// profile {
// applicationIdSuffix ".STOP"
// }
release {
signingConfig signingConfigs.release
}

View file

@ -6,4 +6,6 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application android:usesCleartextTraffic="true" >
</application>
</manifest>

View file

@ -21,6 +21,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/fcm.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/storage.dart';
@ -63,13 +64,7 @@ void main() async {
unawaited(createPushAvatars());
await twonlyDB.messagesDao.purgeMessageTable();
// await twonlyDB.messagesDao.resetPendingDownloadState();
// await twonlyDB.messageRetransmissionDao.purgeOldRetransmissions();
// await twonlyDB.signalDao.purgeOutDatedPreKeys();
// unawaited(purgeSendMediaFiles());
// unawaited(performTwonlySafeBackup());
unawaited(performTwonlySafeBackup());
runApp(
MultiProvider(

View file

@ -109,6 +109,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
..where(
contacts.requested.equals(true) &
contacts.accepted.equals(false) &
contacts.deletedByUser.equals(false) &
contacts.blocked.equals(false),
)
..addColumns([count]);

View file

@ -281,7 +281,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
..where((t) => t.groupId.equals(groupId)))
.getSingle();
final totalMediaCounter = group.totalMediaCounter + 1;
final totalMediaCounter = group.totalMediaCounter + (received ? 0 : 1);
var flameCounter = group.flameCounter;
var maxFlameCounter = group.maxFlameCounter;
var maxFlameCounterFrom = group.maxFlameCounterFrom;
@ -321,7 +321,11 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
if (updateFlame) {
flameCounter += 1;
lastFlameCounterChange = Value(timestamp);
if ((flameCounter + 1) >= maxFlameCounter) {
// Overwrite max flame counter either the current is bigger or the th max flame counter is older then 4 days
if ((flameCounter + 1) >= maxFlameCounter ||
maxFlameCounterFrom == null ||
maxFlameCounterFrom
.isBefore(DateTime.now().subtract(const Duration(days: 5)))) {
maxFlameCounter = flameCounter + 1;
maxFlameCounterFrom = DateTime.now();
}
@ -351,6 +355,15 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
);
}
Stream<int> watchSumTotalMediaCounter() {
final query = selectOnly(groups)
..addColumns([groups.totalMediaCounter.sum()]);
return query.watch().map((rows) {
final expr = rows.first.read(groups.totalMediaCounter.sum());
return expr ?? 0;
});
}
Future<void> increaseLastMessageExchange(
String groupId,
DateTime newLastMessage,

View file

@ -816,5 +816,6 @@
"deleteChatAfterADay": "einem Tag.",
"deleteChatAfterAWeek": "einer Woche.",
"deleteChatAfterAMonth": "einem Monat.",
"deleteChatAfterAYear": "einem Jahr."
"deleteChatAfterAYear": "einem Jahr.",
"yourTwonlyScore": "Dein twonly-Score"
}

View file

@ -594,5 +594,6 @@
"deleteChatAfterADay": "one day.",
"deleteChatAfterAWeek": "one week.",
"deleteChatAfterAMonth": "one month.",
"deleteChatAfterAYear": "one year."
"deleteChatAfterAYear": "one year.",
"yourTwonlyScore": "Your twonly-Score"
}

View file

@ -2671,6 +2671,12 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'one year.'**
String get deleteChatAfterAYear;
/// No description provided for @yourTwonlyScore.
///
/// In en, this message translates to:
/// **'Your twonly-Score'**
String get yourTwonlyScore;
}
class _AppLocalizationsDelegate

View file

@ -1474,4 +1474,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get deleteChatAfterAYear => 'einem Jahr.';
@override
String get yourTwonlyScore => 'Dein twonly-Score';
}

View file

@ -1464,4 +1464,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get deleteChatAfterAYear => 'one year.';
@override
String get yourTwonlyScore => 'Your twonly-Score';
}

View file

@ -51,8 +51,8 @@ final lockRetransStore = Mutex();
/// errors or network changes.
class ApiService {
ApiService();
final String apiHost = kDebugMode ? '10.99.0.140:3030' : 'api.twonly.eu';
final String apiSecure = kDebugMode ? '' : 's';
final String apiHost = kReleaseMode ? 'api.twonly.eu' : '10.99.0.140:3030';
final String apiSecure = kReleaseMode ? 's' : '';
bool appIsOutdated = false;
bool isAuthenticated = false;

View file

@ -35,15 +35,15 @@ Future<void> initFileDownloader() async {
try {
var androidConfig = [];
if (kDebugMode) {
androidConfig = [(Config.bypassTLSCertificateValidation, kDebugMode)];
if (!kReleaseMode) {
androidConfig = [(Config.bypassTLSCertificateValidation, true)];
}
await FileDownloader().configure(androidConfig: androidConfig);
} catch (e) {
Log.error(e);
}
if (kDebugMode) {
if (!kReleaseMode) {
FileDownloader().configureNotification(
running: const TaskNotification(
'Uploading/Downloading',

View file

@ -23,9 +23,10 @@ Future<void> customLocalPushNotification(String title, String msg) async {
'1',
'System',
channelDescription: 'System messages.',
importance: Importance.max,
priority: Priority.max,
importance: Importance.high,
priority: Priority.high,
styleInformation: BigTextStyleInformation(msg),
icon: 'ic_launcher_foreground',
);
const darwinNotificationDetails = DarwinNotificationDetails();
@ -34,8 +35,10 @@ Future<void> customLocalPushNotification(String title, String msg) async {
iOS: darwinNotificationDetails,
);
final id = Random.secure().nextInt(9999);
await flutterLocalNotificationsPlugin.show(
Random.secure().nextInt(9999),
id,
title,
msg,
notificationDetails,
@ -95,11 +98,11 @@ Future<void> handlePushData(String pushDataB64) async {
}
}
} catch (e) {
Log.error(e);
await customLocalPushNotification(
'Du hast eine neue Nachricht.',
'Öffne twonly um mehr zu erfahren.',
);
Log.error(e);
}
}
@ -161,6 +164,7 @@ Future<void> showLocalPushNotification(
priority: Priority.max,
ticker: 'You got a new message.',
largeIcon: styleInformation,
icon: 'ic_launcher_foreground',
);
const darwinNotificationDetails = DarwinNotificationDetails();
@ -174,7 +178,7 @@ Future<void> showLocalPushNotification(
title,
body,
notificationDetails,
payload: pushNotification.kind.name,
// payload: pushNotification.kind.name,
);
}

View file

@ -51,7 +51,7 @@ Future<void> setupNotificationWithUsers({
final pushUser =
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
if (pushUser != null) {
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
// make it harder to predict the change of the key
final timeBefore =
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));

View file

@ -9,7 +9,7 @@ void initLogger() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) async {
await _writeLogToFile(record);
if (kDebugMode) {
if (!kReleaseMode) {
print(
'${record.level.name} [twonly] ${record.loggerName} > ${record.message}',
);

View file

@ -263,7 +263,7 @@ bool isUUIDNewer(String uuid1, String uuid2) {
return timestamp1 > timestamp2;
} catch (e) {
Log.error(e);
return true;
return false;
}
}

View file

@ -3,10 +3,10 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
class CameraZoomButtons extends StatefulWidget {
@ -50,6 +50,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
Future<void> initAsync() async {
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
Log.info('Found ${gCameras.length} cameras for zoom.');
if (!showWideAngleZoom && Platform.isIOS && gCameras.length == 3) {
showWideAngleZoomIOS = true;
}

View file

@ -131,7 +131,7 @@ Future<List<Sticker>> getStickerIndex() async {
final indexFile = File('${directory.path}/stickers.json');
var res = <Sticker>[];
if (indexFile.existsSync() && !kDebugMode) {
if (indexFile.existsSync() && kReleaseMode) {
final lastModified = indexFile.lastModifiedSync();
final difference = DateTime.now().difference(lastModified);
final content = await indexFile.readAsString();

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -45,10 +47,10 @@ class ReactionRow extends StatelessWidget {
child: Center(
child: Text(
reaction.emoji,
style: const TextStyle(fontSize: 18),
strutStyle: const StrutStyle(
style: TextStyle(fontSize: Platform.isIOS ? 18 : 15),
strutStyle: StrutStyle(
forceStrutHeight: true,
height: 1.6,
height: Platform.isIOS ? 1.6 : 1.3,
),
),
),
@ -114,7 +116,7 @@ class ReactionRow extends StatelessWidget {
entry.$2.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontSize: 13,
color:
isDarkMode(context) ? Colors.white : Colors.black,
decoration: TextDecoration.none,

View file

@ -67,9 +67,10 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
if (widget.message.openedAt == null || widget.message.mediaStored) {
return;
}
if (widget.mediaService.tempPath.existsSync()) {
await sendCipherTextToGroup(
widget.message.groupId,
if (widget.mediaService.tempPath.existsSync() &&
widget.message.senderId != null) {
await sendCipherText(
widget.message.senderId!,
EncryptedContent(
mediaUpdate: EncryptedContent_MediaUpdate(
type: EncryptedContent_MediaUpdate_Type.REOPENED,

View file

@ -77,10 +77,10 @@ class ChatTextEntry extends StatelessWidget {
children: [
if (info.expanded)
Expanded(
child: BetterText(text: text, textColor: info.textColor),
child: BetterText(text: info.text, textColor: info.textColor),
)
else ...[
BetterText(text: text, textColor: info.textColor),
BetterText(text: info.text, textColor: info.textColor),
SizedBox(
width: info.spacerWidth,
),

View file

@ -242,10 +242,10 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
child: Center(
child: Text(
widget.lastReaction!.emoji,
style: const TextStyle(fontSize: 18),
style: const TextStyle(fontSize: 15),
strutStyle: const StrutStyle(
forceStrutHeight: true,
height: 1.6,
height: 1.4,
),
),
),

View file

@ -1,242 +0,0 @@
import 'dart:io';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:audio_waveforms_example/chat_bubble.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Audio Waveforms',
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
late final RecorderController recorderController;
String? path;
String? musicFile;
bool isRecording = false;
bool isRecordingCompleted = false;
bool isLoading = true;
late Directory appDirectory;
@override
void initState() {
super.initState();
_getDir();
_initialiseControllers();
}
void _getDir() async {
appDirectory = await getApplicationDocumentsDirectory();
path = "${appDirectory.path}/recording.m4a";
isLoading = false;
setState(() {});
}
void _initialiseControllers() {
recorderController = RecorderController()
..androidEncoder = AndroidEncoder.aac
..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 44100;
}
void _pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
musicFile = result.files.single.path;
setState(() {});
} else {
debugPrint("File not picked");
}
}
@override
void dispose() {
recorderController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF252331),
appBar: AppBar(
backgroundColor: const Color(0xFF252331),
elevation: 1,
centerTitle: true,
shadowColor: Colors.grey,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo.png',
scale: 1.5,
),
const SizedBox(width: 10),
const Text(
'Simform',
style: TextStyle(color: Colors.white),
),
],
),
),
body: isLoading
? const Center(
child: CircularProgressIndicator(),
)
: SafeArea(
child: Column(
children: [
const SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: 4,
itemBuilder: (_, index) {
return WaveBubble(
index: index + 1,
isSender: index.isOdd,
width: MediaQuery.of(context).size.width / 2,
appDirectory: appDirectory,
);
},
),
),
if (isRecordingCompleted)
WaveBubble(
path: path,
isSender: true,
appDirectory: appDirectory,
),
if (musicFile != null)
WaveBubble(
path: musicFile,
isSender: true,
appDirectory: appDirectory,
),
SafeArea(
child: Row(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: isRecording
? AudioWaveforms(
enableGesture: true,
size: Size(
MediaQuery.of(context).size.width / 2,
50),
recorderController: recorderController,
waveStyle: const WaveStyle(
waveColor: Colors.white,
extendWaveform: true,
showMiddleLine: false,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: const Color(0xFF1E1B26),
),
padding: const EdgeInsets.only(left: 18),
margin: const EdgeInsets.symmetric(
horizontal: 15),
)
: Container(
width:
MediaQuery.of(context).size.width / 1.7,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF1E1B26),
borderRadius: BorderRadius.circular(12.0),
),
padding: const EdgeInsets.only(left: 18),
margin: const EdgeInsets.symmetric(
horizontal: 15),
child: TextField(
readOnly: true,
decoration: InputDecoration(
hintText: "Type Something...",
hintStyle: const TextStyle(
color: Colors.white54),
contentPadding:
const EdgeInsets.only(top: 16),
border: InputBorder.none,
suffixIcon: IconButton(
onPressed: _pickFile,
icon: Icon(Icons.adaptive.share),
color: Colors.white54,
),
),
),
),
),
IconButton(
onPressed: _refreshWave,
icon: Icon(
isRecording ? Icons.refresh : Icons.send,
color: Colors.white,
),
),
const SizedBox(width: 16),
IconButton(
onPressed: _startOrStopRecording,
icon: Icon(isRecording ? Icons.stop : Icons.mic),
color: Colors.white,
iconSize: 28,
),
],
),
),
],
),
),
);
}
void _startOrStopRecording() async {
try {
if (isRecording) {
recorderController.reset();
path = await recorderController.stop(false);
if (path != null) {
isRecordingCompleted = true;
debugPrint(path);
debugPrint("Recorded file size: ${File(path!).lengthSync()}");
}
} else {
await recorderController.record(path: path); // Path is optional
}
} catch (e) {
debugPrint(e.toString());
} finally {
if (recorderController.hasPermission) {
setState(() {
isRecording = !isRecording;
});
}
}
}
void _refreshWave() {
if (isRecording) recorderController.refresh();
}
}

View file

@ -82,6 +82,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
_subscription.cancel();
downloadStateListener?.cancel();
videoController?.dispose();
videoController = null;
super.dispose();
}
@ -141,7 +142,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
Future<void> loadCurrentMediaFile({bool showTwonly = false}) async {
if (!mounted || !context.mounted) return;
if (allMediaFiles.isEmpty) return nextMediaOrExit();
if (allMediaFiles.isEmpty || allMediaFiles.first.mediaId == null) {
return nextMediaOrExit();
}
await _noScreenshot.screenshotOff();
setState(() {
@ -175,7 +178,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
downloadTriggered = true;
final mediaFile = await twonlyDB.mediaFilesDao
.getMediaFileById(allMediaFiles.first.mediaId!);
await startDownloadMedia(mediaFile!, true);
if (mediaFile == null) return;
await startDownloadMedia(mediaFile, true);
unawaited(tryDownloadAllMediaFiles(force: true));
}
return;
@ -269,6 +273,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
}
});
progressTimer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
if (currentMedia!.mediaFile.displayLimitInMilliseconds == null ||
canBeSeenUntil == null) {
return;
}
final difference = canBeSeenUntil!.difference(DateTime.now());
// Calculate the progress as a value between 0.0 and 1.0
progress = difference.inMilliseconds /
@ -312,10 +320,12 @@ class _MediaViewerViewState extends State<MediaViewerView> {
void displayShortReactions() {
final renderBox =
mediaWidgetKey.currentContext!.findRenderObject()! as RenderBox;
mediaWidgetKey.currentContext!.findRenderObject() as RenderBox?;
setState(() {
showShortReactions = true;
mediaViewerDistanceFromBottom = renderBox.size.height;
if (renderBox != null) {
mediaViewerDistanceFromBottom = renderBox.size.height;
}
});
}

View file

@ -85,7 +85,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
if (_directChat == null ||
_directChat!.maxFlameCounter == 0 ||
_flameCounter >= (_directChat!.maxFlameCounter + 1) ||
_directChat!.lastFlameCounterChange!
_directChat!.maxFlameCounterFrom!
.isBefore(DateTime.now().subtract(const Duration(days: 4)))) {
return Container();
}

View file

@ -37,7 +37,7 @@ class _AccountViewState extends State<AccountView> {
.where(
(x) =>
x.transactionType != Response_TransactionTypes.ThanksForTesting ||
kDebugMode,
!kReleaseMode,
)
.map((a) => a.depositCents.toInt())
.sum;
@ -101,7 +101,7 @@ class _AccountViewState extends State<AccountView> {
),
)
: Text(context.lang.settingsAccountDeleteAccountNoBallance),
onLongPress: kDebugMode
onLongPress: !kReleaseMode
? () async {
await deleteLocalUserData();
await Restart.restartApp(

View file

@ -204,7 +204,7 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
onPressed: (!isLoading &&
(passwordCtrl.text == repeatedPasswordCtrl.text &&
passwordCtrl.text.length >= 8 ||
kDebugMode))
!kReleaseMode))
? onPressedEnableTwonlySafe
: null,
icon: isLoading

View file

@ -32,7 +32,7 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
),
body: ListView(
children: [
if (kDebugMode)
if (!kReleaseMode)
ListTile(
title: const Text('Sending a lot of messages.'),
subtitle: Text(lotsOfMessagesStatus),

View file

@ -66,7 +66,7 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
);
},
),
// if (kDebugMode)
// if (!kReleaseMode)
// ListTile(
// title: const Text('FlameSync Test'),
// onTap: () async {
@ -74,7 +74,7 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
// await syncFlameCounters();
// },
// ),
if (kDebugMode)
if (!kReleaseMode)
ListTile(
title: const Text('Automated Testing'),
onTap: () async {

View file

@ -25,11 +25,26 @@ class _ProfileViewState extends State<ProfileView> {
final AvatarMakerController _avatarMakerController =
PersistentAvatarMakerController(customizedPropertyCategories: []);
int twonlyScore = 0;
late StreamSubscription<int> twonlyScoreSub;
@override
void initState() {
twonlyScoreSub =
twonlyDB.groupsDao.watchSumTotalMediaCounter().listen((update) {
setState(() {
twonlyScore = update;
});
});
super.initState();
}
@override
void dispose() {
twonlyScoreSub.cancel();
super.dispose();
}
Future<void> updateUserDisplayName(String displayName) async {
await updateUserdata((user) {
user
@ -156,6 +171,17 @@ class _ProfileViewState extends State<ProfileView> {
}
},
),
BetterListTile(
text: context.lang.yourTwonlyScore,
icon: FontAwesomeIcons.trophy,
trailing: Text(
twonlyScore.toString(),
style: TextStyle(
color: context.color.primary,
fontSize: 18,
),
),
),
],
),
);

View file

@ -102,6 +102,7 @@ class _CheckoutViewState extends State<CheckoutView> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Card(
color: context.color.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(

View file

@ -111,6 +111,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
Padding(
padding: const EdgeInsets.all(16),
child: Card(
color: context.color.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
@ -193,6 +194,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Card(
color: context.color.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
@ -215,6 +217,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Card(
color: context.color.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(

View file

@ -444,6 +444,7 @@ class PlanCard extends StatelessWidget {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: context.color.surfaceContainer,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(