mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
improve loading speed. (#414)
This commit is contained in:
@ -13,6 +13,7 @@ import 'package:responsive_builder/responsive_builder.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'stories_event.dart';
|
||||
|
||||
part 'stories_state.dart';
|
||||
|
||||
class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
@ -95,7 +96,8 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
storiesToBeDownloaded: state.storiesToBeDownloaded,
|
||||
),
|
||||
);
|
||||
for (final StoryType type in StoryType.values) {
|
||||
|
||||
for (final StoryType type in _preferenceCubit.state.tabs) {
|
||||
add(LoadStories(type: type));
|
||||
}
|
||||
}
|
||||
@ -111,28 +113,44 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
emit(
|
||||
state
|
||||
.copyWithStoryIdsUpdated(type: type, to: ids)
|
||||
.copyWithCurrentPageUpdated(type: type, to: 0),
|
||||
.copyWithCurrentPageUpdated(type: type, to: 0)
|
||||
.copyWithStatusUpdated(type: type, to: Status.inProgress),
|
||||
);
|
||||
_offlineRepository
|
||||
.getCachedStoriesStream(
|
||||
ids: ids.sublist(0, min(ids.length, state.currentPageSize)),
|
||||
)
|
||||
.listen((Story story) {
|
||||
add(StoryLoaded(story: story, type: type));
|
||||
}).onDone(() {
|
||||
add(StoriesLoaded(type: type));
|
||||
});
|
||||
.listen((Story story) => add(StoryLoaded(story: story, type: type)))
|
||||
.onDone(() => add(StoriesLoaded(type: type)));
|
||||
} else {
|
||||
final List<int> ids =
|
||||
await _hackerNewsRepository.fetchStoryIds(type: type);
|
||||
emit(
|
||||
state
|
||||
.copyWithStoryIdsUpdated(type: type, to: ids)
|
||||
.copyWithCurrentPageUpdated(type: type, to: 0),
|
||||
.copyWithCurrentPageUpdated(type: type, to: 0)
|
||||
.copyWithStatusUpdated(type: type, to: Status.inProgress),
|
||||
);
|
||||
bool hasFirstArrived = false;
|
||||
await _hackerNewsRepository
|
||||
.fetchStoriesStream(ids: ids.sublist(0, state.currentPageSize))
|
||||
.fetchStoriesStream(
|
||||
ids: ids.sublist(0, state.currentPageSize),
|
||||
sequential: !event.isRefreshing,
|
||||
)
|
||||
.listen((Story story) {
|
||||
/// When it's refreshing, only flush
|
||||
/// previous stories when new stories has been fetched.
|
||||
if (event.isRefreshing && !hasFirstArrived) {
|
||||
hasFirstArrived = true;
|
||||
final Map<StoryType, List<Story>> newStoriesMap =
|
||||
Map<StoryType, List<Story>>.from(state.storiesByType);
|
||||
newStoriesMap[type] = <Story>[];
|
||||
emit(
|
||||
state.copyWith(
|
||||
storiesByType: newStoriesMap,
|
||||
),
|
||||
);
|
||||
}
|
||||
add(StoryLoaded(story: story, type: type));
|
||||
}).asFuture<void>();
|
||||
add(StoriesLoaded(type: type));
|
||||
@ -161,11 +179,13 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWithRefreshed(type: event.type));
|
||||
add(LoadStories(type: event.type));
|
||||
add(LoadStories(type: event.type, isRefreshing: true));
|
||||
}
|
||||
}
|
||||
|
||||
void onLoadMore(StoriesLoadMore event, Emitter<StoriesState> emit) {
|
||||
if (state.statusByType[event.type] == Status.inProgress) return;
|
||||
|
||||
emit(
|
||||
state.copyWithStatusUpdated(
|
||||
type: event.type,
|
||||
@ -195,16 +215,10 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
upper,
|
||||
),
|
||||
)
|
||||
.listen((Story story) {
|
||||
add(
|
||||
StoryLoaded(
|
||||
story: story,
|
||||
type: event.type,
|
||||
),
|
||||
);
|
||||
}).onDone(() {
|
||||
add(StoriesLoaded(type: event.type));
|
||||
});
|
||||
.listen(
|
||||
(Story story) => add(StoryLoaded(story: story, type: event.type)),
|
||||
)
|
||||
.onDone(() => add(StoriesLoaded(type: event.type)));
|
||||
} else {
|
||||
_hackerNewsRepository
|
||||
.fetchStoriesStream(
|
||||
@ -213,16 +227,10 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||
upper,
|
||||
),
|
||||
)
|
||||
.listen((Story story) {
|
||||
add(
|
||||
StoryLoaded(
|
||||
story: story,
|
||||
type: event.type,
|
||||
),
|
||||
);
|
||||
}).onDone(() {
|
||||
add(StoriesLoaded(type: event.type));
|
||||
});
|
||||
.listen(
|
||||
(Story story) => add(StoryLoaded(story: story, type: event.type)),
|
||||
)
|
||||
.onDone(() => add(StoriesLoaded(type: event.type)));
|
||||
}
|
||||
} else {
|
||||
emit(
|
||||
|
@ -6,12 +6,16 @@ abstract class StoriesEvent extends Equatable {
|
||||
}
|
||||
|
||||
class LoadStories extends StoriesEvent {
|
||||
LoadStories({required this.type});
|
||||
LoadStories({required this.type, this.isRefreshing = false});
|
||||
|
||||
final StoryType type;
|
||||
final bool isRefreshing;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[type];
|
||||
List<Object?> get props => <Object?>[
|
||||
type,
|
||||
isRefreshing,
|
||||
];
|
||||
}
|
||||
|
||||
class StoriesInitialize extends StoriesEvent {
|
||||
|
@ -150,12 +150,6 @@ class StoriesState extends Equatable {
|
||||
}
|
||||
|
||||
StoriesState copyWithRefreshed({required StoryType type}) {
|
||||
final Map<StoryType, List<Story>> newStoriesMap =
|
||||
Map<StoryType, List<Story>>.from(storiesByType);
|
||||
newStoriesMap[type] = <Story>[];
|
||||
final Map<StoryType, List<int>> newStoryIdsMap =
|
||||
Map<StoryType, List<int>>.from(storyIdsByType);
|
||||
newStoryIdsMap[type] = <int>[];
|
||||
final Map<StoryType, Status> newStatusMap =
|
||||
Map<StoryType, Status>.from(statusByType);
|
||||
newStatusMap[type] = Status.inProgress;
|
||||
@ -163,8 +157,6 @@ class StoriesState extends Equatable {
|
||||
Map<StoryType, int>.from(currentPageByType);
|
||||
newCurrentPageMap[type] = 0;
|
||||
return copyWith(
|
||||
storiesByType: newStoriesMap,
|
||||
storyIdsByType: newStoryIdsMap,
|
||||
statusByType: newStatusMap,
|
||||
currentPageByType: newCurrentPageMap,
|
||||
);
|
||||
|
@ -281,13 +281,21 @@ class HackiApp extends StatelessWidget {
|
||||
.instance.platformDispatcher.platformBrightness,
|
||||
mode,
|
||||
);
|
||||
final bool isDarkModeEnabled = mode == null ||
|
||||
mode == AdaptiveThemeMode.dark ||
|
||||
final bool isDarkModeEnabled = () {
|
||||
if (mode == null) {
|
||||
return View.of(context)
|
||||
.platformDispatcher
|
||||
.platformBrightness ==
|
||||
Brightness.dark;
|
||||
} else {
|
||||
return mode == AdaptiveThemeMode.dark ||
|
||||
(mode == AdaptiveThemeMode.system &&
|
||||
View.of(context)
|
||||
.platformDispatcher
|
||||
.platformBrightness ==
|
||||
Brightness.dark);
|
||||
}
|
||||
}();
|
||||
final ColorScheme colorScheme = ColorScheme.fromSeed(
|
||||
brightness:
|
||||
isDarkModeEnabled ? Brightness.dark : Brightness.light,
|
||||
|
@ -329,7 +329,11 @@ class HackerNewsRepository {
|
||||
|
||||
/// Fetch a list of [Story] based on ids and return results
|
||||
/// using a stream.
|
||||
Stream<Story> fetchStoriesStream({required List<int> ids}) async* {
|
||||
Stream<Story> fetchStoriesStream({
|
||||
required List<int> ids,
|
||||
bool sequential = false,
|
||||
}) async* {
|
||||
if (sequential) {
|
||||
for (final int id in ids) {
|
||||
final Story? story =
|
||||
await _fetchItemJson(id).then((Map<String, dynamic>? json) async {
|
||||
@ -342,6 +346,18 @@ class HackerNewsRepository {
|
||||
yield story;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final List<Map<String, dynamic>?> responses = await Future.wait(
|
||||
<Future<Map<String, dynamic>?>>[
|
||||
...ids.map(_fetchItemJson),
|
||||
],
|
||||
);
|
||||
for (final Map<String, dynamic>? json in responses) {
|
||||
if (json == null) continue;
|
||||
final Story story = Story.fromJson(json);
|
||||
yield story;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a list of [PollOption] based on ids and return results
|
||||
|
@ -3,10 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||
import 'package:hacki/blocs/blocs.dart';
|
||||
import 'package:hacki/config/constants.dart';
|
||||
import 'package:hacki/cubits/cubits.dart';
|
||||
import 'package:hacki/extensions/extensions.dart';
|
||||
import 'package:hacki/models/models.dart';
|
||||
import 'package:hacki/screens/widgets/widgets.dart';
|
||||
import 'package:hacki/styles/styles.dart';
|
||||
import 'package:hacki/utils/utils.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class StoryTile extends StatelessWidget {
|
||||
@ -123,6 +125,17 @@ class StoryTile extends StatelessWidget {
|
||||
excludeSemantics: true,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: () {
|
||||
if (story.url.isNotEmpty) {
|
||||
LinkUtil.launch(
|
||||
story.url,
|
||||
context,
|
||||
useReader: context.read<PreferenceCubit>().state.readerEnabled,
|
||||
offlineReading:
|
||||
context.read<StoriesBloc>().state.isOfflineReading,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: Dimens.pt12),
|
||||
child: Column(
|
||||
|
Reference in New Issue
Block a user