From e707f8b1c84bf270342c561790b7a1504b79c15b Mon Sep 17 00:00:00 2001 From: jfbriere Date: Tue, 27 Feb 2024 01:29:39 -0500 Subject: [PATCH] Implement feature #218 Share tweet as image. --- .../metadata/android/en-US/changelogs/40.txt | 1 + .../android/en-US/changelogs/default.txt | 1 + lib/generated/intl/messages_ar.dart | 4 + lib/generated/intl/messages_en.dart | 2 + lib/generated/intl/messages_es.dart | 50 ++++++++++++ lib/generated/intl/messages_et.dart | 5 ++ lib/generated/intl/messages_fr.dart | 2 + lib/generated/intl/messages_tr.dart | 4 + lib/generated/intl/messages_zh_Hans.dart | 11 +++ lib/generated/l10n.dart | 10 +++ lib/l10n/intl_en.arb | 4 +- lib/l10n/intl_fr.arb | 4 +- lib/tweet/tweet.dart | 79 ++++++++++++------- pubspec.lock | 4 +- pubspec.yaml | 2 +- 15 files changed, 148 insertions(+), 35 deletions(-) diff --git a/fastlane/metadata/android/en-US/changelogs/40.txt b/fastlane/metadata/android/en-US/changelogs/40.txt index 92b82c3e..d14f9a62 100644 --- a/fastlane/metadata/android/en-US/changelogs/40.txt +++ b/fastlane/metadata/android/en-US/changelogs/40.txt @@ -1 +1,2 @@ * Fix issue #226 App does not open, showing a black screen. Some db records have unexpected null fields. +* Implement feature #218 Share tweet as image. diff --git a/fastlane/metadata/android/en-US/changelogs/default.txt b/fastlane/metadata/android/en-US/changelogs/default.txt index 92b82c3e..d14f9a62 100644 --- a/fastlane/metadata/android/en-US/changelogs/default.txt +++ b/fastlane/metadata/android/en-US/changelogs/default.txt @@ -1 +1,2 @@ * Fix issue #226 App does not open, showing a black screen. Some db records have unexpected null fields. +* Implement feature #218 Share tweet as image. diff --git a/lib/generated/intl/messages_ar.dart b/lib/generated/intl/messages_ar.dart index 9c1deb82..0121efbd 100644 --- a/lib/generated/intl/messages_ar.dart +++ b/lib/generated/intl/messages_ar.dart @@ -352,6 +352,10 @@ class MessageLookup extends MessageLookupByLibrary { "تحتوي هذه التغريدة على محتوى يحتمل أن يكون حساسا. هل ترغب في مشاهدته؟"), "prefix": MessageLookupByLibrary.simpleMessage("السابقة"), "private_profile": MessageLookupByLibrary.simpleMessage("ملف شخصي خاص"), + "proxy_description": + MessageLookupByLibrary.simpleMessage("الوكيل لجميع الطلبات"), + "proxy_error": MessageLookupByLibrary.simpleMessage("خطأ في الوكيل"), + "proxy_label": MessageLookupByLibrary.simpleMessage("الوكيل"), "regular_accounts": m12, "released_under_the_mit_license": MessageLookupByLibrary.simpleMessage( "منشور تحت رخصة الإم أي تي (MIT License)"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index ae88ae24..dc0a9a08 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -392,6 +392,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Custom share URL"), "share_base_url_description": MessageLookupByLibrary.simpleMessage( "Use a custom base URL when sharing"), + "share_tweet_as_image": + MessageLookupByLibrary.simpleMessage("Share tweet as image"), "share_tweet_content": MessageLookupByLibrary.simpleMessage("Share tweet content"), "share_tweet_content_and_link": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/intl/messages_es.dart b/lib/generated/intl/messages_es.dart index 82204ea4..da37a700 100644 --- a/lib/generated/intl/messages_es.dart +++ b/lib/generated/intl/messages_es.dart @@ -20,20 +20,53 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'es'; + static String m1(fileName) => "Datos exportados a ${fileName}"; + + static String m2(fullPath) => "Datos exportados a ${fullPath}"; + + static String m8(date) => "Se unió ${date}"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "add_to_group": MessageLookupByLibrary.simpleMessage("Añadir al grupo"), + "cancel": MessageLookupByLibrary.simpleMessage("Cancelar"), "could_not_find_any_tweets_by_this_user": MessageLookupByLibrary.simpleMessage( "¡No se ha podido encontrar ningún tuit de este usuario!"), "could_not_find_any_tweets_from_the_last_7_days": MessageLookupByLibrary.simpleMessage( "No pude encontrar ningún tweet de los últimos 7 días!"), + "data_exported_to_fileName": m1, + "data_exported_to_fullPath": m2, + "data_imported_successfully": MessageLookupByLibrary.simpleMessage( + "Datos importados correctamente"), + "default_tab": + MessageLookupByLibrary.simpleMessage("Pestaña por defecto"), + "export": MessageLookupByLibrary.simpleMessage("Exportar"), + "export_guest_accounts": MessageLookupByLibrary.simpleMessage( + "¿Exportar cuentas de invitados?"), + "export_settings": + MessageLookupByLibrary.simpleMessage("¿Exportar configuración?"), + "export_subscription_group_members": + MessageLookupByLibrary.simpleMessage( + "¿Exportar miembros de grupos de suscripción?"), + "export_subscription_groups": MessageLookupByLibrary.simpleMessage( + "¿Exportar grupos de suscripciones?"), + "export_subscriptions": MessageLookupByLibrary.simpleMessage( + "¿Exportar las suscripciones?"), + "export_tweets": + MessageLookupByLibrary.simpleMessage("¿Exportar Tweets?"), + "feed": MessageLookupByLibrary.simpleMessage("Inicio"), "filters": MessageLookupByLibrary.simpleMessage("Filtros"), + "followers": MessageLookupByLibrary.simpleMessage("Seguidores"), + "following": MessageLookupByLibrary.simpleMessage("Siguiendo"), + "general": MessageLookupByLibrary.simpleMessage("General"), "include_replies": MessageLookupByLibrary.simpleMessage("Incluye las respuestas"), "include_retweets": MessageLookupByLibrary.simpleMessage("Incluye los retweets"), + "joined": m8, + "media": MessageLookupByLibrary.simpleMessage("Multimedia"), "no_results": MessageLookupByLibrary.simpleMessage("No hay resultados"), "note_due_to_a_twitter_limitation_not_all_tweets_may_be_included": MessageLookupByLibrary.simpleMessage( @@ -41,18 +74,28 @@ class MessageLookup extends MessageLookupByLibrary { "reporting_an_error": MessageLookupByLibrary.simpleMessage("Informar sobre un error"), "saved": MessageLookupByLibrary.simpleMessage("Guardado"), + "select": MessageLookupByLibrary.simpleMessage("Seleccionar"), + "send": MessageLookupByLibrary.simpleMessage("Enviar"), + "settings": MessageLookupByLibrary.simpleMessage("Configuración"), "something_just_went_wrong_in_fritter_and_an_error_report_has_been_generated": MessageLookupByLibrary.simpleMessage( "Algo ha ido mal en Squawker y se ha generado un informe de error. El informe puede ser enviado a los desarrolladores de Squawker para ayudar a solucionar el problema."), "subscribe": MessageLookupByLibrary.simpleMessage("suscribirse"), "subscriptions": MessageLookupByLibrary.simpleMessage("Subscripciones"), + "thanks_for_helping_fritter": MessageLookupByLibrary.simpleMessage( + "Gracias por ayudar a Squawker! 💖"), "this_group_contains_no_subscriptions": MessageLookupByLibrary.simpleMessage( "¡Este grupo no contiene suscripciones!"), "this_user_does_not_follow_anyone": MessageLookupByLibrary.simpleMessage( "¡Este usuario no sigue a nadie!"), + "this_user_does_not_have_anyone_following_them": + MessageLookupByLibrary.simpleMessage( + "¡Este usuario no tiene a nadie siguiéndole!"), "trending": MessageLookupByLibrary.simpleMessage("Tendencias"), + "tweets_and_replies": + MessageLookupByLibrary.simpleMessage("Tweets & Respuestas"), "unable_to_find_your_saved_tweets": MessageLookupByLibrary.simpleMessage( "No se pueden encontrar tus tweets guardados."), @@ -67,17 +110,24 @@ class MessageLookup extends MessageLookupByLibrary { "unable_to_load_the_list_of_follows": MessageLookupByLibrary.simpleMessage( "No se puede cargar la lista de seguimiento"), + "unable_to_load_the_next_page_of_follows": + MessageLookupByLibrary.simpleMessage( + "No se puede cargar la pagina siguiente"), "unable_to_load_the_next_page_of_replies": MessageLookupByLibrary.simpleMessage( "No se puede cargar la siguiente página de respuestas"), "unable_to_load_the_next_page_of_tweets": MessageLookupByLibrary.simpleMessage( "No se puede cargar la siguiente página de tweets"), + "unable_to_load_the_profile": MessageLookupByLibrary.simpleMessage( + "No se puede cargar el perfil"), "unable_to_load_the_search_results": MessageLookupByLibrary.simpleMessage( "No se pueden cargar los resultados de la búsqueda."), "unable_to_load_the_tweet": MessageLookupByLibrary.simpleMessage( "No se ha podido cargar el tuit"), + "unable_to_load_the_tweets": MessageLookupByLibrary.simpleMessage( + "No se pueden cargar los tuits"), "unable_to_load_the_tweets_for_the_feed": MessageLookupByLibrary.simpleMessage( "No se pueden cargar los tweets para el feed"), diff --git a/lib/generated/intl/messages_et.dart b/lib/generated/intl/messages_et.dart index 8a141e4c..6715a42d 100644 --- a/lib/generated/intl/messages_et.dart +++ b/lib/generated/intl/messages_et.dart @@ -370,6 +370,11 @@ class MessageLookup extends MessageLookupByLibrary { "prefix": MessageLookupByLibrary.simpleMessage("eesliide"), "private_profile": MessageLookupByLibrary.simpleMessage("Privaatne profiil"), + "proxy_description": MessageLookupByLibrary.simpleMessage( + "Kasuta puhverserverit kõikide päringute jaoks"), + "proxy_error": + MessageLookupByLibrary.simpleMessage("Puhverserveri viga"), + "proxy_label": MessageLookupByLibrary.simpleMessage("Puhverserver"), "regular_accounts": m12, "released_under_the_mit_license": MessageLookupByLibrary.simpleMessage( "Avaldatud MIT litsentsi alusel"), diff --git a/lib/generated/intl/messages_fr.dart b/lib/generated/intl/messages_fr.dart index 401e2fb2..0e88e934 100644 --- a/lib/generated/intl/messages_fr.dart +++ b/lib/generated/intl/messages_fr.dart @@ -408,6 +408,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("URL de partage personnalisé"), "share_base_url_description": MessageLookupByLibrary.simpleMessage( "Utiliser une URL de base personnalisée lors du partage"), + "share_tweet_as_image": + MessageLookupByLibrary.simpleMessage("Partager le tweet en image"), "share_tweet_content": MessageLookupByLibrary.simpleMessage( "Partager le contenu du tweet"), "share_tweet_content_and_link": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/intl/messages_tr.dart b/lib/generated/intl/messages_tr.dart index b722230b..4d115a8f 100644 --- a/lib/generated/intl/messages_tr.dart +++ b/lib/generated/intl/messages_tr.dart @@ -361,6 +361,10 @@ class MessageLookup extends MessageLookupByLibrary { "Bu tweet olası hassas içerik barındırmaktadır. Görüntülemek ister misiniz?"), "prefix": MessageLookupByLibrary.simpleMessage("ön ek"), "private_profile": MessageLookupByLibrary.simpleMessage("Gizli profil"), + "proxy_description": + MessageLookupByLibrary.simpleMessage("Tüm istekler için vekil"), + "proxy_error": MessageLookupByLibrary.simpleMessage("Vekil Hatası"), + "proxy_label": MessageLookupByLibrary.simpleMessage("Vekil"), "regular_accounts": m12, "released_under_the_mit_license": MessageLookupByLibrary.simpleMessage( "MIT Lisansı altında yayınlandı"), diff --git a/lib/generated/intl/messages_zh_Hans.dart b/lib/generated/intl/messages_zh_Hans.dart index dc225168..e208aad5 100644 --- a/lib/generated/intl/messages_zh_Hans.dart +++ b/lib/generated/intl/messages_zh_Hans.dart @@ -83,6 +83,14 @@ class MessageLookup extends MessageLookupByLibrary { "all": MessageLookupByLibrary.simpleMessage("全部"), "all_the_great_software_used_by_fritter": MessageLookupByLibrary.simpleMessage("Squawker 所使用的伟大项目😇"), + "allow_background_play_description": + MessageLookupByLibrary.simpleMessage("允许在后台播放"), + "allow_background_play_label": + MessageLookupByLibrary.simpleMessage("后台播放"), + "allow_background_play_other_apps_description": + MessageLookupByLibrary.simpleMessage("允许其他应用在后台播放"), + "allow_background_play_other_apps_label": + MessageLookupByLibrary.simpleMessage("其他后台应用"), "an_update_for_fritter_is_available": MessageLookupByLibrary.simpleMessage("Squawker 有新版本 🚀"), "app_info": MessageLookupByLibrary.simpleMessage("应用程序信息"), @@ -295,6 +303,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("该推文包含潜在的敏感内容。是否浏览?"), "prefix": MessageLookupByLibrary.simpleMessage("字首"), "private_profile": MessageLookupByLibrary.simpleMessage("个人简介"), + "proxy_description": MessageLookupByLibrary.simpleMessage("所有请求的代理"), + "proxy_error": MessageLookupByLibrary.simpleMessage("代理出错"), + "proxy_label": MessageLookupByLibrary.simpleMessage("代理"), "regular_accounts": m12, "released_under_the_mit_license": MessageLookupByLibrary.simpleMessage("以 MIT 许可证发布"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 93126aad..077aa2fa 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2975,6 +2975,16 @@ class L10n { args: [], ); } + + /// `Share tweet as image` + String get share_tweet_as_image { + return Intl.message( + 'Share tweet as image', + name: 'share_tweet_as_image', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index efafe3af..d54073fd 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -576,5 +576,7 @@ "proxy_description": "Proxy for all requests", "@proxy_description": {}, "proxy_error": "Proxy Error", - "@proxy_error": {} + "@proxy_error": {}, + "share_tweet_as_image": "Share tweet as image", + "@share_tweet_as_image": {} } diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 1204553c..555d2ba3 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -606,5 +606,7 @@ "proxy_description": "Proxy pour toutes les requêtes", "@proxy_description": {}, "proxy_error": "Erreur Proxy", - "@proxy_error": {} + "@proxy_error": {}, + "share_tweet_as_image": "Partager le tweet en image", + "@share_tweet_as_image": {} } diff --git a/lib/tweet/tweet.dart b/lib/tweet/tweet.dart index 75b40520..2fdc8100 100644 --- a/lib/tweet/tweet.dart +++ b/lib/tweet/tweet.dart @@ -1,6 +1,10 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + import 'package:auto_direction/auto_direction.dart'; import 'package:dart_twitter_api/twitter_api.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:squawker/client/client.dart'; import 'package:squawker/constants.dart'; import 'package:squawker/generated/l10n.dart'; @@ -71,6 +75,8 @@ class TweetTileState extends State with SingleTickerProviderStateMixi List _extraContextMenuItems = []; + final GlobalKey _globalKey = GlobalKey(); + static String? _convertRunesToText(Iterable runes, int start, [int? end]) { var string = runes.getRange(start, end).map((e) => String.fromCharCode(e)).join(''); if (string.isEmpty) { @@ -325,6 +331,21 @@ class TweetTileState extends State with SingleTickerProviderStateMixi return customContextMenuBuilder(context, editableTextState, _extraContextMenuItems, processTextActivity); } + Future captureWidget() async { + if (_globalKey.currentContext == null) { + return null; + } + final RenderRepaintBoundary boundary = _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary; + final ui.Image image = await boundary.toImage(); + final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + return null; + } + final Uint8List pngBytes = byteData.buffer.asUint8List(); + + return pngBytes; + } + @override Widget build(BuildContext context) { final prefs = PrefService.of(context, listen: false); @@ -511,7 +532,7 @@ class TweetTileState extends State with SingleTickerProviderStateMixi } }, child: Consumer( - builder: (context, model, child) => Card( + builder: (context, model, child) => RepaintBoundary(key: _globalKey, child: Card( child: Row( children: [ retweetSidebar, @@ -583,38 +604,38 @@ class TweetTileState extends State with SingleTickerProviderStateMixi var isSaved = model.isSaved(tweet.idStr!); if (isSaved) { return createSheetButton( - L10n.of(context).unsave, - Icons.bookmark_border_rounded, - () async { + L10n.of(context).unsave, Icons.bookmark_border_rounded, () async { await model.deleteSavedTweet(tweet.idStr!); Navigator.pop(context); - }, + } ); - } else { + } + else { return createSheetButton( - L10n.of(context).save, Icons.bookmark_border_rounded, () async { - await model.saveTweet(tweet.idStr!, tweet.user?.idStr, tweet.toJson()); - Navigator.pop(context); - }); + L10n.of(context).save, Icons.bookmark_border_rounded, () async { + await model.saveTweet(tweet.idStr!, tweet.user?.idStr, tweet.toJson()); + Navigator.pop(context); + } + ); } }), - createSheetButton( - L10n.of(context).share_tweet_content, - Icons.share, - () async { - Share.share(tweetText); - Navigator.pop(context); - }, - ), + createSheetButton(L10n.of(context).share_tweet_content, Icons.share, () async { + Share.share(tweetText); + Navigator.pop(context); + }), createSheetButton(L10n.of(context).share_tweet_link, Icons.share, () async { - Share.share( - '$shareBaseUrl/${tweet.user!.screenName}/status/${tweet.idStr}'); + Share.share('$shareBaseUrl/${tweet.user!.screenName}/status/${tweet.idStr}'); Navigator.pop(context); }), - createSheetButton(L10n.of(context).share_tweet_content_and_link, Icons.share, - () async { - Share.share( - '$tweetText\n\n$shareBaseUrl/${tweet.user!.screenName}/status/${tweet.idStr}'); + createSheetButton(L10n.of(context).share_tweet_content_and_link, Icons.share, () async { + Share.share('$tweetText\n\n$shareBaseUrl/${tweet.user!.screenName}/status/${tweet.idStr}'); + Navigator.pop(context); + }), + createSheetButton(L10n.of(context).share_tweet_as_image, Icons.share, () async { + Uint8List? imgBytes = await captureWidget(); + if (imgBytes != null) { + Share.shareXFiles([XFile.fromData(imgBytes, mimeType: 'image/png')]); + } Navigator.pop(context); }), const Padding( @@ -623,11 +644,9 @@ class TweetTileState extends State with SingleTickerProviderStateMixi thickness: 1.0, ), ), - createSheetButton( - L10n.of(context).cancel, - Icons.close_rounded, - () => Navigator.pop(context), - ) + createSheetButton(L10n.of(context).cancel, Icons.close_rounded, () { + Navigator.pop(context); + }) ], ) ); @@ -691,7 +710,7 @@ class TweetTileState extends State with SingleTickerProviderStateMixi ) ], ), - ) + )) ) ); } diff --git a/pubspec.lock b/pubspec.lock index 0bfca59f..8998c98d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1009,10 +1009,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd + sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "7.2.2" share_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0fbb78fe..85953baf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,7 +76,7 @@ dependencies: scroll_bottom_navigation_bar: ^4.0.0 scroll_to_index: ^3.0.1 scrollable_positioned_list: ^0.3.8 - share_plus: ^7.2.1 + share_plus: ^7.2.2 shared_preferences: ^2.2.2 socks5_proxy: ^1.0.4 sqflite: ^2.3.2