diff --git a/assets/filters/random/avocardio.png b/assets/filters/random/avocardio.png deleted file mode 100644 index 46a0321..0000000 Binary files a/assets/filters/random/avocardio.png and /dev/null differ diff --git a/assets/filters/random/chillen.png b/assets/filters/random/chillen.png deleted file mode 100644 index e379ce1..0000000 Binary files a/assets/filters/random/chillen.png and /dev/null differ diff --git a/assets/filters/random/duck.png b/assets/filters/random/duck.png deleted file mode 100644 index eae7f36..0000000 Binary files a/assets/filters/random/duck.png and /dev/null differ diff --git a/assets/filters/random/hide_the_pain.png b/assets/filters/random/hide_the_pain.png deleted file mode 100644 index f8a7205..0000000 Binary files a/assets/filters/random/hide_the_pain.png and /dev/null differ diff --git a/assets/filters/random/lol.png b/assets/filters/random/lol.png deleted file mode 100644 index 76b7707..0000000 Binary files a/assets/filters/random/lol.png and /dev/null differ diff --git a/assets/filters/random/yolo.png b/assets/filters/random/yolo.png deleted file mode 100644 index 8f13fb0..0000000 Binary files a/assets/filters/random/yolo.png and /dev/null differ diff --git a/lib/src/views/camera/image_editor/layers/emoji_layer.dart b/lib/src/views/camera/image_editor/layers/emoji_layer.dart index ea53250..1b4ef13 100755 --- a/lib/src/views/camera/image_editor/layers/emoji_layer.dart +++ b/lib/src/views/camera/image_editor/layers/emoji_layer.dart @@ -28,6 +28,7 @@ class _EmojiLayerState extends State { final GlobalKey outlineKey = GlobalKey(); final GlobalKey emojiKey = GlobalKey(); int pointers = 0; + bool display = false; @override void initState() { @@ -38,15 +39,19 @@ class _EmojiLayerState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { widget.layerData.offset = Offset( - MediaQuery.of(context).size.width / 2 - 64, - MediaQuery.of(context).size.height / 2 - 64 - 100); + MediaQuery.of(context).size.width / 2 - (153 / 2), + MediaQuery.of(context).size.height / 2 - (153 / 2) - 100); }); + display = true; }); + } else { + display = true; } } @override Widget build(BuildContext context) { + if (!display) return Container(); if (widget.layerData.isDeleted) return Container(); return Stack( key: outlineKey, diff --git a/lib/src/views/camera/image_editor/layers/filter_layer.dart b/lib/src/views/camera/image_editor/layers/filter_layer.dart index 949ab04..972b016 100644 --- a/lib/src/views/camera/image_editor/layers/filter_layer.dart +++ b/lib/src/views/camera/image_editor/layers/filter_layer.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; @@ -19,8 +20,8 @@ class FilterLayer extends StatefulWidget { State createState() => _FilterLayerState(); } -class FilterSceleton extends StatelessWidget { - const FilterSceleton({super.key, this.child}); +class FilterSkeleton extends StatelessWidget { + const FilterSkeleton({super.key, this.child}); final Widget? child; @override @@ -65,17 +66,11 @@ class FilterText extends StatelessWidget { class _FilterLayerState extends State { final PageController pageController = PageController(); - final List pages = [ - FilterSceleton(), + List pages = [ + FilterSkeleton(), DateTimeFilter(), LocationFilter(), - ImageFilter(imagePath: "random/lol.png"), - ImageFilter(imagePath: "random/hide_the_pain.png"), - ImageFilter(imagePath: "random/yolo.png"), - ImageFilter(imagePath: "random/chillen.png"), - ImageFilter(imagePath: "random/avocardio.png"), - ImageFilter(imagePath: "random/duck.png"), - FilterSceleton(), + FilterSkeleton(), ]; @override @@ -84,6 +79,20 @@ class _FilterLayerState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { pageController.jumpToPage(1); }); + initAsync(); + } + + Future initAsync() async { + var stickers = (await getStickerIndex()) + .where((x) => x.imageSrc.contains("/imagefilter/")) + .toList(); + stickers.sortBy((x) => x.imageSrc); + + for (final sticker in stickers) { + pages.insert(pages.length - 1, ImageFilter(imagePath: sticker.imageSrc)); + } + if (!mounted) return; + setState(() {}); } @override diff --git a/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart b/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart index 9924f7f..9d175db 100644 --- a/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart +++ b/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart @@ -11,7 +11,7 @@ class DateTimeFilter extends StatelessWidget { Widget build(BuildContext context) { String currentTime = DateFormat('HH:mm').format(DateTime.now()); String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now()); - return FilterSceleton( + return FilterSkeleton( child: Positioned( bottom: 80, left: 40, diff --git a/lib/src/views/camera/image_editor/layers/filters/image_filter.dart b/lib/src/views/camera/image_editor/layers/filters/image_filter.dart index 8ea6309..6c6629d 100644 --- a/lib/src/views/camera/image_editor/layers/filters/image_filter.dart +++ b/lib/src/views/camera/image_editor/layers/filters/image_filter.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; @@ -7,14 +8,14 @@ class ImageFilter extends StatelessWidget { @override Widget build(BuildContext context) { - return FilterSceleton( + return FilterSkeleton( child: Positioned( bottom: 50, left: 10, right: 10, child: Center( - child: Image.asset( - "assets/filters/$imagePath", + child: CachedNetworkImage( + imageUrl: "https://twonly.eu/$imagePath", height: 150, ), ), diff --git a/lib/src/views/camera/image_editor/layers/filters/location_filter.dart b/lib/src/views/camera/image_editor/layers/filters/location_filter.dart index a504b73..28c91e4 100644 --- a/lib/src/views/camera/image_editor/layers/filters/location_filter.dart +++ b/lib/src/views/camera/image_editor/layers/filters/location_filter.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; @@ -31,25 +32,25 @@ class _LocationFilterState extends State { if (res.isSuccess) { location = res.value.location; _searchForImage(); - setState(() {}); + if (mounted) setState(() {}); } } void _searchForImage() async { if (location == null) return; - List imageIndex = await getStickerIndex(); + List imageIndex = await getStickerIndex(); // Normalize the city and country for search String normalizedCity = location!.city.toLowerCase().replaceAll(' ', '_'); String normalizedCountry = location!.county.toLowerCase(); // Search for the city first for (var item in imageIndex) { - if (item.contains('/cities/$normalizedCountry/')) { + if (item.imageSrc.contains('/cities/$normalizedCountry/')) { // Check if the item matches the normalized city - if (item.endsWith('$normalizedCity.png')) { - if (item.startsWith("/api/")) { + if (item.imageSrc.endsWith('$normalizedCity.png')) { + if (item.imageSrc.startsWith("/api/")) { _imageUrl = "https://twonly.eu/$item"; - setState(() {}); + if (mounted) setState(() {}); } return; } @@ -59,10 +60,11 @@ class _LocationFilterState extends State { // If city not found, search for the country if (_imageUrl == null) { for (var item in imageIndex) { - if (item.contains('/countries/') && item.contains(normalizedCountry)) { - if (item.startsWith("/api/")) { + if (item.imageSrc.contains('/countries/') && + item.imageSrc.contains(normalizedCountry)) { + if (item.imageSrc.startsWith("/api/")) { _imageUrl = "https://twonly.eu/$item"; - setState(() {}); + if (mounted) setState(() {}); } break; } @@ -73,7 +75,7 @@ class _LocationFilterState extends State { @override Widget build(BuildContext context) { if (_imageUrl != null) { - return FilterSceleton( + return FilterSkeleton( child: Positioned( bottom: 0, left: 40, @@ -89,7 +91,7 @@ class _LocationFilterState extends State { if (location != null) { if (location!.county != "-") { - return FilterSceleton( + return FilterSkeleton( child: Positioned( bottom: 50, left: 40, @@ -109,24 +111,42 @@ class _LocationFilterState extends State { } } -Future> getStickerIndex() async { - final directory = await getApplicationCacheDirectory(); - final indexFile = File('${directory.path}/index.json'); +class Sticker { + final String imageSrc; + final String source; - if (await indexFile.exists()) { + Sticker({required this.imageSrc, required this.source}); + + factory Sticker.fromJson(Map json) { + return Sticker( + imageSrc: json['imageSrc'], + source: json['source'] ?? '', // Handle null source + ); + } +} + +Future> getStickerIndex() async { + final directory = await getApplicationCacheDirectory(); + final indexFile = File('${directory.path}/stickers.json'); + List res = []; + + if (await indexFile.exists() && !kDebugMode) { final lastModified = await indexFile.lastModified(); final difference = DateTime.now().difference(lastModified); - if (difference.inHours < 24) { - final content = await indexFile.readAsString(); - return await json.decode(content).whereType().toList(); + final content = await indexFile.readAsString(); + List jsonList = json.decode(content); + res = jsonList.map((json) => Sticker.fromJson(json)).toList(); + if (difference.inHours < 2) { + return res; } } final response = - await http.get(Uri.parse('https://twonly.eu/api/sticker/index.json')); + await http.get(Uri.parse('https://twonly.eu/api/sticker/stickers.json')); if (response.statusCode == 200) { await indexFile.writeAsString(response.body); - return json.decode(response.body).whereType().toList(); + List jsonList = json.decode(response.body); + return jsonList.map((json) => Sticker.fromJson(json)).toList(); } else { - return []; + return res; } } diff --git a/lib/src/views/camera/image_editor/layers/text_layer.dart b/lib/src/views/camera/image_editor/layers/text_layer.dart index 1889b82..9c1293a 100755 --- a/lib/src/views/camera/image_editor/layers/text_layer.dart +++ b/lib/src/views/camera/image_editor/layers/text_layer.dart @@ -29,6 +29,8 @@ class _TextViewState extends State { void initState() { super.initState(); + textController.text = widget.layerData.text; + if (widget.layerData.offset.dy == 0) { // Set the initial offset to the center of the screen WidgetsBinding.instance.addPostFrameCallback((_) { @@ -78,14 +80,17 @@ class _TextViewState extends State { }); }, decoration: InputDecoration( - border: InputBorder.none, // Keine Umrandung - contentPadding: EdgeInsets.zero, // Kein Padding + border: InputBorder.none, + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), ), - // widget.layerData.text.toString(), textAlign: TextAlign.center, style: TextStyle( color: Colors.white, - fontSize: 17, + fontSize: 20, ), ), ), diff --git a/lib/src/views/camera/image_editor/layers_viewer.dart b/lib/src/views/camera/image_editor/layers_viewer.dart index 2640eed..aa29540 100644 --- a/lib/src/views/camera/image_editor/layers_viewer.dart +++ b/lib/src/views/camera/image_editor/layers_viewer.dart @@ -39,6 +39,7 @@ class LayersViewer extends StatelessWidget { .map((layerItem) { if (layerItem is EmojiLayerData) { return EmojiLayer( + key: GlobalKey(), layerData: layerItem, onUpdate: onUpdate, ); @@ -52,6 +53,7 @@ class LayersViewer extends StatelessWidget { }), ...layers.whereType().map((layerItem) { return TextLayer( + key: GlobalKey(), layerData: layerItem, onUpdate: onUpdate, ); diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 6170410..4deb8a7 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -72,10 +72,10 @@ class _ShareImageEditorView extends State { super.initState(); initAsync(); initMediaFileUpload(); + layers.add(FilterLayerData()); if (widget.imageBytes != null) { loadImage(widget.imageBytes!); } else if (widget.videoFilePath != null) { - layers.add(FilterLayerData()); setState(() { sendingOrLoadingImage = false; }); @@ -373,13 +373,12 @@ class _ShareImageEditorView extends State { if (!context.mounted) return; - layers.clear(); - - layers.add(BackgroundLayerData( - image: currentImage, - )); - - layers.add(FilterLayerData()); + layers.insert( + 0, + BackgroundLayerData( + image: currentImage, + ), + ); setState(() { sendingOrLoadingImage = false; }); @@ -465,6 +464,7 @@ class _ShareImageEditorView extends State { if (layers.any((x) => x.isEditing)) { return; } + layers = layers.where((x) => !x.isDeleted).toList(); undoLayers.clear(); removedLayers.clear(); layers.add(TextLayerData( diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_view.dart index 6b99e07..a6dc2e6 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_view.dart @@ -46,7 +46,7 @@ class _ShareImageView extends State { List contacts = []; List _otherUsers = []; List _bestFriends = []; - List _pinnedContacs = []; + List _pinnedContacts = []; Uint8List? imageBytes; bool sendingImage = false; bool hideArchivedUsers = true; @@ -122,7 +122,7 @@ class _ShareImageView extends State { setState(() { _bestFriends = bestFriends; - _pinnedContacs = pinnedContacts; + _pinnedContacts = pinnedContacts; _otherUsers = otherUsers; }); } @@ -188,9 +188,9 @@ class _ShareImageView extends State { ), ), ), - if (_pinnedContacs.isNotEmpty) const SizedBox(height: 10), + if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10), BestFriendsSelector( - users: _pinnedContacs, + users: _pinnedContacts, selectedUserIds: widget.selectedUserIds, isRealTwonly: widget.isRealTwonly, updateStatus: updateStatus, diff --git a/lib/src/views/settings/help/credits.view.dart b/lib/src/views/settings/help/credits.view.dart index 4a6bd2d..4e3dac7 100644 --- a/lib/src/views/settings/help/credits.view.dart +++ b/lib/src/views/settings/help/credits.view.dart @@ -1,20 +1,29 @@ +import 'package:cached_network_image/cached_network_image.dart'; 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/views/camera/image_editor/layers/filters/location_filter.dart'; import 'package:url_launcher/url_launcher.dart'; class UrlListTitle extends StatelessWidget { - final String title; + final String? title; final String url; final String? subtitle; + final Widget? leading; - const UrlListTitle( - {super.key, required this.title, required this.url, this.subtitle}); + const UrlListTitle({ + super.key, + required this.title, + required this.url, + this.leading, + this.subtitle, + }); @override Widget build(BuildContext context) { return ListTile( - title: Text(title), + leading: leading, + title: (title != null) ? Text(title!) : null, subtitle: subtitle == null ? null : Text(subtitle!), onTap: () { launchUrl(Uri.parse(url)); @@ -24,9 +33,27 @@ class UrlListTitle extends StatelessWidget { } } -class CreditsView extends StatelessWidget { +class CreditsView extends StatefulWidget { const CreditsView({super.key}); + @override + State createState() => _CreditsViewState(); +} + +class _CreditsViewState extends State { + List sticker = []; + + @override + void initState() { + super.initState(); + initAsync(); + } + + Future initAsync() async { + sticker = (await getStickerIndex()).where((x) => x.source != "").toList(); + setState(() {}); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -109,46 +136,25 @@ class CreditsView extends StatelessWidget { url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv", ), const Divider(), - ListTile( - title: Center( - child: Text( - "Filters", - style: TextStyle(fontWeight: FontWeight.bold), - )), - ), - UrlListTitle( - title: "Avo Cardio", - subtitle: "by RalfDesign", - url: - "https://pixabay.com/illustrations/avocado-cartoon-funny-cardio-gym-4570642/", - ), - UrlListTitle( - title: "Sloth", - subtitle: "by RalfDesign", - url: - "https://pixabay.com/illustrations/sloth-swimming-summer-pool-cartoon-4575121/", - ), - UrlListTitle( - title: "Duck", - subtitle: "by lachkegeetanjali", - url: - "https://pixabay.com/de/vectors/ente-gans-meme-lustig-k%C3%A4mpfen-8409656/", - ), - UrlListTitle( - title: "Lol", - subtitle: "TheDigitalArtist", - url: - "https://pixabay.com/de/illustrations/lachen-lustig-l%C3%A4cheln-spa%C3%9F-meme-7820654/", - ), - UrlListTitle( - title: "Yolo", - subtitle: "TheDigitalArtist", - url: - "https://pixabay.com/illustrations/yolo-meme-modern-live-once-phrase-7820660/", - ), - UrlListTitle( - title: "Hide The Pain Arold", - url: "https://hidethepainharold.com/", + if (sticker.isNotEmpty) + ListTile( + title: Center( + child: Text( + "Filters", + style: TextStyle(fontWeight: FontWeight.bold), + )), + ), + ...sticker.map( + (x) => UrlListTitle( + leading: SizedBox( + height: 50, + width: 50, + child: CachedNetworkImage( + imageUrl: "https://twonly.eu/${x.imageSrc}"), + ), + title: "", + url: x.source, + ), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index b646d61..e6aa7d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,4 @@ flutter: - assets/images/ - assets/animated_icons/ - assets/animations/ - - assets/filters/ - - assets/filters/random/