part of '../neon_news.dart'; class NewsArticlePage extends StatefulWidget { const NewsArticlePage({ required this.bloc, required this.articlesBloc, required this.useWebView, this.bodyData, this.url, super.key, }) : assert(useWebView || bodyData != null, 'bodyData has to be set when not using a WebView'), assert(!useWebView || url != null, 'url has to be set when using a WebView'); final NewsArticleBloc bloc; final NewsArticlesBloc articlesBloc; final bool useWebView; final String? bodyData; final String? url; @override State createState() => _NewsArticlePageState(); } class _NewsArticlePageState extends State { WebViewController? _webviewController; Timer? _markAsReadTimer; @override void initState() { super.initState(); widget.bloc.errors.listen((final error) { NeonException.showSnackbar(context, error); }); WidgetsBinding.instance.addPostFrameCallback((final _) { unawaited(WakelockPlus.enable()); }); if (widget.useWebView) { _webviewController = WebViewController() // ignore: discarded_futures ..setJavaScriptMode(JavaScriptMode.unrestricted) // ignore: discarded_futures ..setNavigationDelegate( NavigationDelegate( onPageFinished: (final _) async { await _startMarkAsReadTimer(); }, ), ) // ignore: discarded_futures ..loadRequest(Uri.parse(widget.url!)); } else { unawaited(_startMarkAsReadTimer()); } } @override void dispose() { _cancelMarkAsReadTimer(); super.dispose(); } Future _startMarkAsReadTimer() async { if (await widget.bloc.unread.first) { if (widget.articlesBloc.options.articleDisableMarkAsReadTimeoutOption.value) { widget.bloc.markArticleAsRead(); } else { _markAsReadTimer = Timer(const Duration(seconds: 3), () async { if (await widget.bloc.unread.first) { widget.bloc.markArticleAsRead(); } }); } } } void _cancelMarkAsReadTimer() { if (_markAsReadTimer != null) { _markAsReadTimer!.cancel(); _markAsReadTimer = null; } } Future _getURL() async { if (_webviewController != null) { return (await _webviewController!.currentUrl())!; } return widget.url!; } @override Widget build(final BuildContext context) => BackButtonListener( onBackButtonPressed: () async { if (_webviewController != null && await _webviewController!.canGoBack()) { await _webviewController!.goBack(); return true; } unawaited(WakelockPlus.disable()); return false; }, child: Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( actions: [ StreamBuilder( stream: widget.bloc.starred, builder: (final context, final starredSnapshot) { final starred = starredSnapshot.data ?? false; return IconButton( onPressed: () async { if (starred) { widget.bloc.unstarArticle(); } else { widget.bloc.starArticle(); } }, tooltip: starred ? AppLocalizations.of(context).articleUnstar : AppLocalizations.of(context).articleStar, icon: Icon(starred ? Icons.star : Icons.star_outline), ); }, ), StreamBuilder( stream: widget.bloc.unread, builder: (final context, final unreadSnapshot) { final unread = unreadSnapshot.data ?? false; return IconButton( onPressed: () async { if (unread) { widget.bloc.markArticleAsRead(); } else { widget.bloc.markArticleAsUnread(); } }, tooltip: unread ? AppLocalizations.of(context).articleMarkRead : AppLocalizations.of(context).articleMarkUnread, icon: Icon(unread ? MdiIcons.email : MdiIcons.emailMarkAsUnread), ); }, ), if (widget.url != null) ...[ IconButton( onPressed: () async { await launchUrlString( await _getURL(), mode: LaunchMode.externalApplication, ); }, tooltip: AppLocalizations.of(context).articleOpenLink, icon: const Icon(Icons.open_in_new), ), IconButton( onPressed: () async { await Share.share(await _getURL()); }, tooltip: AppLocalizations.of(context).articleShare, icon: const Icon(Icons.share), ), ], ], ), body: widget.useWebView ? WebViewWidget( controller: _webviewController!, ) : SingleChildScrollView( padding: const EdgeInsets.all(10), child: Html( data: widget.bodyData, onLinkTap: ( final url, final attributes, final element, ) async { if (url != null) { await launchUrlString( url, mode: LaunchMode.externalApplication, ); } }, ), ), ), ); }