mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
fix #111
This commit is contained in:
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 |
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
final content = await indexFile.readAsString();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
image: currentImage,
|
||||
));
|
||||
|
||||
layers.add(FilterLayerData());
|
||||
layers.insert(
|
||||
0,
|
||||
BackgroundLayerData(
|
||||
image: currentImage,
|
||||
),
|
||||
);
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -96,6 +96,4 @@ flutter:
|
|||
- assets/images/
|
||||
- assets/animated_icons/
|
||||
- assets/animations/
|
||||
- assets/filters/
|
||||
- assets/filters/random/
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue