add barcode scanner and remove tutorial

This commit is contained in:
otsmr 2025-12-07 16:02:08 +01:00
parent 9667ea21b6
commit 76b617e63a
13 changed files with 73 additions and 277 deletions

0
.gitmodules vendored Normal file
View file

View file

@ -1,9 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:usesCleartextTraffic="true" >
</application>
<application android:usesCleartextTraffic="true" >
<!-- // https://github.com/juliansteenbakker/mobile_scanner/issues/553 -->
<meta-data android:name="firebase_performance_collection_deactivated" android:value="true" />
<!-- <service
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
tools:node="remove">
</service> -->
</application>
</manifest>

View file

@ -1,4 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application
android:label="twonly"
android:name="${applicationName}"
@ -33,6 +33,12 @@
android:name="flutterEmbedding"
android:value="2" />
<!-- // https://github.com/juliansteenbakker/mobile_scanner/issues/553 -->
<meta-data android:name="firebase_performance_collection_deactivated" android:value="true" />
<service
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
tools:node="remove">
</service>
<!-- <service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"

View file

@ -8,4 +8,5 @@
<uses-permission android:name="android.permission.CAMERA"/>
<application android:usesCleartextTraffic="true" >
</application>
<meta-data android:name="firebase_performance_collection_deactivated" android:value="true" />
</manifest>

View file

@ -85,5 +85,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<!--Disable Firebase Telemetry-->
<key>firebase_performance_collection_deactivated</key>
<true/>
<!--...-->
</dict>
</plist>

View file

@ -23,24 +23,26 @@ class BarcodeDetectorPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3.0
..color = Colors.lightGreenAccent;
final Paint background = Paint()..color = Color(0x99000000);
final background = Paint()..color = const Color(0x99000000);
for (final Barcode barcode in barcodes) {
final ParagraphBuilder builder = ParagraphBuilder(
for (final barcode in barcodes) {
final builder = ParagraphBuilder(
ParagraphStyle(
textAlign: TextAlign.left,
fontSize: 16,
textDirection: TextDirection.ltr),
);
builder.pushStyle(
ui.TextStyle(color: Colors.lightGreenAccent, background: background));
builder.addText('${barcode.displayValue}');
builder.pop();
textAlign: TextAlign.left,
fontSize: 16,
textDirection: TextDirection.ltr,
),
)
..pushStyle(
ui.TextStyle(color: Colors.lightGreenAccent, background: background),
)
..addText('${barcode.displayValue}')
..pop();
final left = translateX(
barcode.boundingBox.left,
@ -77,16 +79,16 @@ class BarcodeDetectorPainter extends CustomPainter {
// paint,
// );
final List<Offset> cornerPoints = <Offset>[];
final cornerPoints = <Offset>[];
for (final point in barcode.cornerPoints) {
final double x = translateX(
final x = translateX(
point.x.toDouble(),
size,
imageSize,
rotation,
cameraLensDirection,
);
final double y = translateY(
final y = translateY(
point.y.toDouble(),
size,
imageSize,
@ -99,20 +101,23 @@ class BarcodeDetectorPainter extends CustomPainter {
// Add the first point to close the polygon
cornerPoints.add(cornerPoints.first);
canvas.drawPoints(PointMode.polygon, cornerPoints, paint);
canvas.drawParagraph(
builder.build()
..layout(ParagraphConstraints(
width: (right - left).abs(),
)),
Offset(
canvas
..drawPoints(PointMode.polygon, cornerPoints, paint)
..drawParagraph(
builder.build()
..layout(
ParagraphConstraints(
width: (right - left).abs(),
),
),
Offset(
Platform.isAndroid &&
cameraLensDirection == CameraLensDirection.front
? right
: left,
top),
);
top,
),
);
}
}

View file

@ -26,6 +26,7 @@ double translateX(
switch (cameraLensDirection) {
case CameraLensDirection.back:
return x * canvasSize.width / imageSize.width;
// ignore: no_default_cases
default:
return canvasSize.width - x * canvasSize.width / imageSize.width;
}

View file

@ -23,7 +23,6 @@ import 'package:twonly/src/views/settings/help/changelog.view.dart';
import 'package:twonly/src/views/settings/profile/profile.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/tutorial/tutorials.dart';
class ChatListView extends StatefulWidget {
const ChatListView({super.key});
@ -38,7 +37,6 @@ class _ChatListViewState extends State<ChatListView> {
List<Group> _groupsArchived = [];
GlobalKey searchForOtherUsers = GlobalKey();
Timer? tutorial;
bool showFeedbackShortcut = false;
@override
@ -58,16 +56,6 @@ class _ChatListViewState extends State<ChatListView> {
});
});
tutorial = Timer(const Duration(seconds: 1), () async {
tutorial = null;
if (!mounted) return;
await showChatListTutorialSearchOtherUsers(context, searchForOtherUsers);
if (!mounted) return;
// if (_groupsNotPinned.isNotEmpty) {
// await showChatListTutorialContextMenu(context, firstUserListItemKey);
// }
});
final changeLog = await rootBundle.loadString('CHANGELOG.md');
final changeLogHash =
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
@ -97,7 +85,6 @@ class _ChatListViewState extends State<ChatListView> {
@override
void dispose() {
tutorial?.cancel();
_contactsSub.cancel();
super.dispose();
}

View file

@ -22,7 +22,6 @@ import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
import 'package:twonly/src/views/contact/contact.view.dart';
import 'package:twonly/src/views/groups/group.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart';
Color getMessageColor(Message message) {
return (message.senderId == null)
@ -89,7 +88,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
Message? quotesMessage;
GlobalKey verifyShieldKey = GlobalKey();
late FocusNode textFieldFocus;
Timer? tutorial;
final ItemScrollController itemScrollController = ItemScrollController();
int? focusedScrollItem;
@ -99,12 +97,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
group = widget.group;
textFieldFocus = FocusNode();
initStreams();
tutorial = Timer(const Duration(seconds: 1), () async {
tutorial = null;
if (!mounted) return;
await showVerifyShieldTutorial(context, verifyShieldKey);
});
}
@override
@ -114,7 +106,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
contactSub?.cancel();
groupActionsSub?.cancel();
lastOpenedMessageByContactSub?.cancel();
tutorial?.cancel();
super.dispose();
}

View file

@ -156,7 +156,7 @@ class HomeViewState extends State<HomeView> {
CustomPaint? _customPaint;
String? _text;
final _orientations = {
final Map<DeviceOrientation, int> _orientations = {
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeLeft: 90,
DeviceOrientation.portraitDown: 180,

View file

@ -62,26 +62,26 @@ class _HelpViewState extends State<HelpView> {
);
},
),
ListTile(
title: Text(context.lang.settingsResetTutorials),
subtitle: Text(
context.lang.settingsResetTutorialsDesc,
style: const TextStyle(fontSize: 12),
),
onTap: () async {
await updateUserdata((user) {
user.tutorialDisplayed = [];
return user;
});
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.lang.settingsResetTutorialsSuccess),
duration: const Duration(seconds: 3),
),
);
},
),
// ListTile(
// title: Text(context.lang.settingsResetTutorials),
// subtitle: Text(
// context.lang.settingsResetTutorialsDesc,
// style: const TextStyle(fontSize: 12),
// ),
// onTap: () async {
// await updateUserdata((user) {
// user.tutorialDisplayed = [];
// return user;
// });
// if (!context.mounted) return;
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(context.lang.settingsResetTutorialsSuccess),
// duration: const Duration(seconds: 3),
// ),
// );
// },
// ),
const Divider(),
ListTile(
title: Text(context.lang.allowErrorTracking),

View file

@ -1,114 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
Future<void> showTutorial(
BuildContext context,
List<TargetFocus> targets,
) async {
final completer = Completer<dynamic>();
TutorialCoachMark(
targets: targets,
colorShadow: context.color.primary,
textSkip: context.lang.ok,
alignSkip: Alignment.bottomCenter,
textStyleSkip: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
),
onSkip: () {
completer.complete();
return true;
},
onFinish: () {
completer.complete();
},
).show(context: context);
await completer.future;
}
Future<bool> checkIfTutorialAlreadyShown(String tutorialId) async {
final user = await getUser();
if (user == null) return true;
user.tutorialDisplayed ??= [];
if (user.tutorialDisplayed!.contains(tutorialId)) {
return true;
}
user.tutorialDisplayed!.add(tutorialId);
await updateUserdata((u) {
u.tutorialDisplayed = user.tutorialDisplayed;
return u;
});
return false;
}
TargetFocus getTargetFocus(
BuildContext context,
GlobalKey key,
String title,
String body,
) {
final renderBox = key.currentContext!.findRenderObject()! as RenderBox;
final position = renderBox.localToGlobal(Offset.zero);
final screenHeight = MediaQuery.of(context).size.height;
final centerY = screenHeight / 2;
double top = 0;
double bottom = 0;
if (position.dy < centerY) {
bottom = 0;
top = position.dy;
} else {
bottom = centerY;
top = 0;
}
return TargetFocus(
identify: title,
keyTarget: key,
contents: [
TargetContent(
align: ContentAlign.custom,
customPosition: CustomTargetContentPosition(
left: 0,
right: 0,
top: top,
bottom: bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20,
),
),
Padding(
padding: const EdgeInsets.only(top: 10),
child: Text(
body,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
],
),
),
],
);
}

View file

@ -1,91 +0,0 @@
import 'package:flutter/material.dart';
import 'package:mutex/mutex.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/tutorial/show_tutorial.dart';
final lockDisplayTutorial = Mutex();
Future<void> showChatListTutorialSearchOtherUsers(
BuildContext context,
GlobalKey searchForOtherUsers,
) async {
await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown('chat_list:search_users')) {
return;
}
if (!context.mounted) return;
final targets = <TargetFocus>[
getTargetFocus(
context,
searchForOtherUsers,
context.lang.tutorialChatListSearchUsersTitle,
context.lang.tutorialChatListSearchUsersDesc,
),
];
await showTutorial(context, targets);
});
}
Future<void> showChatListTutorialContextMenu(
BuildContext context,
GlobalKey firstUserListItemKey,
) async {
await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown('chat_list:context_menu')) {
return;
}
if (!context.mounted) return;
final targets = <TargetFocus>[
getTargetFocus(
context,
firstUserListItemKey,
context.lang.tutorialChatListContextMenuTitle,
context.lang.tutorialChatListContextMenuDesc,
),
];
await showTutorial(context, targets);
});
}
Future<void> showVerifyShieldTutorial(
BuildContext context,
GlobalKey firstUserListItemKey,
) async {
await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown('chat_messages:verify_shield')) {
return;
}
if (!context.mounted) return;
final targets = <TargetFocus>[
getTargetFocus(
context,
firstUserListItemKey,
context.lang.tutorialChatMessagesVerifyShieldTitle,
context.lang.tutorialChatMessagesVerifyShieldDesc,
),
];
await showTutorial(context, targets);
});
}
Future<void> showReopenMediaFilesTutorial(
BuildContext context,
GlobalKey firstUserListItemKey,
) async {
await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown('chat_messages:reopen_message')) {
return;
}
if (!context.mounted) return;
final targets = <TargetFocus>[
getTargetFocus(
context,
firstUserListItemKey,
context.lang.tutorialChatMessagesReopenMessageTitle,
context.lang.tutorialChatMessagesReopenMessageDesc,
),
];
await showTutorial(context, targets);
});
}