M lib/l10n/intl_en.arb => lib/l10n/intl_en.arb +5 -1
@@ 83,5 83,9 @@
"getApiKey": "Get API ID & Key",
"getApiKeyHint": "In \"Preferences\" > \"Developer\"",
"prev": "Previous",
- "next": "Next"
+ "next": "Next",
+ "wentWrong": "Something went wrong.",
+ "retry": "Retry",
+ "copy": "Copy",
+ "errorLog": "Error log"
}=
\ No newline at end of file
M lib/l10n/intl_zh.arb => lib/l10n/intl_zh.arb +5 -1
@@ 83,5 83,9 @@
"getApiKey": "获取 API ID & KEY",
"getApiKeyHint": "在 “偏好设置” > “开发者” 下",
"prev": "前一项",
- "next": "后一项"
+ "next": "后一项",
+ "wentWrong": "发生错误",
+ "retry": "重试",
+ "copy": "复制",
+ "errorLog": "错误日志"
}=
\ No newline at end of file
M lib/main.dart => lib/main.dart +2 -0
@@ 2,6 2,7 @@ import 'dart:io';
import 'package:fluent_reader_lite/models/service.dart';
import 'package:fluent_reader_lite/pages/article_page.dart';
+import 'package:fluent_reader_lite/pages/error_log_page.dart';
import 'package:fluent_reader_lite/pages/settings/about_page.dart';
import 'package:fluent_reader_lite/pages/home_page.dart';
import 'package:fluent_reader_lite/pages/settings/feed_page.dart';
@@ 53,6 54,7 @@ void main() async {
class MyApp extends StatelessWidget {
static final Map<String, Widget Function(BuildContext)> baseRoutes = {
"/article": (context) => ArticlePage(),
+ "/error-log": (context) => ErrorLogPage(),
"/settings": (context) => SettingsPage(),
"/settings/sources": (context) => SourcesPage(),
"/settings/sources/edit": (context) => SourceEditPage(),
M lib/models/sync_model.dart => lib/models/sync_model.dart +1 -0
@@ 56,6 56,7 @@ class SyncModel with ChangeNotifier {
lastSyncSuccess = true;
} catch(exp) {
lastSyncSuccess = false;
+ Store.setErrorLog(exp.toString());
print(exp);
}
lastSynced = DateTime.now();
M lib/pages/article_page.dart => lib/pages/article_page.dart +45 -9
@@ 11,6 11,7 @@ import 'package:fluent_reader_lite/utils/global.dart';
import 'package:fluent_reader_lite/utils/store.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
@@ 29,10 30,14 @@ class ArticlePage extends StatefulWidget {
ArticlePageState createState() => ArticlePageState();
}
+enum _ArticleLoadState {
+ Loading, Success, Failure
+}
+
class ArticlePageState extends State<ArticlePage> {
WebViewController _controller;
int requestId = 0;
- bool loaded = false;
+ _ArticleLoadState loaded = _ArticleLoadState.Loading;
bool navigated = false;
SourceOpenTarget _target;
String iid;
@@ 44,7 49,7 @@ class ArticlePageState extends State<ArticlePage> {
}
setState(() {
iid = id;
- loaded = false;
+ loaded = _ArticleLoadState.Loading;
navigated = false;
_target = null;
if (isSource != null) isSourceFeed = isSource;
@@ 70,12 75,15 @@ class ArticlePageState extends State<ArticlePage> {
var html = (await http.get(item.link)).body;
a = Uri.encodeComponent(html);
} catch(exp) {
- setState(() { loaded = true; });
+ if (mounted && currId == requestId) {
+ setState(() { loaded = _ArticleLoadState.Failure; });
+ }
return;
}
} else {
a = Uri.encodeComponent(item.content);
}
+ if (!mounted || currId != requestId) return;
var h = '<p id="source">${source.name}${(item.creator!=null&&item.creator.length>0)?' / '+item.creator:''}</p>';
h += '<p id="title">${item.title}</p>';
h += '<p id="date">${DateFormat.yMd(Localizations.localeOf(context).toString()).add_Hm().format(item.date)}</p>';
@@ 87,20 95,20 @@ class ArticlePageState extends State<ArticlePage> {
var brightness = Global.currentBrightness(context);
localUrl += "&t=${brightness.index}";
}
- if (currId == requestId) _controller.loadUrl(localUrl);
+ _controller.loadUrl(localUrl);
}
void _onPageReady(_) async {
if (Platform.isAndroid || Global.globalModel.getBrightness() != null) {
await Future.delayed(Duration(milliseconds: 300));
}
- setState(() { loaded = true; });
+ setState(() { loaded = _ArticleLoadState.Success; });
if (_target == SourceOpenTarget.Local || _target == SourceOpenTarget.FullContent) {
navigated = true;
}
}
void _onWebpageReady(_) {
- if (loaded) navigated = true;
+ if (loaded == _ArticleLoadState.Success) navigated = true;
}
void _setOpenTarget(RSSSource source, {SourceOpenTarget target}) {
@@ 112,7 120,7 @@ class ArticlePageState extends State<ArticlePage> {
void _loadOpenTarget(RSSItem item, RSSSource source) {
setState(() {
requestId += 1;
- loaded = false;
+ loaded = _ArticleLoadState.Loading;
navigated = false;
});
switch (_target) {
@@ 166,7 174,7 @@ class ArticlePageState extends State<ArticlePage> {
var source = tuple.item2;
if (_target == null) _target = source.openTarget;
final body = SafeArea(child: IndexedStack(
- index: !loaded ? 0 : 1,
+ index: loaded.index,
children: [
Center(
child: CupertinoActivityIndicator()
@@ 182,6 190,21 @@ class ArticlePageState extends State<ArticlePage> {
onPageFinished: _onWebpageReady,
navigationDelegate: _onNavigate,
),
+ Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ S.of(context).wentWrong,
+ style: TextStyle(color: CupertinoColors.label.resolveFrom(context)),
+ ),
+ CupertinoButton(
+ child: Text(S.of(context).retry),
+ onPressed: () { _loadOpenTarget(item, source); },
+ ),
+ ],
+ ),
+ )
],
), bottom: false,);
return CupertinoPageScaffold(
@@ 208,6 231,7 @@ class ArticlePageState extends State<ArticlePage> {
? S.of(context).markUnread
: S.of(context).markRead,
onPressed: () {
+ HapticFeedback.mediumImpact();
Global.itemsModel.updateItem(item.id, read: !item.hasRead);
},
),
@@ 219,13 243,25 @@ class ArticlePageState extends State<ArticlePage> {
? S.of(context).star
: S.of(context).unstar,
onPressed: () {
+ HapticFeedback.mediumImpact();
Global.itemsModel.updateItem(item.id, starred: !item.starred);
},
),
CupertinoToolbarItem(
icon: CupertinoIcons.share,
semanticLabel: S.of(context).share,
- onPressed: () { Share.share(item.link); },
+ onPressed: () {
+ final media = MediaQuery.of(context);
+ Share.share(
+ item.link,
+ sharePositionOrigin: Rect.fromLTWH(
+ media.size.width - ArticlePage.state.currentContext.size.width / 2,
+ media.size.height - media.padding.bottom - 54,
+ 0,
+ 0
+ )
+ );
+ },
),
CupertinoToolbarItem(
icon: CupertinoIcons.chevron_up,
A lib/pages/error_log_page.dart => lib/pages/error_log_page.dart +35 -0
@@ 0,0 1,35 @@
+import 'package:fluent_reader_lite/components/list_tile_group.dart';
+import 'package:fluent_reader_lite/generated/l10n.dart';
+import 'package:fluent_reader_lite/utils/colors.dart';
+import 'package:fluent_reader_lite/utils/store.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class ErrorLogPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ final errorLog = Store.getErrorLog();
+ return CupertinoPageScaffold(
+ backgroundColor: MyColors.background,
+ navigationBar: CupertinoNavigationBar(
+ middle: Text(S.of(context).errorLog),
+ trailing: CupertinoButton(
+ padding: EdgeInsets.zero,
+ child: Text(S.of(context).copy),
+ onPressed: () {
+ Clipboard.setData(ClipboardData(text: errorLog));
+ },
+ ),
+ ),
+ child: ListView(children: [
+ ListTileGroup([
+ SelectableText(
+ errorLog,
+ style: TextStyle(color: CupertinoColors.label.resolveFrom(context)),
+ ),
+ ]),
+ ]),
+ );
+ }
+}<
\ No newline at end of file
M lib/pages/item_list_page.dart => lib/pages/item_list_page.dart +7 -1
@@ 241,7 241,13 @@ class _ItemListPageState extends State<ItemListPage> {
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
- Share.share(item.link);
+ final media = MediaQuery.of(context);
+ Share.share(
+ item.link,
+ sharePositionOrigin: Rect.fromLTWH(
+ 160, media.size.height - media.padding.bottom, 0, 0
+ ),
+ );
},
),
],
M lib/pages/settings/source_edit_page.dart => lib/pages/settings/source_edit_page.dart +4 -1
@@ 58,7 58,10 @@ class SourceEditPage extends StatelessWidget {
final urlTile = ListTileGroup([
MyListTile(
title: Flexible(child: Text(source.url, style: urlStyle, overflow: TextOverflow.ellipsis)),
- trailing: Icon(CupertinoIcons.doc_on_clipboard),
+ trailing: Icon(
+ CupertinoIcons.doc_on_clipboard,
+ semanticLabel: S.of(context).copy,
+ ),
onTap: () { Clipboard.setData(ClipboardData(text: source.url)); },
trailingChevron: false,
withDivider: false,
M lib/pages/subscription_list_page.dart => lib/pages/subscription_list_page.dart +28 -17
@@ 13,6 13,7 @@ import 'package:fluent_reader_lite/utils/colors.dart';
import 'package:fluent_reader_lite/utils/global.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:provider/provider.dart';
@@ 98,6 99,13 @@ class _SubscriptionListPageState extends State<SubscriptionListPage> {
Navigator.of(context, rootNavigator: true).pushNamed("/settings");
}
+ void _openErrorLog() {
+ if (!Global.syncModel.lastSyncSuccess) {
+ HapticFeedback.mediumImpact();
+ Navigator.of(context, rootNavigator: true).pushNamed("/error-log");
+ }
+ }
+
@override
Widget build(BuildContext context) {
final navigationBar = CupertinoSliverNavigationBar(
@@ 166,23 174,26 @@ class _SubscriptionListPageState extends State<SubscriptionListPage> {
final syncInfo = Consumer<SyncModel>(
builder: (context, syncModel, child) {
return SliverToBoxAdapter(
- child: Container(
- padding: EdgeInsets.all(12),
- child: Column(
- children: [
- Text(
- syncModel.lastSyncSuccess
- ? S.of(context).lastSyncSuccess
- : S.of(context).lastSyncFailure,
- style: syncStyle,
- ),
- Text(
- DateFormat
- .Md(Localizations.localeOf(context).toString())
- .add_Hm().format(syncModel.lastSynced),
- style: syncStyle,
- ),
- ],
+ child: GestureDetector(
+ onLongPress: _openErrorLog,
+ child: Container(
+ padding: EdgeInsets.all(12),
+ child: Column(
+ children: [
+ Text(
+ syncModel.lastSyncSuccess
+ ? S.of(context).lastSyncSuccess
+ : S.of(context).lastSyncFailure,
+ style: syncStyle,
+ ),
+ Text(
+ DateFormat
+ .Md(Localizations.localeOf(context).toString())
+ .add_Hm().format(syncModel.lastSynced),
+ style: syncStyle,
+ ),
+ ],
+ ),
),
),
);
M lib/utils/store.dart => lib/utils/store.dart +9 -0
@@ 6,6 6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
abstract class StoreKeys {
static const GROUPS = "groups";
+ static const ERROR_LOG = "errorLog";
// General
static const THEME = "theme";
@@ 97,4 98,12 @@ class Store {
static void setArticleFontSize(int value) {
sp.setInt(StoreKeys.ARTICLE_FONT_SIZE, value);
}
+
+ static String getErrorLog() {
+ return sp.getString(StoreKeys.ERROR_LOG) ?? "";
+ }
+
+ static void setErrorLog(String value) {
+ sp.setString(StoreKeys.ERROR_LOG, value);
+ }
}=
\ No newline at end of file
M pubspec.yaml => pubspec.yaml +1 -1
@@ 15,7 15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.0.0+3
+version: 1.0.0+4
environment:
sdk: ">=2.7.0 <3.0.0"