diff --git a/lib/src/components/better_text.dart b/lib/src/components/better_text.dart new file mode 100644 index 0000000..847770a --- /dev/null +++ b/lib/src/components/better_text.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart'; +import 'package:logging/logging.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class BetterText extends StatelessWidget { + final String text; + + const BetterText({super.key, required this.text}); + + @override + Widget build(BuildContext context) { + // Regular expression to find URLs and domains + final RegExp urlRegExp = RegExp( + r'(?:(?:https?://|www\.)[^\s]+|(?:[a-zA-Z0-9-]+\.[a-zA-Z]{2,}))', + caseSensitive: false, + multiLine: false, + ); + + // Split the text into parts based on the URLs and domains + final List spans = []; + final Iterable matches = urlRegExp.allMatches(text); + + int lastMatchEnd = 0; + + for (final match in matches) { + // Add the text before the URL/domain + if (match.start > lastMatchEnd) { + spans.add(TextSpan(text: text.substring(lastMatchEnd, match.start))); + } + + // Add the URL/domain as a clickable TextSpan + final String? url = match.group(0); + spans.add(TextSpan( + text: url, + style: TextStyle(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final lUrl = + Uri.parse(url!.startsWith('http') ? url : 'http://$url'); + try { + await launchUrl(lUrl); + } catch (e) { + Logger("launchUrl").shout("Could not launch $e"); + } + }, + )); + + lastMatchEnd = match.end; + } + + // Add any remaining text after the last URL/domain + if (lastMatchEnd < text.length) { + spans.add(TextSpan(text: text.substring(lastMatchEnd))); + } + + return SelectableText.rich( + TextSpan( + children: spans, + ), + style: TextStyle( + color: Colors.white, // Set text color for contrast + fontSize: 17, + ), + ); + +// child: SelectableText( +// content.text, +// style: TextStyle( +// color: Colors.white, // Set text color for contrast +// fontSize: 17, +// ), +// textAlign: TextAlign.left, // Center the text +// ), +// RichText + } +} diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index ae4cce1..bd07567 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -19,7 +19,7 @@ "onboardingTryForFree": "Kostenlos testen", "registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!", "registerUsernameDecoration": "Benutzername", - "registerUsernameLimits": "Der Benutzername muss 4 bis 12 Zeichen lang sein und darf nur aus Buchstaben (a-z) und Zahlen (0-9) bestehen.", + "registerUsernameLimits": "Der Benutzername muss 3 bis 12 Zeichen lang sein und darf nur aus Buchstaben (a-z) und Zahlen (0-9) bestehen.", "registerSubmitButton": "Jetzt registrieren!", "newMessageTitle": "Neue Nachricht", "chatsTapToSend": "Klicke, um dein erstes Bild zu teilen.", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 1ddaef0..6c02e11 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -19,7 +19,7 @@ "onboardingTryForFree": "Try for free", "registerUsernameSlogan": "Please select a username so others can find you!", "registerUsernameDecoration": "Username", - "registerUsernameLimits": "Username must be 4 to 12 characters long, consisting only of letters (a-z) and numbers (0-9).", + "registerUsernameLimits": "Username must be 3 to 12 characters long, consisting only of letters (a-z) and numbers (0-9).", "registerSubmitButton": "Register now!", "newMessageTitle": "New message", "chatsTapToSend": "Click to send your first image", diff --git a/lib/src/providers/hive.dart b/lib/src/providers/hive.dart index e0fcc00..3df1e17 100644 --- a/lib/src/providers/hive.dart +++ b/lib/src/providers/hive.dart @@ -19,9 +19,14 @@ Future initMediaStorage() async { } Future getMediaStorage() async { - await initMediaStorage(); - final storage = getSecureStorage(); + + var containsEncryptionKey = + await storage.containsKey(key: 'hive_encryption_key'); + if (!containsEncryptionKey) { + await initMediaStorage(); + } + var encryptionKey = base64Url.decode((await storage.read(key: 'hive_encryption_key'))!); diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index ea486f3..1349a2c 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -6,6 +6,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/components/animate_icon.dart'; +import 'package:twonly/src/components/better_text.dart'; import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/verified_shield.dart'; @@ -62,14 +63,7 @@ class ChatListEntry extends StatelessWidget { 83, 68, 137, 255), // Set the background color borderRadius: BorderRadius.circular(12.0), // Set border radius ), - child: SelectableText( - content.text, - style: TextStyle( - color: Colors.white, // Set text color for contrast - fontSize: 17, - ), - textAlign: TextAlign.left, // Center the text - ), + child: BetterText(text: content.text), ); } } else if (content is MediaMessageContent && !content.isVideo) { diff --git a/lib/src/views/onboarding/register_view.dart b/lib/src/views/onboarding/register_view.dart index 7130cab..8ac596d 100644 --- a/lib/src/views/onboarding/register_view.dart +++ b/lib/src/views/onboarding/register_view.dart @@ -105,9 +105,15 @@ class _RegisterViewState extends State { const SizedBox(height: 15), TextField( controller: usernameController, + onChanged: (value) { + usernameController.text = value.toLowerCase(); + usernameController.selection = TextSelection.fromPosition( + TextPosition(offset: usernameController.text.length), + ); + }, inputFormatters: [ LengthLimitingTextInputFormatter(12), - FilteringTextInputFormatter.allow(RegExp(r'[a-z0-9]')), + FilteringTextInputFormatter.allow(RegExp(r'[a-z0-9A-Z]')), ], style: TextStyle(fontSize: 17), decoration: getInputDecoration(