diff --git a/app/lib/app_config.dart b/app/lib/app_config.dart index eae04122e..e7c14fc61 100644 --- a/app/lib/app_config.dart +++ b/app/lib/app_config.dart @@ -221,4 +221,5 @@ void setFallbackConfigs() { Globals().relayUrl = ''; Globals().termsAndConditionsUrl = ''; Globals().spendingLimit = 0; + Globals().newsUrl = ''; } diff --git a/app/lib/apps/news/news.dart b/app/lib/apps/news/news.dart index 33437a6e8..158dba4df 100644 --- a/app/lib/apps/news/news.dart +++ b/app/lib/apps/news/news.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/app.dart'; -import 'package:threebotlogin/apps/news/news_widget.dart'; +import 'package:threebotlogin/apps/news/news_screen.dart'; import 'package:threebotlogin/events/events.dart'; -import 'package:threebotlogin/apps/news/news_events.dart'; +import 'package:threebotlogin/events/go_home_event.dart'; class News implements App { static final News _singleton = News._internal(); - static const NewsWidget _newsWidget = NewsWidget(); + static const NewsScreen _newsWidget = NewsScreen(); factory News() { return _singleton; @@ -34,6 +34,6 @@ class News implements App { @override void back() { - Events().emit(NewsBackEvent()); + Events().emit(GoHomeEvent()); } } diff --git a/app/lib/apps/news/news_screen.dart b/app/lib/apps/news/news_screen.dart new file mode 100644 index 000000000..706f9436d --- /dev/null +++ b/app/lib/apps/news/news_screen.dart @@ -0,0 +1,280 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:http/http.dart' as http; +import 'package:threebotlogin/helpers/globals.dart'; +import 'package:threebotlogin/widgets/layout_drawer.dart'; +import 'package:xml2json/xml2json.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:timeago/timeago.dart' as timeago; + +class NewsScreen extends StatefulWidget { + const NewsScreen({super.key}); + + @override + State createState() => _NewsScreenState(); +} + +class _NewsScreenState extends State { + final Xml2Json xml2json = Xml2Json(); + List allArticles = []; + List visibleArticles = []; + bool isLoading = false; + bool isInitialLoading = true; + int articlesPerPage = 5; + int currentPage = 0; + final ScrollController _scrollController = ScrollController(); + final newsUrl = Globals().newsUrl; + + Future getArticles() async { + setState(() { + isLoading = true; + if (currentPage == 0) { + isInitialLoading = true; + } + }); + + final url = Uri.parse(newsUrl); + final response = await http.get(url); + + xml2json.parse(response.body); + + var jsondata = xml2json.toGData(); + var data = json.decode(jsondata); + + var allEntries = data['feed']['entry'] ?? []; + + setState(() { + allArticles = allEntries; + loadMoreArticles(); + + isLoading = false; + if (currentPage > 0) { + isInitialLoading = false; + } + }); + } + + void loadMoreArticles() { + final startIndex = currentPage * articlesPerPage; + final endIndex = startIndex + articlesPerPage; + + if (startIndex < allArticles.length) { + setState(() { + isLoading = true; + visibleArticles.addAll( + allArticles.sublist( + startIndex, endIndex.clamp(0, allArticles.length)), + ); + currentPage++; + }); + } + } + + @override + void initState() { + super.initState(); + getArticles(); + _scrollController.addListener(() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + loadMoreArticles(); + } else { + setState(() { + isLoading = false; + }); + } + }); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutDrawer( + titleText: 'News', + content: Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: visibleArticles.length + (isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index == visibleArticles.length && isInitialLoading) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only( + bottom: 4, + top: MediaQuery.of(context).size.height * 0.4), + child: const CircularProgressIndicator(), + ), + Text( + 'Loading Articles...', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ], + ); + } + var entry = visibleArticles[index]; + + var title = entry['title']?['\$t'] ?? 'No Title'; + var content = entry['content']?['\$t'] ?? 'No Content'; + var link = entry['link'] is List + ? entry['link'].first['href'] + : entry['link']['href']; + + var publishedDateStr = entry['published']?['\$t'] ?? ''; + DateTime publishedDate; + try { + publishedDate = DateTime.parse(publishedDateStr).toLocal(); + } catch (e) { + publishedDate = DateTime.now(); + } + + String formattedDate = timeago.format(publishedDate); + + content = cleanHtmlContent(content); + + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4.0), + child: Card( + elevation: 4.0, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset( + 'assets/tft_icon.png', + color: Theme.of(context).colorScheme.onSurface, + height: 20, + width: 20, + ), + const SizedBox(width: 2), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'THREEFOLD - ', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .onSurface, + )), + TextSpan( + text: formattedDate, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .onSurface, + )), + ], + ), + ), + ], + ), + const SizedBox( + height: 3, + ), + Text( + title, + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: + Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 5), + HtmlWidget( + content.length > 200 + ? '${content.substring(0, 200)}...' + : content, + textStyle: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + onTapUrl: (url) { + if (url.isNotEmpty) { + _launchURL(url); + return true; + } + return false; + }, + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () async { + _launchURL(link); + }, + child: Text( + 'Read more', + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith( + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ), + if (isLoading && !isInitialLoading) + const Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + ), + ], + ), + ); + } + + void _launchURL(String url) async { + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); + } else { + throw 'Could not launch $url'; + } + } + + String cleanHtmlContent(String content) { + return content.replaceAll(r'\\n', ''); + } +} diff --git a/app/lib/helpers/flags.dart b/app/lib/helpers/flags.dart index 54f1ed1e2..6b5f36c86 100644 --- a/app/lib/helpers/flags.dart +++ b/app/lib/helpers/flags.dart @@ -72,6 +72,8 @@ class Flags { (await Flags().getFlagValueByFeatureName('terms-conditions-url'))!; Globals().spendingLimit = int.parse( (await Flags().getFlagValueByFeatureName('spending-limit')).toString()); + Globals().newsUrl = + (await Flags().getFlagValueByFeatureName('news-url'))!; } Future hasFlagValueByFeatureName(String name) async { diff --git a/app/lib/helpers/globals.dart b/app/lib/helpers/globals.dart index 0921a5bca..cd8598684 100644 --- a/app/lib/helpers/globals.dart +++ b/app/lib/helpers/globals.dart @@ -59,6 +59,7 @@ class Globals { String activationUrl = ''; String relayUrl = ''; String termsAndConditionsUrl = ''; + String newsUrl = ''; bool isCacheClearedWallet = false; bool isCacheClearedFarmer = false; diff --git a/app/lib/screens/wizard/web_view.dart b/app/lib/screens/wizard/web_view.dart index 1815848d2..53066de81 100644 --- a/app/lib/screens/wizard/web_view.dart +++ b/app/lib/screens/wizard/web_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:threebotlogin/helpers/globals.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebView extends StatefulWidget { @@ -11,6 +12,7 @@ class WebView extends StatefulWidget { class _WebViewState extends State { bool isLoading = true; late WebViewController controller; + final termsAndConditionsUrl = Globals().termsAndConditionsUrl; @override void dispose() { @@ -34,7 +36,7 @@ class _WebViewState extends State { }, ), ) - ..loadRequest(Uri.parse('https://library.threefold.me/info/legal/')); + ..loadRequest(Uri.parse(termsAndConditionsUrl)); } @override diff --git a/app/pubspec.lock b/app/pubspec.lock index ebf2c5788..802876ee9 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -62,6 +62,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" + url: "https://pub.dev" + source: hosted + version: "0.1.21" base_x: dependency: transitive description: @@ -158,6 +166,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.2" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + url: "https://pub.dev" + source: hosted + version: "1.2.0" characters: dependency: transitive description: @@ -174,6 +206,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + chewie: + dependency: transitive + description: + name: chewie + sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144" + url: "https://pub.dev" + source: hosted + version: "1.7.5" clock: dependency: transitive description: @@ -234,10 +274,10 @@ packages: dependency: transitive description: name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "0.17.3" cupertino_icons: dependency: "direct main" description: @@ -254,6 +294,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" decimal: dependency: transitive description: @@ -379,6 +427,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_curve25519: dependency: transitive description: @@ -446,6 +502,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "9e2a6201c4d2eb910b6b3ebb2a9f5c490fc61c9a1aa35eafdde38f0fc659cf4c" + url: "https://pub.dev" + source: hosted + version: "0.15.2" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + sha256: b1048fd119a14762e2361bd057da608148a895477846d6149109b2151d2f7abf + url: "https://pub.dev" + source: hosted + version: "0.15.2" freezed_annotation: dependency: transitive description: @@ -462,6 +534,54 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "8e44226801bfba27930673953afce8af44da7e92573be93f60385d9865a089dd" + url: "https://pub.dev" + source: hosted + version: "0.14.3" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: "37bde9cedfb6dc5546176f7f0c56af1e814966cb33ec58f16c9565ed93ccb704" + url: "https://pub.dev" + source: hosted + version: "0.14.8" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: "38dc2c55803bd3cef33042c473e0c40b891ad4548078424641a32032f6a1245f" + url: "https://pub.dev" + source: hosted + version: "0.15.2" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "550b1014d12b5528d8bdb6e3b44b58721f3fb1f65d7a852d1623a817008bdfc4" + url: "https://pub.dev" + source: hosted + version: "0.8.3" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: b9f5d55a5ae2c2c07243ba33f7ba49ac9544bdb2f4c16d8139df9ccbebe3449c + url: "https://pub.dev" + source: hosted + version: "0.9.1" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: f67890bc0d6278da98bd197469ae9511c859f7db327e92299fe0ea0cf46c4057 + url: "https://pub.dev" + source: hosted + version: "0.15.2" glob: dependency: transitive description: @@ -489,9 +609,11 @@ packages: gridproxy_client: dependency: "direct main" description: - path: "../../../codescalers/tfgrid-sdk-dart/packages/gridproxy_client" - relative: true - source: path + path: "packages/gridproxy_client" + ref: tfchain_graphql + resolved-ref: bd83a0bb1778b78c1b0b3b6c34d93f2ca2b54d16 + url: "https://github.com/codescalers/tfgrid-sdk-dart" + source: git version: "1.0.0" hashlib: dependency: transitive @@ -597,6 +719,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4+9" + just_audio: + dependency: transitive + description: + name: just_audio + sha256: b41646a8241688f1d99c2e69c4da2bb26aa4b3a99795f6ff205c2a165e033fda + url: "https://pub.dev" + source: hosted + version: "0.9.41" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" + url: "https://pub.dev" + source: hosted + version: "4.3.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "0edb481ad4aa1ff38f8c40f1a3576013c3420bf6669b686fe661627d49bc606c" + url: "https://pub.dev" + source: hosted + version: "0.4.11" leak_tracker: dependency: transitive description: @@ -717,6 +863,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" open_filex: dependency: "direct main" description: @@ -1267,6 +1421,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" sr25519: dependency: transitive description: @@ -1294,9 +1464,11 @@ packages: stellar_client: dependency: "direct main" description: - path: "../../../codescalers/tfgrid-sdk-dart/packages/stellar_client" - relative: true - source: path + path: "packages/stellar_client" + ref: tfchain_graphql + resolved-ref: bd83a0bb1778b78c1b0b3b6c34d93f2ca2b54d16 + url: "https://github.com/codescalers/tfgrid-sdk-dart" + source: git version: "0.1.0" stellar_flutter_sdk: dependency: transitive @@ -1381,10 +1553,20 @@ packages: tfchain_client: dependency: "direct main" description: - path: "../../../codescalers/tfgrid-sdk-dart/packages/tfchain_client" - relative: true - source: path + path: "packages/tfchain_client" + ref: tfchain_graphql + resolved-ref: bd83a0bb1778b78c1b0b3b6c34d93f2ca2b54d16 + url: "https://github.com/codescalers/tfgrid-sdk-dart" + source: git version: "0.1.0" + timeago: + dependency: "direct main" + description: + name: timeago + sha256: "054cedf68706bb142839ba0ae6b135f6b68039f0b8301cbe8784ae653d5ff8de" + url: "https://pub.dev" + source: hosted + version: "3.7.0" timing: dependency: transitive description: @@ -1585,6 +1767,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: transitive + description: + name: video_player + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + url: "https://pub.dev" + source: hosted + version: "2.9.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: e343701aa890b74a863fa460f5c0e628127ed06a975d7d9af6b697133fb25bdf + url: "https://pub.dev" + source: hosted + version: "2.7.1" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f + url: "https://pub.dev" + source: hosted + version: "2.6.2" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" + url: "https://pub.dev" + source: hosted + version: "2.2.0" vm_service: dependency: transitive description: @@ -1593,6 +1815,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + url: "https://pub.dev" + source: hosted + version: "1.2.1" watcher: dependency: transitive description: @@ -1681,6 +1919,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.0" + xml2json: + dependency: "direct main" + description: + name: xml2json + sha256: "52b7c8d350fdce09545b058982c26689ee89f1eb188cc9910d585665bfe27bc0" + url: "https://pub.dev" + source: hosted + version: "6.2.3" yaml: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a732ea43b..969334377 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -66,6 +66,9 @@ dependencies: screen_brightness: ^1.0.1 validators: ^3.0.0 lottie: ^3.1.2 + xml2json: ^6.2.3 + timeago: ^3.7.0 + flutter_widget_from_html: ^0.15.2 dev_dependencies: flutter_test: