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 emojiKey = GlobalKey();
int pointers = 0;
bool display = false;
@override
void initState() {
@ -38,15 +39,19 @@ class _EmojiLayerState extends State<EmojiLayer> {
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,

View file

@ -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<FilterLayer> 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<FilterLayer> {
final PageController pageController = PageController();
final List<Widget> pages = [
FilterSceleton(),
List<Widget> 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<FilterLayer> {
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

View file

@ -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,

View file

@ -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,
),
),

View file

@ -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<LocationFilter> {
if (res.isSuccess) {
location = res.value.location;
_searchForImage();
setState(() {});
if (mounted) setState(() {});
}
}
void _searchForImage() async {
if (location == null) return;
List<String> imageIndex = await getStickerIndex();
List<Sticker> 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<LocationFilter> {
// 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<LocationFilter> {
@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<LocationFilter> {
if (location != null) {
if (location!.county != "-") {
return FilterSceleton(
return FilterSkeleton(
child: Positioned(
bottom: 50,
left: 40,
@ -109,24 +111,42 @@ class _LocationFilterState extends State<LocationFilter> {
}
}
Future<List<String>> 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<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 difference = DateTime.now().difference(lastModified);
if (difference.inHours < 24) {
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 =
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<String>().toList();
List<dynamic> jsonList = json.decode(response.body);
return jsonList.map((json) => Sticker.fromJson(json)).toList();
} else {
return [];
return res;
}
}

View file

@ -29,6 +29,8 @@ class _TextViewState extends State<TextLayer> {
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<TextLayer> {
});
},
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,
),
),
),

View file

@ -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<TextLayerData>().map((layerItem) {
return TextLayer(
key: GlobalKey(),
layerData: layerItem,
onUpdate: onUpdate,
);

View file

@ -72,10 +72,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
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<ShareImageEditorView> {
if (!context.mounted) return;
layers.clear();
layers.add(BackgroundLayerData(
layers.insert(
0,
BackgroundLayerData(
image: currentImage,
));
layers.add(FilterLayerData());
),
);
setState(() {
sendingOrLoadingImage = false;
});
@ -465,6 +464,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (layers.any((x) => x.isEditing)) {
return;
}
layers = layers.where((x) => !x.isDeleted).toList();
undoLayers.clear();
removedLayers.clear();
layers.add(TextLayerData(

View file

@ -46,7 +46,7 @@ class _ShareImageView extends State<ShareImageView> {
List<Contact> contacts = [];
List<Contact> _otherUsers = [];
List<Contact> _bestFriends = [];
List<Contact> _pinnedContacs = [];
List<Contact> _pinnedContacts = [];
Uint8List? imageBytes;
bool sendingImage = false;
bool hideArchivedUsers = true;
@ -122,7 +122,7 @@ class _ShareImageView extends State<ShareImageView> {
setState(() {
_bestFriends = bestFriends;
_pinnedContacs = pinnedContacts;
_pinnedContacts = pinnedContacts;
_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(
users: _pinnedContacs,
users: _pinnedContacts,
selectedUserIds: widget.selectedUserIds,
isRealTwonly: widget.isRealTwonly,
updateStatus: updateStatus,

View file

@ -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<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
Widget build(BuildContext context) {
return Scaffold(
@ -109,6 +136,7 @@ class CreditsView extends StatelessWidget {
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv",
),
const Divider(),
if (sticker.isNotEmpty)
ListTile(
title: Center(
child: Text(
@ -116,39 +144,17 @@ class CreditsView extends StatelessWidget {
style: TextStyle(fontWeight: FontWeight.bold),
)),
),
UrlListTitle(
title: "Avo Cardio",
subtitle: "by RalfDesign",
url:
"https://pixabay.com/illustrations/avocado-cartoon-funny-cardio-gym-4570642/",
...sticker.map(
(x) => UrlListTitle(
leading: SizedBox(
height: 50,
width: 50,
child: CachedNetworkImage(
imageUrl: "https://twonly.eu/${x.imageSrc}"),
),
UrlListTitle(
title: "Sloth",
subtitle: "by RalfDesign",
url:
"https://pixabay.com/illustrations/sloth-swimming-summer-pool-cartoon-4575121/",
title: "",
url: x.source,
),
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/animated_icons/
- assets/animations/
- assets/filters/
- assets/filters/random/