|
|
|
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<NewsArticlePage> createState() => _NewsArticlePageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _NewsArticlePageState extends State<NewsArticlePage> {
|
|
|
|
WebViewController? _webviewController;
|
|
|
|
Timer? _markAsReadTimer;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
widget.bloc.errors.listen((final error) {
|
|
|
|
NeonException.showSnackbar(context, error);
|
|
|
|
});
|
|
|
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((final _) async {
|
|
|
|
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
|
|
|
|
await 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<String> _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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mounted && Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
|
|
|
|
await WakelockPlus.disable();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
child: Scaffold(
|
|
|
|
resizeToAvoidBottomInset: false,
|
|
|
|
appBar: AppBar(
|
|
|
|
actions: [
|
|
|
|
StreamBuilder<bool>(
|
|
|
|
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<bool>(
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|