mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
9cefffa518 | |||
fe630ea7a9 |
@ -10,8 +10,6 @@ A simple noiseless [Hacker News](https://news.ycombinator.com/) client made with
|
||||
[](https://img.shields.io/github/stars/livinglist/Hacki?style=social)
|
||||
[](https://pub.dev/packages/effective_dart)
|
||||
|
||||
<noscript><a href="https://liberapay.com/jfeng_for_open_source/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
|
||||
|
||||
[<img src="assets/images/app_store_badge.png" height="50">](https://apps.apple.com/us/app/hacki/id1602043763?platform=iphone) [<img src="assets/images/google_play_badge.png" height="50">](https://play.google.com/store/apps/details?id=com.jiaqifeng.hacki&hl=en_US&gl=US) [<img src="assets/images/f_droid_badge.png" height="50">](https://f-droid.org/en/packages/com.jiaqifeng.hacki/)
|
||||
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 698 KiB After Width: | Height: | Size: 935 KiB |
1
fastlane/metadata/android/en-US/changelogs/61.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/61.txt
Normal file
@ -0,0 +1 @@
|
||||
- Offline mode now includes web pages.
|
2
fastlane/metadata/android/en-US/changelogs/62.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/62.txt
Normal file
@ -0,0 +1,2 @@
|
||||
- Offline mode now includes web pages.
|
||||
- You can now sort comments in story screen.
|
Binary file not shown.
Before Width: | Height: | Size: 698 KiB After Width: | Height: | Size: 935 KiB |
@ -568,7 +568,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -577,7 +577,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.19;
|
||||
MARKETING_VERSION = 0.2.20;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -705,7 +705,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -714,7 +714,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.19;
|
||||
MARKETING_VERSION = 0.2.20;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -736,7 +736,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = QMWX3X2NF7;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -745,7 +745,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.2.19;
|
||||
MARKETING_VERSION = 0.2.20;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:hacki/config/locator.dart';
|
||||
@ -71,28 +72,34 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
final Story updatedStory = state.offlineReading
|
||||
? story
|
||||
: await _storiesRepository.fetchStoryBy(story.id) ?? story;
|
||||
final List<int> kids = () {
|
||||
switch (state.order) {
|
||||
case CommentsOrder.natural:
|
||||
return updatedStory.kids;
|
||||
case CommentsOrder.newestFirst:
|
||||
return updatedStory.kids.sorted((int a, int b) => b.compareTo(a));
|
||||
case CommentsOrder.oldestFirst:
|
||||
return updatedStory.kids.sorted((int a, int b) => a.compareTo(b));
|
||||
}
|
||||
}();
|
||||
|
||||
emit(state.copyWith(story: updatedStory));
|
||||
|
||||
if (state.offlineReading) {
|
||||
_streamSubscription = _cacheRepository
|
||||
.getCachedCommentsStream(ids: updatedStory.kids)
|
||||
.getCachedCommentsStream(ids: kids)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
} else {
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchCommentsStream(ids: updatedStory.kids)
|
||||
.fetchCommentsStream(ids: kids)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
final bool offlineReading = await _cacheRepository.hasCachedStories;
|
||||
|
||||
_cacheService.resetCollapsedComments();
|
||||
|
||||
if (offlineReading) {
|
||||
if (state.offlineReading) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CommentsStatus.loaded,
|
||||
@ -101,6 +108,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
return;
|
||||
}
|
||||
|
||||
_cacheService
|
||||
..resetComments()
|
||||
..resetCollapsedComments();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CommentsStatus.loading,
|
||||
@ -113,8 +124,19 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
final Story story = state.story;
|
||||
final Story updatedStory =
|
||||
await _storiesRepository.fetchStoryBy(story.id) ?? story;
|
||||
final List<int> kids = () {
|
||||
switch (state.order) {
|
||||
case CommentsOrder.natural:
|
||||
return updatedStory.kids;
|
||||
case CommentsOrder.newestFirst:
|
||||
return updatedStory.kids.sorted((int a, int b) => b.compareTo(a));
|
||||
case CommentsOrder.oldestFirst:
|
||||
return updatedStory.kids.sorted((int a, int b) => a.compareTo(b));
|
||||
}
|
||||
}();
|
||||
|
||||
_streamSubscription = _storiesRepository
|
||||
.fetchCommentsStream(ids: updatedStory.kids)
|
||||
.fetchCommentsStream(ids: kids)
|
||||
.listen(_onCommentFetched)
|
||||
..onDone(_onDone);
|
||||
|
||||
@ -161,7 +183,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
..cacheComment(comment);
|
||||
_sembastRepository.cacheComment(comment);
|
||||
|
||||
final List<LinkifyElement> elements = linkify(
|
||||
final List<LinkifyElement> elements = _linkify(
|
||||
comment.text,
|
||||
);
|
||||
|
||||
@ -194,7 +216,14 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
}
|
||||
}
|
||||
|
||||
List<LinkifyElement> linkify(
|
||||
void onOrderChanged(CommentsOrder? order) {
|
||||
if (order == null) return;
|
||||
_streamSubscription?.cancel();
|
||||
emit(state.copyWith(order: order, comments: <Comment>[]));
|
||||
init();
|
||||
}
|
||||
|
||||
static List<LinkifyElement> _linkify(
|
||||
String text, {
|
||||
LinkifyOptions options = const LinkifyOptions(),
|
||||
List<Linkifier> linkifiers = const <Linkifier>[
|
||||
|
@ -8,12 +8,18 @@ enum CommentsStatus {
|
||||
failure,
|
||||
}
|
||||
|
||||
enum CommentsOrder {
|
||||
natural,
|
||||
newestFirst,
|
||||
oldestFirst,
|
||||
}
|
||||
|
||||
class CommentsState extends Equatable {
|
||||
const CommentsState({
|
||||
required this.story,
|
||||
required this.comments,
|
||||
required this.status,
|
||||
required this.collapsed,
|
||||
required this.order,
|
||||
required this.onlyShowTargetComment,
|
||||
required this.offlineReading,
|
||||
required this.currentPage,
|
||||
@ -24,14 +30,14 @@ class CommentsState extends Equatable {
|
||||
required this.story,
|
||||
}) : comments = <Comment>[],
|
||||
status = CommentsStatus.init,
|
||||
collapsed = false,
|
||||
order = CommentsOrder.natural,
|
||||
onlyShowTargetComment = false,
|
||||
currentPage = 0;
|
||||
|
||||
final Story story;
|
||||
final List<Comment> comments;
|
||||
final CommentsStatus status;
|
||||
final bool collapsed;
|
||||
final CommentsOrder order;
|
||||
final bool onlyShowTargetComment;
|
||||
final bool offlineReading;
|
||||
final int currentPage;
|
||||
@ -40,7 +46,7 @@ class CommentsState extends Equatable {
|
||||
Story? story,
|
||||
List<Comment>? comments,
|
||||
CommentsStatus? status,
|
||||
bool? collapsed,
|
||||
CommentsOrder? order,
|
||||
bool? onlyShowTargetComment,
|
||||
bool? offlineReading,
|
||||
int? currentPage,
|
||||
@ -49,7 +55,7 @@ class CommentsState extends Equatable {
|
||||
story: story ?? this.story,
|
||||
comments: comments ?? this.comments,
|
||||
status: status ?? this.status,
|
||||
collapsed: collapsed ?? this.collapsed,
|
||||
order: order ?? this.order,
|
||||
onlyShowTargetComment:
|
||||
onlyShowTargetComment ?? this.onlyShowTargetComment,
|
||||
offlineReading: offlineReading ?? this.offlineReading,
|
||||
@ -62,7 +68,7 @@ class CommentsState extends Equatable {
|
||||
story,
|
||||
comments,
|
||||
status,
|
||||
collapsed,
|
||||
order,
|
||||
onlyShowTargetComment,
|
||||
offlineReading,
|
||||
currentPage,
|
||||
|
@ -179,10 +179,6 @@ class HackiApp extends StatelessWidget {
|
||||
lazy: false,
|
||||
create: (BuildContext context) => PostCubit(),
|
||||
),
|
||||
BlocProvider<EditCubit>(
|
||||
lazy: false,
|
||||
create: (BuildContext context) => EditCubit(),
|
||||
),
|
||||
],
|
||||
child: AdaptiveTheme(
|
||||
light: ThemeData(
|
||||
|
@ -11,6 +11,7 @@ class BuildableComment extends Comment {
|
||||
required super.by,
|
||||
required super.text,
|
||||
required super.kids,
|
||||
required super.dead,
|
||||
required super.deleted,
|
||||
required super.level,
|
||||
required this.elements,
|
||||
@ -25,6 +26,7 @@ class BuildableComment extends Comment {
|
||||
by: comment.by,
|
||||
text: comment.text,
|
||||
kids: comment.kids,
|
||||
dead: comment.dead,
|
||||
deleted: comment.deleted,
|
||||
level: comment.level,
|
||||
);
|
||||
|
@ -12,11 +12,11 @@ class Comment extends Item {
|
||||
required super.by,
|
||||
required super.text,
|
||||
required super.kids,
|
||||
required super.dead,
|
||||
required super.deleted,
|
||||
required this.level,
|
||||
}) : super(
|
||||
descendants: 0,
|
||||
dead: false,
|
||||
parts: <int>[],
|
||||
title: '',
|
||||
url: '',
|
||||
@ -55,6 +55,7 @@ class Comment extends Item {
|
||||
by: by,
|
||||
text: text,
|
||||
kids: kids,
|
||||
dead: dead,
|
||||
deleted: deleted,
|
||||
level: level ?? this.level,
|
||||
);
|
||||
|
@ -408,7 +408,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'Hacki',
|
||||
applicationVersion: 'v0.2.19',
|
||||
applicationVersion: 'v0.2.20',
|
||||
applicationIcon: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
@ -676,7 +676,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
||||
.get<SembastRepository>()
|
||||
.deleteAllCachedComments()
|
||||
.whenComplete(
|
||||
locator.get<SembastRepository>().deleteAllCachedComments,
|
||||
locator.get<CacheRepository>().deleteAll,
|
||||
)
|
||||
.whenComplete(
|
||||
locator.get<PreferenceRepository>().clearAllReadStories,
|
||||
|
@ -76,6 +76,10 @@ class StoryScreen extends StatefulWidget {
|
||||
targetParents: args.targetComments,
|
||||
),
|
||||
),
|
||||
BlocProvider<EditCubit>(
|
||||
lazy: false,
|
||||
create: (BuildContext context) => EditCubit(),
|
||||
),
|
||||
if (args.story.isPoll)
|
||||
BlocProvider<PollCubit>(
|
||||
create: (BuildContext context) =>
|
||||
@ -112,6 +116,10 @@ class StoryScreen extends StatefulWidget {
|
||||
targetParents: args.targetComments,
|
||||
),
|
||||
),
|
||||
BlocProvider<EditCubit>(
|
||||
lazy: false,
|
||||
create: (BuildContext context) => EditCubit(),
|
||||
),
|
||||
if (args.story.isPoll)
|
||||
BlocProvider<PollCubit>(
|
||||
create: (BuildContext context) =>
|
||||
@ -187,6 +195,7 @@ class _StoryScreenState extends State<StoryScreen> {
|
||||
scrollController.dispose();
|
||||
storyLinkTapThrottle.dispose();
|
||||
featureDiscoveryDismissThrottle.dispose();
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -271,16 +280,18 @@ class _StoryScreenState extends State<StoryScreen> {
|
||||
controller: refreshController,
|
||||
onRefresh: () {
|
||||
HapticFeedback.lightImpact();
|
||||
locator.get<CacheService>().resetComments();
|
||||
context.read<CommentsCubit>().refresh();
|
||||
|
||||
if (widget.story.isPoll) {
|
||||
context.read<PollCubit>().refresh();
|
||||
if (context.read<StoriesBloc>().state.offlineReading) {
|
||||
refreshController.refreshCompleted();
|
||||
} else {
|
||||
context.read<CommentsCubit>().refresh();
|
||||
|
||||
if (widget.story.isPoll) {
|
||||
context.read<PollCubit>().refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoading: () {
|
||||
context.read<CommentsCubit>().loadMore();
|
||||
},
|
||||
onLoading: context.read<CommentsCubit>().loadMore,
|
||||
child: ListView(
|
||||
primary: false,
|
||||
children: <Widget>[
|
||||
@ -432,6 +443,59 @@ class _StoryScreenState extends State<StoryScreen> {
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
] else ...<Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
'''${state.story.score} karma, ${state.story.descendants} comment${state.story.descendants > 1 ? 's' : ''}''',
|
||||
),
|
||||
const Spacer(),
|
||||
DropdownButton<CommentsOrder>(
|
||||
value: state.order,
|
||||
underline: const SizedBox.shrink(),
|
||||
items: const <DropdownMenuItem<CommentsOrder>>[
|
||||
DropdownMenuItem<CommentsOrder>(
|
||||
value: CommentsOrder.natural,
|
||||
child: Text(
|
||||
'Natural',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem<CommentsOrder>(
|
||||
value: CommentsOrder.newestFirst,
|
||||
child: Text(
|
||||
'Newest first',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem<CommentsOrder>(
|
||||
value: CommentsOrder.oldestFirst,
|
||||
child: Text(
|
||||
'Oldest first',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged:
|
||||
context.read<CommentsCubit>().onOrderChanged,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
],
|
||||
if (state.comments.isEmpty &&
|
||||
state.status == CommentsStatus.allLoaded) ...<Widget>[
|
||||
@ -530,8 +594,10 @@ class _StoryScreenState extends State<StoryScreen> {
|
||||
SplitViewState current,
|
||||
) =>
|
||||
previous.expanded != current.expanded,
|
||||
builder:
|
||||
(BuildContext context, SplitViewState state) {
|
||||
builder: (
|
||||
BuildContext context,
|
||||
SplitViewState state,
|
||||
) {
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
|
@ -109,7 +109,7 @@ class LinkPreview extends StatefulWidget {
|
||||
|
||||
class _LinkPreviewState extends State<LinkPreview> {
|
||||
InfoBase? _info;
|
||||
String? _errorTitle, _errorBody, _url;
|
||||
String? _errorTitle, _errorBody;
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
@ -119,37 +119,19 @@ class _LinkPreviewState extends State<LinkPreview> {
|
||||
'Oops! Unable to parse the url. We have '
|
||||
'sent feedback to our developers & '
|
||||
'we will try to fix this in our next release. Thanks!';
|
||||
_url = widget.link.trim();
|
||||
|
||||
if (_url?.isNotEmpty ?? false) {
|
||||
_info = WebAnalyzer.getInfoFromCache(_url);
|
||||
} else {
|
||||
_info = WebAnalyzer.getInfoFromCache(widget.story.id.toString());
|
||||
}
|
||||
_loading = true;
|
||||
_getInfo();
|
||||
|
||||
if (_info == null) {
|
||||
_loading = true;
|
||||
_getInfo();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _getInfo() async {
|
||||
if (_url!.startsWith('http') || _url!.startsWith('https')) {
|
||||
_info = await WebAnalyzer.getInfo(
|
||||
_url,
|
||||
story: widget.story,
|
||||
cache: widget.cache,
|
||||
offlineReading: widget.offlineReading,
|
||||
);
|
||||
} else {
|
||||
_info = await WebAnalyzer.getInfo(
|
||||
null,
|
||||
story: widget.story,
|
||||
cache: widget.cache,
|
||||
offlineReading: widget.offlineReading,
|
||||
);
|
||||
}
|
||||
_info = await WebAnalyzer.getInfo(
|
||||
story: widget.story,
|
||||
cache: widget.cache,
|
||||
offlineReading: widget.offlineReading,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@ -193,7 +175,7 @@ class _LinkPreviewState extends State<LinkPreview> {
|
||||
metadata: widget.story.simpleMetadata,
|
||||
url: widget.link,
|
||||
title: widget.story.title,
|
||||
description: desc ?? title ?? 'no comments yet.',
|
||||
description: desc ?? title ?? 'no comment yet.',
|
||||
imageUri: imageUri,
|
||||
imagePath: Constants.hackerNewsLogoPath,
|
||||
onTap: _launchURL,
|
||||
|
@ -95,12 +95,12 @@ class WebAnalyzer {
|
||||
|
||||
/// Get web information
|
||||
/// return [InfoBase]
|
||||
static InfoBase? getInfoFromCache(String? url) {
|
||||
final InfoBase? info = cacheMap[url];
|
||||
static InfoBase? getInfoFromCache(String? cacheKey) {
|
||||
final InfoBase? info = cacheMap[cacheKey];
|
||||
|
||||
if (info != null) {
|
||||
if (!info._timeout.isAfter(DateTime.now())) {
|
||||
cacheMap.remove(url);
|
||||
cacheMap.remove(cacheKey);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
@ -108,14 +108,16 @@ class WebAnalyzer {
|
||||
|
||||
/// Get web information
|
||||
/// return [InfoBase]
|
||||
static Future<InfoBase?> getInfo(
|
||||
String? url, {
|
||||
static Future<InfoBase?> getInfo({
|
||||
required Story story,
|
||||
Duration cache = const Duration(hours: 24),
|
||||
bool multimedia = true,
|
||||
required bool offlineReading,
|
||||
}) async {
|
||||
InfoBase? info = getInfoFromCache(url);
|
||||
final String key = getKey(story);
|
||||
final String url = story.url;
|
||||
|
||||
InfoBase? info = getInfoFromCache(key);
|
||||
|
||||
if (info != null) return info;
|
||||
|
||||
@ -126,7 +128,8 @@ class WebAnalyzer {
|
||||
)
|
||||
.._timeout = DateTime.now().add(cache)
|
||||
.._shouldRetry = false;
|
||||
cacheMap[story.id.toString()] = info;
|
||||
|
||||
cacheMap[key] = info;
|
||||
|
||||
return info;
|
||||
}
|
||||
@ -148,7 +151,9 @@ class WebAnalyzer {
|
||||
)
|
||||
.._shouldRetry = false
|
||||
.._timeout = DateTime.now();
|
||||
cacheMap[url] = info;
|
||||
|
||||
cacheMap[key] = info;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -161,7 +166,7 @@ class WebAnalyzer {
|
||||
|
||||
if (info != null && !info._shouldRetry) {
|
||||
info._timeout = DateTime.now().add(cache);
|
||||
cacheMap[url] = info;
|
||||
cacheMap[key] = info;
|
||||
}
|
||||
|
||||
return info;
|
||||
@ -214,12 +219,12 @@ class WebAnalyzer {
|
||||
|
||||
if (res == null || isEmpty(res[2] as String?)) {
|
||||
final String? commentText = await compute(
|
||||
_fetchInfoFromStoryId,
|
||||
story.kids,
|
||||
_fetchInfoFromStory,
|
||||
<int>[story.id, ...story.kids],
|
||||
);
|
||||
|
||||
shouldRetry = commentText == null;
|
||||
fallbackDescription = commentText ?? 'no comments yet';
|
||||
fallbackDescription = commentText ?? 'no comment yet';
|
||||
} else {
|
||||
shouldRetry = false;
|
||||
}
|
||||
@ -281,11 +286,25 @@ class WebAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> _fetchInfoFromStoryId(List<int> kids) async {
|
||||
if (kids.isEmpty) return null;
|
||||
static Future<String?> _fetchInfoFromStory(List<int> meta) async {
|
||||
final StoriesRepository storiesRepository = StoriesRepository();
|
||||
final int storyId = meta.first;
|
||||
List<int> kids = meta.sublist(1, meta.length);
|
||||
|
||||
// Kids of stories from search results are always empty, so here we try
|
||||
// to fetch the story itself first and see if the kids are still empty.
|
||||
if (kids.isEmpty) {
|
||||
final Story? story = await storiesRepository.fetchStoryBy(storyId);
|
||||
|
||||
if (story == null) return null;
|
||||
|
||||
kids = story.kids;
|
||||
|
||||
if (kids.isEmpty) return null;
|
||||
}
|
||||
|
||||
final Comment? comment =
|
||||
await StoriesRepository().fetchCommentBy(id: kids.first);
|
||||
await storiesRepository.fetchCommentBy(id: kids.first);
|
||||
|
||||
return comment != null ? '${comment.by}: ${comment.text}' : null;
|
||||
}
|
||||
@ -540,4 +559,7 @@ class WebAnalyzer {
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
static String getKey(Story story) =>
|
||||
story.url.isNotEmpty ? story.url : story.id.toString();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: hacki
|
||||
description: A Hacker News reader.
|
||||
version: 0.2.19+60
|
||||
version: 0.2.20+62
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
Reference in New Issue
Block a user