This commit is contained in:
otsmr 2025-06-03 16:58:37 +02:00
parent a339c6c9df
commit 4988242901
17 changed files with 147 additions and 101 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View file

@ -28,6 +28,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
final GlobalKey outlineKey = GlobalKey(); final GlobalKey outlineKey = GlobalKey();
final GlobalKey emojiKey = GlobalKey(); final GlobalKey emojiKey = GlobalKey();
int pointers = 0; int pointers = 0;
bool display = false;
@override @override
void initState() { void initState() {
@ -38,15 +39,19 @@ class _EmojiLayerState extends State<EmojiLayer> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() { setState(() {
widget.layerData.offset = Offset( widget.layerData.offset = Offset(
MediaQuery.of(context).size.width / 2 - 64, MediaQuery.of(context).size.width / 2 - (153 / 2),
MediaQuery.of(context).size.height / 2 - 64 - 100); MediaQuery.of(context).size.height / 2 - (153 / 2) - 100);
}); });
display = true;
}); });
} else {
display = true;
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!display) return Container();
if (widget.layerData.isDeleted) return Container(); if (widget.layerData.isDeleted) return Container();
return Stack( return Stack(
key: outlineKey, key: outlineKey,

View file

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.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/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart';
@ -19,8 +20,8 @@ class FilterLayer extends StatefulWidget {
State<FilterLayer> createState() => _FilterLayerState(); State<FilterLayer> createState() => _FilterLayerState();
} }
class FilterSceleton extends StatelessWidget { class FilterSkeleton extends StatelessWidget {
const FilterSceleton({super.key, this.child}); const FilterSkeleton({super.key, this.child});
final Widget? child; final Widget? child;
@override @override
@ -65,17 +66,11 @@ class FilterText extends StatelessWidget {
class _FilterLayerState extends State<FilterLayer> { class _FilterLayerState extends State<FilterLayer> {
final PageController pageController = PageController(); final PageController pageController = PageController();
final List<Widget> pages = [ List<Widget> pages = [
FilterSceleton(), FilterSkeleton(),
DateTimeFilter(), DateTimeFilter(),
LocationFilter(), LocationFilter(),
ImageFilter(imagePath: "random/lol.png"), FilterSkeleton(),
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(),
]; ];
@override @override
@ -84,6 +79,20 @@ class _FilterLayerState extends State<FilterLayer> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
pageController.jumpToPage(1); 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 @override

View file

@ -11,7 +11,7 @@ class DateTimeFilter extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
String currentTime = DateFormat('HH:mm').format(DateTime.now()); String currentTime = DateFormat('HH:mm').format(DateTime.now());
String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now()); String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
return FilterSceleton( return FilterSkeleton(
child: Positioned( child: Positioned(
bottom: 80, bottom: 80,
left: 40, left: 40,

View file

@ -1,3 +1,4 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
@ -7,14 +8,14 @@ class ImageFilter extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FilterSceleton( return FilterSkeleton(
child: Positioned( child: Positioned(
bottom: 50, bottom: 50,
left: 10, left: 10,
right: 10, right: 10,
child: Center( child: Center(
child: Image.asset( child: CachedNetworkImage(
"assets/filters/$imagePath", imageUrl: "https://twonly.eu/$imagePath",
height: 150, height: 150,
), ),
), ),

View file

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
@ -31,25 +32,25 @@ class _LocationFilterState extends State<LocationFilter> {
if (res.isSuccess) { if (res.isSuccess) {
location = res.value.location; location = res.value.location;
_searchForImage(); _searchForImage();
setState(() {}); if (mounted) setState(() {});
} }
} }
void _searchForImage() async { void _searchForImage() async {
if (location == null) return; if (location == null) return;
List<String> imageIndex = await getStickerIndex(); List<Sticker> imageIndex = await getStickerIndex();
// Normalize the city and country for search // Normalize the city and country for search
String normalizedCity = location!.city.toLowerCase().replaceAll(' ', '_'); String normalizedCity = location!.city.toLowerCase().replaceAll(' ', '_');
String normalizedCountry = location!.county.toLowerCase(); String normalizedCountry = location!.county.toLowerCase();
// Search for the city first // Search for the city first
for (var item in imageIndex) { for (var item in imageIndex) {
if (item.contains('/cities/$normalizedCountry/')) { if (item.imageSrc.contains('/cities/$normalizedCountry/')) {
// Check if the item matches the normalized city // Check if the item matches the normalized city
if (item.endsWith('$normalizedCity.png')) { if (item.imageSrc.endsWith('$normalizedCity.png')) {
if (item.startsWith("/api/")) { if (item.imageSrc.startsWith("/api/")) {
_imageUrl = "https://twonly.eu/$item"; _imageUrl = "https://twonly.eu/$item";
setState(() {}); if (mounted) setState(() {});
} }
return; return;
} }
@ -59,10 +60,11 @@ class _LocationFilterState extends State<LocationFilter> {
// If city not found, search for the country // If city not found, search for the country
if (_imageUrl == null) { if (_imageUrl == null) {
for (var item in imageIndex) { for (var item in imageIndex) {
if (item.contains('/countries/') && item.contains(normalizedCountry)) { if (item.imageSrc.contains('/countries/') &&
if (item.startsWith("/api/")) { item.imageSrc.contains(normalizedCountry)) {
if (item.imageSrc.startsWith("/api/")) {
_imageUrl = "https://twonly.eu/$item"; _imageUrl = "https://twonly.eu/$item";
setState(() {}); if (mounted) setState(() {});
} }
break; break;
} }
@ -73,7 +75,7 @@ class _LocationFilterState extends State<LocationFilter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_imageUrl != null) { if (_imageUrl != null) {
return FilterSceleton( return FilterSkeleton(
child: Positioned( child: Positioned(
bottom: 0, bottom: 0,
left: 40, left: 40,
@ -89,7 +91,7 @@ class _LocationFilterState extends State<LocationFilter> {
if (location != null) { if (location != null) {
if (location!.county != "-") { if (location!.county != "-") {
return FilterSceleton( return FilterSkeleton(
child: Positioned( child: Positioned(
bottom: 50, bottom: 50,
left: 40, left: 40,
@ -109,24 +111,42 @@ class _LocationFilterState extends State<LocationFilter> {
} }
} }
Future<List<String>> getStickerIndex() async { class Sticker {
final directory = await getApplicationCacheDirectory(); final String imageSrc;
final indexFile = File('${directory.path}/index.json'); final String source;
if (await indexFile.exists()) { Sticker({required this.imageSrc, required this.source});
factory Sticker.fromJson(Map<String, dynamic> json) {
return Sticker(
imageSrc: json['imageSrc'],
source: json['source'] ?? '', // Handle null source
);
}
}
Future<List<Sticker>> getStickerIndex() async {
final directory = await getApplicationCacheDirectory();
final indexFile = File('${directory.path}/stickers.json');
List<Sticker> res = [];
if (await indexFile.exists() && !kDebugMode) {
final lastModified = await indexFile.lastModified(); final lastModified = await indexFile.lastModified();
final difference = DateTime.now().difference(lastModified); final difference = DateTime.now().difference(lastModified);
if (difference.inHours < 24) {
final content = await indexFile.readAsString(); final content = await indexFile.readAsString();
return await json.decode(content).whereType<String>().toList(); List<dynamic> jsonList = json.decode(content);
res = jsonList.map((json) => Sticker.fromJson(json)).toList();
if (difference.inHours < 2) {
return res;
} }
} }
final response = 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) { if (response.statusCode == 200) {
await indexFile.writeAsString(response.body); await indexFile.writeAsString(response.body);
return json.decode(response.body).whereType<String>().toList(); List<dynamic> jsonList = json.decode(response.body);
return jsonList.map((json) => Sticker.fromJson(json)).toList();
} else { } else {
return []; return res;
} }
} }

View file

@ -29,6 +29,8 @@ class _TextViewState extends State<TextLayer> {
void initState() { void initState() {
super.initState(); super.initState();
textController.text = widget.layerData.text;
if (widget.layerData.offset.dy == 0) { if (widget.layerData.offset.dy == 0) {
// Set the initial offset to the center of the screen // Set the initial offset to the center of the screen
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -78,14 +80,17 @@ class _TextViewState extends State<TextLayer> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, // Keine Umrandung border: InputBorder.none,
contentPadding: EdgeInsets.zero, // Kein Padding isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
), ),
// widget.layerData.text.toString(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 17, fontSize: 20,
), ),
), ),
), ),

View file

@ -39,6 +39,7 @@ class LayersViewer extends StatelessWidget {
.map((layerItem) { .map((layerItem) {
if (layerItem is EmojiLayerData) { if (layerItem is EmojiLayerData) {
return EmojiLayer( return EmojiLayer(
key: GlobalKey(),
layerData: layerItem, layerData: layerItem,
onUpdate: onUpdate, onUpdate: onUpdate,
); );
@ -52,6 +53,7 @@ class LayersViewer extends StatelessWidget {
}), }),
...layers.whereType<TextLayerData>().map((layerItem) { ...layers.whereType<TextLayerData>().map((layerItem) {
return TextLayer( return TextLayer(
key: GlobalKey(),
layerData: layerItem, layerData: layerItem,
onUpdate: onUpdate, onUpdate: onUpdate,
); );

View file

@ -72,10 +72,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
super.initState(); super.initState();
initAsync(); initAsync();
initMediaFileUpload(); initMediaFileUpload();
layers.add(FilterLayerData());
if (widget.imageBytes != null) { if (widget.imageBytes != null) {
loadImage(widget.imageBytes!); loadImage(widget.imageBytes!);
} else if (widget.videoFilePath != null) { } else if (widget.videoFilePath != null) {
layers.add(FilterLayerData());
setState(() { setState(() {
sendingOrLoadingImage = false; sendingOrLoadingImage = false;
}); });
@ -373,13 +373,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (!context.mounted) return; if (!context.mounted) return;
layers.clear(); layers.insert(
0,
layers.add(BackgroundLayerData( BackgroundLayerData(
image: currentImage, image: currentImage,
)); ),
);
layers.add(FilterLayerData());
setState(() { setState(() {
sendingOrLoadingImage = false; sendingOrLoadingImage = false;
}); });
@ -465,6 +464,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (layers.any((x) => x.isEditing)) { if (layers.any((x) => x.isEditing)) {
return; return;
} }
layers = layers.where((x) => !x.isDeleted).toList();
undoLayers.clear(); undoLayers.clear();
removedLayers.clear(); removedLayers.clear();
layers.add(TextLayerData( layers.add(TextLayerData(

View file

@ -46,7 +46,7 @@ class _ShareImageView extends State<ShareImageView> {
List<Contact> contacts = []; List<Contact> contacts = [];
List<Contact> _otherUsers = []; List<Contact> _otherUsers = [];
List<Contact> _bestFriends = []; List<Contact> _bestFriends = [];
List<Contact> _pinnedContacs = []; List<Contact> _pinnedContacts = [];
Uint8List? imageBytes; Uint8List? imageBytes;
bool sendingImage = false; bool sendingImage = false;
bool hideArchivedUsers = true; bool hideArchivedUsers = true;
@ -122,7 +122,7 @@ class _ShareImageView extends State<ShareImageView> {
setState(() { setState(() {
_bestFriends = bestFriends; _bestFriends = bestFriends;
_pinnedContacs = pinnedContacts; _pinnedContacts = pinnedContacts;
_otherUsers = otherUsers; _otherUsers = otherUsers;
}); });
} }
@ -188,9 +188,9 @@ class _ShareImageView extends State<ShareImageView> {
), ),
), ),
), ),
if (_pinnedContacs.isNotEmpty) const SizedBox(height: 10), if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10),
BestFriendsSelector( BestFriendsSelector(
users: _pinnedContacs, users: _pinnedContacts,
selectedUserIds: widget.selectedUserIds, selectedUserIds: widget.selectedUserIds,
isRealTwonly: widget.isRealTwonly, isRealTwonly: widget.isRealTwonly,
updateStatus: updateStatus, updateStatus: updateStatus,

View file

@ -1,20 +1,29 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/misc.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'; import 'package:url_launcher/url_launcher.dart';
class UrlListTitle extends StatelessWidget { class UrlListTitle extends StatelessWidget {
final String title; final String? title;
final String url; final String url;
final String? subtitle; final String? subtitle;
final Widget? leading;
const UrlListTitle( const UrlListTitle({
{super.key, required this.title, required this.url, this.subtitle}); super.key,
required this.title,
required this.url,
this.leading,
this.subtitle,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
title: Text(title), leading: leading,
title: (title != null) ? Text(title!) : null,
subtitle: subtitle == null ? null : Text(subtitle!), subtitle: subtitle == null ? null : Text(subtitle!),
onTap: () { onTap: () {
launchUrl(Uri.parse(url)); launchUrl(Uri.parse(url));
@ -24,9 +33,27 @@ class UrlListTitle extends StatelessWidget {
} }
} }
class CreditsView extends StatelessWidget { class CreditsView extends StatefulWidget {
const CreditsView({super.key}); const CreditsView({super.key});
@override
State<CreditsView> createState() => _CreditsViewState();
}
class _CreditsViewState extends State<CreditsView> {
List<Sticker> sticker = [];
@override
void initState() {
super.initState();
initAsync();
}
Future initAsync() async {
sticker = (await getStickerIndex()).where((x) => x.source != "").toList();
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -109,6 +136,7 @@ class CreditsView extends StatelessWidget {
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv", url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv",
), ),
const Divider(), const Divider(),
if (sticker.isNotEmpty)
ListTile( ListTile(
title: Center( title: Center(
child: Text( child: Text(
@ -116,39 +144,17 @@ class CreditsView extends StatelessWidget {
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
)), )),
), ),
UrlListTitle( ...sticker.map(
title: "Avo Cardio", (x) => UrlListTitle(
subtitle: "by RalfDesign", leading: SizedBox(
url: height: 50,
"https://pixabay.com/illustrations/avocado-cartoon-funny-cardio-gym-4570642/", width: 50,
child: CachedNetworkImage(
imageUrl: "https://twonly.eu/${x.imageSrc}"),
), ),
UrlListTitle( title: "",
title: "Sloth", url: x.source,
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/",
), ),
], ],
), ),

View file

@ -96,6 +96,4 @@ flutter:
- assets/images/ - assets/images/
- assets/animated_icons/ - assets/animated_icons/
- assets/animations/ - assets/animations/
- assets/filters/
- assets/filters/random/