From abbfdaaa2897a6aa35d1cfdffeb745ae1a63d4ee Mon Sep 17 00:00:00 2001 From: Restioson Date: Tue, 23 Apr 2024 18:11:01 +0200 Subject: [PATCH 1/6] feat: use TradingView for candlesticks --- .../android/app/src/main/AndroidManifest.xml | 8 ++ mobile/lib/common/init_service.dart | 4 - .../application/candlestick_service.dart | 26 ---- .../trade/candlestick_change_notifier.dart | 43 ------- mobile/lib/features/trade/trade_screen.dart | 11 +- .../trade/tradingview_candlestick.dart | 113 ++++++++++++++++++ mobile/pubspec.lock | 44 +++++-- mobile/pubspec.yaml | 2 +- mobile/test/trade_test.dart | 44 ------- 9 files changed, 159 insertions(+), 136 deletions(-) delete mode 100644 mobile/lib/features/trade/application/candlestick_service.dart delete mode 100644 mobile/lib/features/trade/candlestick_change_notifier.dart create mode 100644 mobile/lib/features/trade/tradingview_candlestick.dart diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 70889ac29..f65d538bc 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -57,4 +57,12 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + + diff --git a/mobile/lib/common/init_service.dart b/mobile/lib/common/init_service.dart index e25c438a6..bf85a15e4 100644 --- a/mobile/lib/common/init_service.dart +++ b/mobile/lib/common/init_service.dart @@ -6,7 +6,6 @@ import 'package:get_10101/common/dlc_channel_service.dart'; import 'package:get_10101/common/domain/dlc_channel.dart'; import 'package:get_10101/common/domain/tentenone_config.dart'; import 'package:get_10101/features/brag/meme_service.dart'; -import 'package:get_10101/features/trade/candlestick_change_notifier.dart'; import 'package:get_10101/features/trade/order_change_notifier.dart'; import 'package:get_10101/features/trade/position_change_notifier.dart'; import 'package:get_10101/common/amount_denomination_change_notifier.dart'; @@ -16,7 +15,6 @@ import 'package:get_10101/features/trade/trade_value_change_notifier.dart'; import 'package:get_10101/features/wallet/application/wallet_service.dart'; import 'package:get_10101/features/wallet/wallet_change_notifier.dart'; import 'package:get_10101/features/trade/submit_order_change_notifier.dart'; -import 'package:get_10101/features/trade/application/candlestick_service.dart'; import 'package:get_10101/features/trade/application/order_service.dart'; import 'package:get_10101/features/trade/application/position_service.dart'; import 'package:get_10101/features/trade/application/trade_values_service.dart'; @@ -53,8 +51,6 @@ List createProviders() { ChangeNotifierProvider(create: (context) => OrderChangeNotifier(OrderService())), ChangeNotifierProvider(create: (context) => PositionChangeNotifier(PositionService())), ChangeNotifierProvider(create: (context) => WalletChangeNotifier(const WalletService())), - ChangeNotifierProvider( - create: (context) => CandlestickChangeNotifier(const CandlestickService()).initialize()), ChangeNotifierProvider(create: (context) => ServiceStatusNotifier()), ChangeNotifierProvider(create: (context) => DlcChannelChangeNotifier(dlcChannelService)), ChangeNotifierProvider(create: (context) => BackgroundTaskChangeNotifier()), diff --git a/mobile/lib/features/trade/application/candlestick_service.dart b/mobile/lib/features/trade/application/candlestick_service.dart deleted file mode 100644 index 7fe0075ce..000000000 --- a/mobile/lib/features/trade/application/candlestick_service.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:candlesticks/candlesticks.dart'; -import 'package:http/http.dart' as http; -import 'dart:async'; -import 'dart:convert'; - -class CandlestickService { - const CandlestickService(); - - Future> fetchCandles(int amount) async { - final uri = Uri.parse( - "https://www.bitmex.com/api/v1/trade/bucketed?binSize=1m&partial=false&symbol=XBTUSD&count=$amount&reverse=true"); - final res = await http.get(uri); - return (jsonDecode(res.body) as List).map((e) => _parse(e)).toList().toList(); - } - - Candle _parse(Map json) { - var date = DateTime.parse(json['timestamp']).toLocal(); - var high = json['high'].toDouble(); - var low = json['low'].toDouble(); - var open = json['open'].toDouble(); - var close = json['close'].toDouble(); - var volume = json['volume'].toDouble(); - - return Candle(date: date, high: high, low: low, open: open, close: close, volume: volume); - } -} diff --git a/mobile/lib/features/trade/candlestick_change_notifier.dart b/mobile/lib/features/trade/candlestick_change_notifier.dart deleted file mode 100644 index f1fca3fed..000000000 --- a/mobile/lib/features/trade/candlestick_change_notifier.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:async'; - -import 'package:candlesticks/candlesticks.dart'; -import 'package:flutter/material.dart'; -import 'package:get_10101/features/trade/application/candlestick_service.dart'; - -class CandlestickChangeNotifier extends ChangeNotifier { - late List candles = []; - - final CandlestickService _candlestickService; - Timer? timer; - - CandlestickChangeNotifier( - this._candlestickService, - ); - - CandlestickChangeNotifier initialize() { - _candlestickService.fetchCandles(1000).then((candles) { - this.candles = candles; - notifyListeners(); - }); - - timer = Timer.periodic(const Duration(seconds: 30), (Timer t) async { - final list = await _candlestickService.fetchCandles(1); - if (list.isNotEmpty) { - // we expect only one item to be in the list - var item = list[0]; - if (candles.isEmpty || candles[0].date.isBefore(item.date)) { - candles.insert(0, item); - notifyListeners(); - } - } - }); - - return this; - } - - @override - void dispose() { - timer!.cancel(); - super.dispose(); - } -} diff --git a/mobile/lib/features/trade/trade_screen.dart b/mobile/lib/features/trade/trade_screen.dart index ebe913a89..fc13a17d2 100644 --- a/mobile/lib/features/trade/trade_screen.dart +++ b/mobile/lib/features/trade/trade_screen.dart @@ -1,7 +1,5 @@ -import 'package:candlesticks/candlesticks.dart'; import 'package:flutter/material.dart'; import 'package:get_10101/common/domain/model.dart'; -import 'package:get_10101/features/trade/candlestick_change_notifier.dart'; import 'package:get_10101/features/trade/domain/direction.dart'; import 'package:get_10101/features/trade/domain/order.dart'; import 'package:get_10101/features/trade/domain/position.dart'; @@ -15,6 +13,7 @@ import 'package:get_10101/features/trade/trade_bottom_sheet_confirmation.dart'; import 'package:get_10101/features/trade/trade_tabs.dart'; import 'package:get_10101/features/trade/trade_theme.dart'; import 'package:get_10101/features/trade/trade_value_change_notifier.dart'; +import 'package:get_10101/features/trade/tradingview_candlestick.dart'; import 'package:get_10101/util/constants.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -39,8 +38,6 @@ class TradeScreen extends StatelessWidget { OrderChangeNotifier orderChangeNotifier = context.watch(); PositionChangeNotifier positionChangeNotifier = context.watch(); - CandlestickChangeNotifier candlestickChangeNotifier = - context.watch(); TradeValuesChangeNotifier tradeValuesChangeNotifier = context.read(); SubmitOrderChangeNotifier submitOrderChangeNotifier = context.read(); @@ -79,14 +76,12 @@ class TradeScreen extends StatelessWidget { ], ), const SizedBox(height: 5), - Column( + const Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( height: 250, - child: Candlesticks( - candles: candlestickChangeNotifier.candles, - ), + child: TradingViewCandlestick(), ) ], ), diff --git a/mobile/lib/features/trade/tradingview_candlestick.dart b/mobile/lib/features/trade/tradingview_candlestick.dart new file mode 100644 index 000000000..79ad592ae --- /dev/null +++ b/mobile/lib/features/trade/tradingview_candlestick.dart @@ -0,0 +1,113 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get_10101/common/global_keys.dart'; +import 'package:get_10101/common/snack_bar.dart'; +import 'package:get_10101/main.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class TradingViewCandlestick extends StatefulWidget { + const TradingViewCandlestick({super.key}); + + @override + State createState() => _TradingViewCandlestickState(); +} + +class _TradingViewCandlestickState extends State { + late final WebViewController controller; + + static bool enabled() => Platform.isAndroid || Platform.isIOS; + + @override + void initState() { + super.initState(); + + const Color bg = appBackgroundColor; + String rgba = "rgba(${bg.red}, ${bg.green}, ${bg.blue}, ${255.0 / bg.alpha})"; + String html = ''' + + + + + + + +
+ +
+
+ + +
+
+ + + + '''; + + if (enabled()) { + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..enableZoom(false) + ..setNavigationDelegate(NavigationDelegate( + onNavigationRequest: (req) async { + final uri = Uri.parse(req.url); + + if (uri.scheme == "about") { + return NavigationDecision.navigate; + } + + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + showSnackBar(ScaffoldMessenger.of(rootNavigatorKey.currentContext!), + "Failed to open link to TradingView"); + } + + return NavigationDecision.prevent; + }, + )) + ..loadHtmlString(html); + } + } + + @override + Widget build(BuildContext context) => enabled() + ? WebViewWidget(controller: controller) + : const Text("TradingView chart only supported on IOS and Android"); +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index ddb47a70f..2ea08b202 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -137,14 +137,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.6.1" - candlesticks: - dependency: "direct main" - description: - name: candlesticks - sha256: aeb7dff279f64bcfa1d07cdd18325a0a02f14e1af408ffb2d9f8ee796231876e - url: "https://pub.dev" - source: hosted - version: "2.1.0" card_swiper: dependency: "direct main" description: @@ -828,10 +820,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.8" pointycastle: dependency: transitive description: @@ -1365,6 +1357,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934 + url: "https://pub.dev" + source: hosted + version: "3.16.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7 + url: "https://pub.dev" + source: hosted + version: "3.13.0" win32: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a98c2e6c4..9fbd4eb9b 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -28,7 +28,6 @@ dependencies: share_plus: ^7.0.2 logger: ^2.0.2+1 carousel_slider: ^4.2.1 - candlesticks: ^2.1.0 http: ^1.0.0 timeago: ^3.3.0 shared_preferences: ^2.1.2 @@ -38,6 +37,7 @@ dependencies: test: ^1.24.1 version: ^3.0.2 firebase_core: ^2.15.0 + webview_flutter: ^4.7.0 firebase_messaging: ^14.6.5 flutter_local_notifications: ^15.1.0+1 url_launcher: ^6.1.14 diff --git a/mobile/test/trade_test.dart b/mobile/test/trade_test.dart index 0b9c20d69..6c7ca85cd 100644 --- a/mobile/test/trade_test.dart +++ b/mobile/test/trade_test.dart @@ -1,4 +1,3 @@ -import 'package:candlesticks/candlesticks.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge; @@ -11,15 +10,12 @@ import 'package:get_10101/common/dlc_channel_service.dart'; import 'package:get_10101/common/dlc_channel_change_notifier.dart'; import 'package:get_10101/common/domain/dlc_channel.dart'; import 'package:get_10101/common/domain/model.dart'; -@GenerateNiceMocks([MockSpec()]) -import 'package:get_10101/features/trade/application/candlestick_service.dart'; @GenerateNiceMocks([MockSpec()]) import 'package:get_10101/features/trade/application/order_service.dart'; @GenerateNiceMocks([MockSpec()]) import 'package:get_10101/features/trade/application/position_service.dart'; @GenerateNiceMocks([MockSpec()]) import 'package:get_10101/features/trade/application/trade_values_service.dart'; -import 'package:get_10101/features/trade/candlestick_change_notifier.dart'; import 'package:get_10101/features/trade/domain/direction.dart'; import 'package:get_10101/features/trade/order_change_notifier.dart'; import 'package:get_10101/features/trade/position_change_notifier.dart'; @@ -82,7 +78,6 @@ void main() { MockTradeValuesService tradeValueService = MockTradeValuesService(); MockChannelInfoService channelConstraintsService = MockChannelInfoService(); MockWalletService walletService = MockWalletService(); - MockCandlestickService candlestickService = MockCandlestickService(); MockDlcChannelService dlcChannelService = MockDlcChannelService(); MockOrderService orderService = MockOrderService(); @@ -128,17 +123,6 @@ void main() { maintenanceMarginRate: 0.1, orderMatchingFeeRate: 0.003)); - when(candlestickService.fetchCandles(1000)).thenAnswer((_) async { - return getDummyCandles(1000); - }); - when(candlestickService.fetchCandles(1)).thenAnswer((_) async { - return getDummyCandles(1); - }); - - CandlestickChangeNotifier candlestickChangeNotifier = - CandlestickChangeNotifier(candlestickService); - candlestickChangeNotifier.initialize(); - SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); WalletChangeNotifier walletChangeNotifier = WalletChangeNotifier(walletService); @@ -168,7 +152,6 @@ void main() { ChangeNotifierProvider(create: (context) => positionChangeNotifier), ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), ChangeNotifierProvider(create: (context) => walletChangeNotifier), - ChangeNotifierProvider(create: (context) => candlestickChangeNotifier), ChangeNotifierProvider(create: (context) => configChangeNotifier), ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), ], child: const TestWrapperWithTradeTheme(child: TradeScreen()))); @@ -255,21 +238,10 @@ void main() { when(dlcChannelService.getEstimatedFundingTxFee()).thenReturn((Amount(300))); - when(candlestickService.fetchCandles(1000)).thenAnswer((_) async { - return getDummyCandles(1000); - }); - when(candlestickService.fetchCandles(1)).thenAnswer((_) async { - return getDummyCandles(1); - }); - when(dlcChannelService.getDlcChannels()).thenAnswer((_) async { return List.filled(1, DlcChannel(id: "foo", state: ChannelState.signed)); }); - CandlestickChangeNotifier candlestickChangeNotifier = - CandlestickChangeNotifier(candlestickService); - candlestickChangeNotifier.initialize(); - SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); WalletChangeNotifier walletChangeNotifier = WalletChangeNotifier(walletService); @@ -300,7 +272,6 @@ void main() { ChangeNotifierProvider(create: (context) => positionChangeNotifier), ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), ChangeNotifierProvider(create: (context) => walletChangeNotifier), - ChangeNotifierProvider(create: (context) => candlestickChangeNotifier), ChangeNotifierProvider(create: (context) => configChangeNotifier), ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), ], child: const TestWrapperWithTradeTheme(child: TradeScreen()))); @@ -332,18 +303,3 @@ void main() { verify(orderService.submitMarketOrder(any, any, any, any, any)).called(1); }); } - -List getDummyCandles(int amount) { - List candles = List.empty(growable: true); - for (int i = 0; i < amount; i++) { - candles.add(Candle( - date: DateTime.now(), - close: 23.000, - high: 24.000, - low: 22.000, - open: 22.000, - volume: 23.000, - )); - } - return candles; -} From 9bcdf431ddf4be045b51b00b3545507d71ae4aba Mon Sep 17 00:00:00 2001 From: Restioson Date: Tue, 23 Apr 2024 18:11:31 +0200 Subject: [PATCH 2/6] fix(justfile): fix run-local-android --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index a79f96436..4ebbed433 100644 --- a/justfile +++ b/justfile @@ -175,7 +175,7 @@ run-local-android args="": fvm flutter run {{args}} \ --dart-define="COMMIT=$(git rev-parse HEAD)" \ --dart-define="BRANCH=$(git rev-parse --abbrev-ref HEAD)" \ - --dart-define="ELECTRS_ENDPOINT=http://${LOCAL_IP}:5050" \ + --dart-define="ELECTRS_ENDPOINT=http://${LOCAL_IP}:3000" \ --dart-define="COORDINATOR_P2P_ENDPOINT=02dd6abec97f9a748bf76ad502b004ce05d1b2d1f43a9e76bd7d85e767ffb022c9@${LOCAL_IP}:9045" \ --dart-define="COORDINATOR_PORT_HTTP=8000" \ --dart-define="ORACLE_ENDPOINT=http://${LOCAL_IP}:8081" \ From 61d151172b4a5d5a0e9e21675bb131d36a26d722 Mon Sep 17 00:00:00 2001 From: Restioson Date: Tue, 23 Apr 2024 18:12:47 +0200 Subject: [PATCH 3/6] fix(justfile): docker-compose -> docker compose This allows it to work for me. Otherwise, I get a command not found error --- justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 4ebbed433..f6f487173 100644 --- a/justfile +++ b/justfile @@ -204,7 +204,7 @@ wipe: wipe-docker wipe-coordinator wipe-maker wipe-app wipe-webapp wipe-docker: #!/usr/bin/env bash set -euxo pipefail - docker-compose down -v + docker compose down -v wipe-coordinator: pkill -9 coordinator && echo "stopped coordinator" || echo "coordinator not running, skipped" @@ -335,7 +335,7 @@ xxi-test args="": docker # Runs background Docker services docker: #!/usr/bin/env bash - docker-compose up -d + docker compose up -d height=$(docker exec bitcoin bitcoin-cli --regtest -rpcuser=admin1 -rpcpassword=123 getblockcount) height="${height%%[[:cntrl:]]}" @@ -347,7 +347,7 @@ docker: fi docker-logs: - docker-compose logs + docker compose logs # Starts coordinator process in the background, piping logs to a file (used in other recipes) run-coordinator-detached: From c59ea2898ddac490823fcc44219965c5576ebba1 Mon Sep 17 00:00:00 2001 From: Restioson Date: Wed, 1 May 2024 10:26:18 +0200 Subject: [PATCH 4/6] refactor: extract app background colour into const --- mobile/lib/common/app_bar_wrapper.dart | 3 ++- mobile/lib/common/scaffold_with_nav_bar.dart | 3 ++- mobile/lib/main.dart | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mobile/lib/common/app_bar_wrapper.dart b/mobile/lib/common/app_bar_wrapper.dart index 036ee5c5c..d52fc7946 100644 --- a/mobile/lib/common/app_bar_wrapper.dart +++ b/mobile/lib/common/app_bar_wrapper.dart @@ -4,6 +4,7 @@ import 'package:get_10101/common/settings/settings_screen.dart'; import 'package:get_10101/features/trade/contract_symbol_icon.dart'; import 'package:get_10101/features/trade/domain/contract_symbol.dart'; import 'package:get_10101/features/trade/trade_screen.dart'; +import 'package:get_10101/main.dart'; import 'package:go_router/go_router.dart'; class AppBarWrapper extends StatelessWidget { @@ -30,7 +31,7 @@ class AppBarWrapper extends StatelessWidget { child: AppBar( centerTitle: true, elevation: 0, - backgroundColor: const Color(0xFFFAFAFA), + backgroundColor: appBackgroundColor, iconTheme: const IconThemeData(color: tenTenOnePurple, size: appBarHeight - 8.0), leading: leadingButton, title: location == TradeScreen.route diff --git a/mobile/lib/common/scaffold_with_nav_bar.dart b/mobile/lib/common/scaffold_with_nav_bar.dart index 1b5913996..567946019 100644 --- a/mobile/lib/common/scaffold_with_nav_bar.dart +++ b/mobile/lib/common/scaffold_with_nav_bar.dart @@ -4,6 +4,7 @@ import 'package:get_10101/common/app_bar_wrapper.dart'; import 'package:get_10101/common/color.dart'; import 'package:get_10101/features/trade/trade_screen.dart'; import 'package:get_10101/features/wallet/wallet_screen.dart'; +import 'package:get_10101/main.dart'; import 'package:get_10101/util/constants.dart'; import 'package:go_router/go_router.dart'; @@ -25,7 +26,7 @@ class ScaffoldWithNavBar extends StatelessWidget { appBar: const PreferredSize( preferredSize: Size.fromHeight(40), child: SafeArea(child: AppBarWrapper())), bottomNavigationBar: BottomNavigationBar( - backgroundColor: const Color(0xFFFAFAFA), + backgroundColor: appBackgroundColor, selectedItemColor: tenTenOnePurple, unselectedItemColor: Colors.black, items: [ diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 4d9c74534..5663c760f 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -11,6 +11,8 @@ import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:get_10101/logger/logger.dart'; +const Color appBackgroundColor = Color(0xFFFAFAFA); + void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); @@ -66,7 +68,7 @@ class _TenTenOneAppState extends State with WidgetsBindingObserver scaffoldMessengerKey: scaffoldMessengerKey, theme: ThemeData( primarySwatch: swatch, - scaffoldBackgroundColor: const Color(0xFFFAFAFA), + scaffoldBackgroundColor: appBackgroundColor, cardTheme: const CardTheme( shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))), surfaceTintColor: Colors.white, From 2f8ca8360591cc8b1fab0fc97d152812783ad5d8 Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Thu, 9 May 2024 10:51:56 +1000 Subject: [PATCH 5/6] fix: make trading view work using 3rd party dependency --- .../trade/tradingview_candlestick.dart | 181 ++++++++++-------- mobile/pubspec.lock | 60 +++++- mobile/pubspec.yaml | 2 + 3 files changed, 157 insertions(+), 86 deletions(-) diff --git a/mobile/lib/features/trade/tradingview_candlestick.dart b/mobile/lib/features/trade/tradingview_candlestick.dart index 79ad592ae..e967bad3e 100644 --- a/mobile/lib/features/trade/tradingview_candlestick.dart +++ b/mobile/lib/features/trade/tradingview_candlestick.dart @@ -1,23 +1,27 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:get_10101/common/global_keys.dart'; -import 'package:get_10101/common/snack_bar.dart'; -import 'package:get_10101/main.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:get_10101/main.dart'; class TradingViewCandlestick extends StatefulWidget { - const TradingViewCandlestick({super.key}); + const TradingViewCandlestick({ + super.key, + }); @override - State createState() => _TradingViewCandlestickState(); + State createState() => _TradingViewCandlestickState(); } class _TradingViewCandlestickState extends State { - late final WebViewController controller; + final GlobalKey webViewKey = GlobalKey(); + InAppWebViewController? webViewController; - static bool enabled() => Platform.isAndroid || Platform.isIOS; + double progress = 0; + + /// place holder if loading fails + late String html = """

Tradingview chart not found

"""; + // this url doesn't matter, it just has to exist + final baseUrl = WebUri("https://10101.finance/"); @override void initState() { @@ -25,89 +29,98 @@ class _TradingViewCandlestickState extends State { const Color bg = appBackgroundColor; String rgba = "rgba(${bg.red}, ${bg.green}, ${bg.blue}, ${255.0 / bg.alpha})"; - String html = ''' - - - - - - + - iframe { - height: 100% !important; - } + - .tradingview-widget-copyright { - display: none; + +
+ +
+
+ + -
-
- - + + + + + '''; + } - if (enabled()) { - controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..enableZoom(false) - ..setNavigationDelegate(NavigationDelegate( - onNavigationRequest: (req) async { - final uri = Uri.parse(req.url); - - if (uri.scheme == "about") { - return NavigationDecision.navigate; - } + @override + Widget build(BuildContext context) { + return Stack( + children: [ + InAppWebView( + key: webViewKey, + onWebViewCreated: (controller) { + webViewController = controller; + webViewController!.loadData(data: html, baseUrl: baseUrl, historyUrl: baseUrl); + }, + shouldOverrideUrlLoading: (controller, navigationAction) async { + var uri = navigationAction.request.url!; - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - showSnackBar(ScaffoldMessenger.of(rootNavigatorKey.currentContext!), - "Failed to open link to TradingView"); + if (uri.toString().startsWith("https://www.tradingview.com/chart")) { + // this is the link to the external chart, we want to open this in an external window + if (await canLaunchUrl(uri)) { + // Launch the App + await launchUrl(uri, mode: LaunchMode.externalApplication); + // and cancel the request + return NavigationActionPolicy.CANCEL; + } } - return NavigationDecision.prevent; + return NavigationActionPolicy.ALLOW; + }, + onProgressChanged: (controller, progress) { + setState(() { + this.progress = progress / 100; + }); }, - )) - ..loadHtmlString(html); - } + ), + progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(), + ], + ); } - - @override - Widget build(BuildContext context) => enabled() - ? WebViewWidget(controller: controller) - : const Text("TradingView chart only supported on IOS and Android"); } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 2ea08b202..7d6fd1535 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -398,6 +398,62 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421 + url: "https://pub.dev" + source: hosted + version: "1.0.13" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f + url: "https://pub.dev" + source: hosted + version: "1.0.13" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07 + url: "https://pub.dev" + source: hosted + version: "1.0.8" flutter_launcher_icons: dependency: "direct dev" description: @@ -1430,5 +1486,5 @@ packages: source: hosted version: "2.1.1" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9fbd4eb9b..8bbe07811 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -49,6 +49,8 @@ dependencies: syncfusion_flutter_sliders: ^24.2.9 syncfusion_flutter_core: ^24.2.9 html: ^0.15.4 + flutter_inappwebview: ^6.0.0-beta.23 + dev_dependencies: analyzer: ^6.4.1 flutter_launcher_icons: ^0.13.1 From 8d6e9d96b5060e936fe56c381aab1b04b093f439 Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Thu, 9 May 2024 16:07:55 +1000 Subject: [PATCH 6/6] fix: only render on iOs and Android this is also needed so that our tests run, which run on native --- .../trade/tradingview_candlestick.dart | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/mobile/lib/features/trade/tradingview_candlestick.dart b/mobile/lib/features/trade/tradingview_candlestick.dart index e967bad3e..5db795f5c 100644 --- a/mobile/lib/features/trade/tradingview_candlestick.dart +++ b/mobile/lib/features/trade/tradingview_candlestick.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -23,6 +25,8 @@ class _TradingViewCandlestickState extends State { // this url doesn't matter, it just has to exist final baseUrl = WebUri("https://10101.finance/"); + static bool enabled() => Platform.isAndroid || Platform.isIOS; + @override void initState() { super.initState(); @@ -90,37 +94,39 @@ class _TradingViewCandlestickState extends State { @override Widget build(BuildContext context) { - return Stack( - children: [ - InAppWebView( - key: webViewKey, - onWebViewCreated: (controller) { - webViewController = controller; - webViewController!.loadData(data: html, baseUrl: baseUrl, historyUrl: baseUrl); - }, - shouldOverrideUrlLoading: (controller, navigationAction) async { - var uri = navigationAction.request.url!; + return enabled() + ? Stack( + children: [ + InAppWebView( + key: webViewKey, + onWebViewCreated: (controller) { + webViewController = controller; + webViewController!.loadData(data: html, baseUrl: baseUrl, historyUrl: baseUrl); + }, + shouldOverrideUrlLoading: (controller, navigationAction) async { + var uri = navigationAction.request.url!; - if (uri.toString().startsWith("https://www.tradingview.com/chart")) { - // this is the link to the external chart, we want to open this in an external window - if (await canLaunchUrl(uri)) { - // Launch the App - await launchUrl(uri, mode: LaunchMode.externalApplication); - // and cancel the request - return NavigationActionPolicy.CANCEL; - } - } + if (uri.toString().startsWith("https://www.tradingview.com/chart")) { + // this is the link to the external chart, we want to open this in an external window + if (await canLaunchUrl(uri)) { + // Launch the App + await launchUrl(uri, mode: LaunchMode.externalApplication); + // and cancel the request + return NavigationActionPolicy.CANCEL; + } + } - return NavigationActionPolicy.ALLOW; - }, - onProgressChanged: (controller, progress) { - setState(() { - this.progress = progress / 100; - }); - }, - ), - progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(), - ], - ); + return NavigationActionPolicy.ALLOW; + }, + onProgressChanged: (controller, progress) { + setState(() { + this.progress = progress / 100; + }); + }, + ), + progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(), + ], + ) + : const Text("Platform not supported"); } }