fixing multiple issues

This commit is contained in:
otsmr 2025-07-20 19:10:07 +02:00
parent e9fd8726c9
commit a71eb1fe94
7 changed files with 120 additions and 110 deletions

View file

@ -33,7 +33,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "eu.twonly.testing"
applicationId = "eu.twonly"
multiDexEnabled true
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
@ -53,7 +53,7 @@ android {
buildTypes {
debug {
applicationIdSuffix ".debug"
applicationIdSuffix ".testing"
}
release {
signingConfig signingConfigs.release

View file

@ -497,11 +497,18 @@ class ApiService {
return sendRequestSync(req);
}
Future<Result> getUserData(String username) async {
Future<Response_UserData?> getUserData(String username) async {
final get = ApplicationData_GetUserByUsername()..username = username;
final appData = ApplicationData()..getuserbyusername = get;
final req = createClientToServerFromApplicationData(appData);
return sendRequestSync(req);
final res = await sendRequestSync(req);
if (res.isSuccess) {
final ok = res.value as server.Response_Ok;
if (ok.hasUserdata()) {
return ok.userdata;
}
}
return null;
}
Future<Response_PlanBallance?> getPlanBallance() async {

View file

@ -8,6 +8,7 @@ import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:drift/drift.dart';
import 'package:http/http.dart' as http;
import 'package:mutex/mutex.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart';
@ -20,11 +21,8 @@ import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
Map<int, DateTime> downloadStartedForMediaReceived = {};
Future<void> tryDownloadAllMediaFiles({bool force = false}) async {
// This is called when WebSocket is newly connected, so allow all downloads to be restarted.
downloadStartedForMediaReceived = {};
final messages =
await twonlyDB.messagesDao.getAllMessagesPendingDownloading();
@ -102,13 +100,15 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
}
Future<void> handleDownloadStatusUpdateInternal(
int messageId, bool failed) async {
int messageId,
bool failed,
) async {
if (failed) {
Log.error('Download failed for $messageId');
final message = await twonlyDB.messagesDao
.getMessageByMessageId(messageId)
.getSingleOrNull();
if (message != null) {
if (message != null && message.downloadState != DownloadState.downloaded) {
await handleMediaError(message);
}
} else {
@ -117,18 +117,10 @@ Future<void> handleDownloadStatusUpdateInternal(
}
}
Future<void> startDownloadMedia(Message message, bool force,
{int retryCounter = 0}) async {
Mutex protectDownload = Mutex();
Future<void> startDownloadMedia(Message message, bool force) async {
if (message.contentJson == null) return;
if (downloadStartedForMediaReceived[message.messageId] != null &&
retryCounter == 0) {
final started = downloadStartedForMediaReceived[message.messageId]!;
final elapsed = DateTime.now().difference(started);
if (elapsed <= const Duration(seconds: 60)) {
Log.error('Download already started...');
return;
}
}
final content = MessageContent.fromJson(
message.kind, jsonDecode(message.contentJson!) as Map);
@ -157,16 +149,33 @@ Future<void> startDownloadMedia(Message message, bool force,
return;
}
if (message.downloadState != DownloadState.downloaded) {
final isBlocked = await protectDownload.protect<bool>(() async {
final msg = await twonlyDB.messagesDao
.getMessageByMessageId(message.messageId)
.getSingleOrNull();
if (msg == null) return true;
if (msg.downloadState != DownloadState.pending) {
Log.error(
'${message.messageId} is already downloaded or is downloading.');
return true;
}
await twonlyDB.messagesDao.updateMessageByMessageId(
message.messageId,
const MessagesCompanion(
downloadState: Value(DownloadState.downloading),
),
);
}
downloadStartedForMediaReceived[message.messageId] = DateTime.now();
return false;
});
if (isBlocked) {
Log.info('Download for ${message.messageId} already started.');
return;
}
final downloadToken = uint8ListToHex(content.downloadToken!);

View file

@ -43,8 +43,14 @@ class CameraSendToViewState extends State<CameraSendToView> {
return cameraController;
}
/// same function also in home.view.dart
Future<void> toggleSelectedCamera() async {
await cameraController?.dispose();
if (cameraController == null) return;
// do not allow switching camera when recording
if (cameraController!.value.isRecordingVideo == true) {
return;
}
await cameraController!.dispose();
cameraController = null;
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
}

View file

@ -50,7 +50,7 @@ class _LocationFilterState extends State<LocationFilter> {
for (final item in imageIndex) {
if (item.imageSrc.contains('/cities/$normalizedCountry/')) {
// Check if the item matches the normalized city
if (item.imageSrc.endsWith('$normalizedCity.png')) {
if (item.imageSrc.contains('$normalizedCity.')) {
if (item.imageSrc.startsWith('/api/')) {
_imageUrl = 'https://twonly.eu/${item.imageSrc}';
if (mounted) setState(() {});

View file

@ -1,5 +1,3 @@
// ignore_for_file: avoid_dynamic_calls
import 'dart:async';
import 'package:drift/drift.dart' hide Column;
@ -11,7 +9,6 @@ import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/api/utils.dart';
@ -41,7 +38,11 @@ class _SearchUsernameView extends State<AddNewUserView> {
@override
void initState() {
super.initState();
initStreams();
contactsStream = twonlyDB.contactsDao
.watchNotAcceptedContacts()
.listen((update) => setState(() {
contacts = update;
}));
}
@override
@ -50,18 +51,9 @@ class _SearchUsernameView extends State<AddNewUserView> {
super.dispose();
}
void initStreams() {
contactsStream =
twonlyDB.contactsDao.watchNotAcceptedContacts().listen((update) {
setState(() {
contacts = update;
});
});
}
Future<void> _addNewUser(BuildContext context) async {
final user = await getUser();
if (user == null || user.username == searchUserName.text) {
if (user == null || user.username == searchUserName.text || !mounted) {
return;
}
@ -69,70 +61,67 @@ class _SearchUsernameView extends State<AddNewUserView> {
_isLoading = true;
});
final res = await apiService.getUserData(searchUserName.text);
final userdata = await apiService.getUserData(searchUserName.text);
if (!context.mounted) return;
if (!context.mounted) {
return;
}
if (res.isSuccess) {
final addUser = await showAlertDialog(
context, context.lang.userFound, context.lang.userFoundBody);
if (!addUser || !context.mounted) {
setState(() {
_isLoading = false;
});
return;
}
final added = await twonlyDB.contactsDao.insertContact(
ContactsCompanion(
username: Value(searchUserName.text),
userId: Value(res.value.userdata.userId.toInt() as int),
requested: const Value(false),
),
);
if (added > 0) {
if (await createNewSignalSession(
res.value.userdata as Response_UserData)) {
// before notifying the other party, add
await setupNotificationWithUsers(
forceContact: res.value.userdata.userId.toInt() as int,
);
await encryptAndSendMessageAsync(
null,
res.value.userdata.userId.toInt() as int,
MessageJson(
kind: MessageKind.contactRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
pushNotification: PushNotification(kind: PushKind.contactRequest),
);
}
}
} else {
await showAlertDialog(context, context.lang.searchUsernameNotFound,
context.lang.searchUsernameNotFoundBody(searchUserName.text));
}
setState(() {
_isLoading = false;
});
if (userdata == null) {
await showAlertDialog(context, context.lang.searchUsernameNotFound,
context.lang.searchUsernameNotFoundBody(searchUserName.text));
return;
}
final addUser = await showAlertDialog(
context,
context.lang.userFound,
context.lang.userFoundBody,
);
if (!addUser || !context.mounted) {
return;
}
final added = await twonlyDB.contactsDao.insertContact(
ContactsCompanion(
username: Value(searchUserName.text),
userId: Value(userdata.userId.toInt()),
requested: const Value(false),
),
);
if (added > 0) {
if (await createNewSignalSession(userdata)) {
// before notifying the other party, add
await setupNotificationWithUsers(
forceContact: userdata.userId.toInt(),
);
await encryptAndSendMessageAsync(
null,
userdata.userId.toInt(),
MessageJson(
kind: MessageKind.contactRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
pushNotification: PushNotification(kind: PushKind.contactRequest),
);
}
}
}
InputDecoration getInputDecoration(String hintText) {
final primaryColor =
Theme.of(context).colorScheme.primary; // Get the primary color
return InputDecoration(
hintText: hintText,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(9),
borderSide: BorderSide(color: primaryColor),
borderSide: BorderSide(color: context.color.primary),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
borderSide: BorderSide(color: context.color.outline),
),
contentPadding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
);
@ -146,8 +135,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
),
body: SafeArea(
child: Padding(
padding:
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Column(
children: [
Padding(
@ -186,10 +174,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
floatingActionButton: Padding(
padding: const EdgeInsets.only(bottom: 30),
child: FloatingActionButton(
foregroundColor: Colors.white,
onPressed: () {
if (!_isLoading) _addNewUser(context);
},
onPressed: _isLoading ? null : () async => _addNewUser(context),
child: _isLoading
? const Center(child: CircularProgressIndicator())
: const FaIcon(FontAwesomeIcons.magnifyingGlassPlus),
@ -199,17 +184,11 @@ class _SearchUsernameView extends State<AddNewUserView> {
}
}
class ContactsListView extends StatefulWidget {
class ContactsListView extends StatelessWidget {
const ContactsListView(this.contacts, {super.key});
final List<Contact> contacts;
@override
State<ContactsListView> createState() => _ContactsListViewState();
}
class _ContactsListViewState extends State<ContactsListView> {
List<Widget> sendRequestActions(Contact contact) {
List<Widget> sendRequestActions(BuildContext context, Contact contact) {
return [
Tooltip(
message: context.lang.searchUserNameArchiveUserTooltip,
@ -225,7 +204,7 @@ class _ContactsListViewState extends State<ContactsListView> {
];
}
List<Widget> requestedActions(Contact contact) {
List<Widget> requestedActions(BuildContext context, Contact contact) {
return [
Tooltip(
message: context.lang.searchUserNameBlockUserTooltip,
@ -272,9 +251,9 @@ class _ContactsListViewState extends State<ContactsListView> {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.contacts.length,
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = widget.contacts[index];
final contact = contacts[index];
final displayName = getContactDisplayName(contact);
return ListTile(
title: Text(displayName),
@ -282,8 +261,8 @@ class _ContactsListViewState extends State<ContactsListView> {
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: contact.requested
? requestedActions(contact)
: sendRequestActions(contact),
? requestedActions(context, contact)
: sendRequestActions(context, contact),
),
);
},

View file

@ -112,7 +112,10 @@ class HomeViewState extends State<HomeView> {
}
Future<CameraController?> selectCamera(
int sCameraId, bool init, bool enableAudio) async {
int sCameraId,
bool init,
bool enableAudio,
) async {
final opts = await initializeCameraController(
selectedCameraDetails, sCameraId, init, enableAudio);
if (opts != null) {
@ -124,8 +127,14 @@ class HomeViewState extends State<HomeView> {
return cameraController;
}
/// same function also in camera_send_to_view
Future<void> toggleSelectedCamera() async {
await cameraController?.dispose();
if (cameraController == null) return;
// do not allow switching camera when recording
if (cameraController!.value.isRecordingVideo == true) {
return;
}
await cameraController!.dispose();
cameraController = null;
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
}