improve loading speed. (#414)

This commit is contained in:
Jojo Feng
2024-06-01 19:40:37 -07:00
committed by GitHub
parent 9763a94e1d
commit e46432b86c
6 changed files with 109 additions and 68 deletions

View File

@ -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(

View File

@ -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 {

View File

@ -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,
);

View File

@ -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,

View File

@ -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

View File

@ -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(