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';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
part 'stories_event.dart';
|
part 'stories_event.dart';
|
||||||
|
|
||||||
part 'stories_state.dart';
|
part 'stories_state.dart';
|
||||||
|
|
||||||
class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||||
@ -95,7 +96,8 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
storiesToBeDownloaded: state.storiesToBeDownloaded,
|
storiesToBeDownloaded: state.storiesToBeDownloaded,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (final StoryType type in StoryType.values) {
|
|
||||||
|
for (final StoryType type in _preferenceCubit.state.tabs) {
|
||||||
add(LoadStories(type: type));
|
add(LoadStories(type: type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,28 +113,44 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
emit(
|
emit(
|
||||||
state
|
state
|
||||||
.copyWithStoryIdsUpdated(type: type, to: ids)
|
.copyWithStoryIdsUpdated(type: type, to: ids)
|
||||||
.copyWithCurrentPageUpdated(type: type, to: 0),
|
.copyWithCurrentPageUpdated(type: type, to: 0)
|
||||||
|
.copyWithStatusUpdated(type: type, to: Status.inProgress),
|
||||||
);
|
);
|
||||||
_offlineRepository
|
_offlineRepository
|
||||||
.getCachedStoriesStream(
|
.getCachedStoriesStream(
|
||||||
ids: ids.sublist(0, min(ids.length, state.currentPageSize)),
|
ids: ids.sublist(0, min(ids.length, state.currentPageSize)),
|
||||||
)
|
)
|
||||||
.listen((Story story) {
|
.listen((Story story) => add(StoryLoaded(story: story, type: type)))
|
||||||
add(StoryLoaded(story: story, type: type));
|
.onDone(() => add(StoriesLoaded(type: type)));
|
||||||
}).onDone(() {
|
|
||||||
add(StoriesLoaded(type: type));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
final List<int> ids =
|
final List<int> ids =
|
||||||
await _hackerNewsRepository.fetchStoryIds(type: type);
|
await _hackerNewsRepository.fetchStoryIds(type: type);
|
||||||
emit(
|
emit(
|
||||||
state
|
state
|
||||||
.copyWithStoryIdsUpdated(type: type, to: ids)
|
.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
|
await _hackerNewsRepository
|
||||||
.fetchStoriesStream(ids: ids.sublist(0, state.currentPageSize))
|
.fetchStoriesStream(
|
||||||
|
ids: ids.sublist(0, state.currentPageSize),
|
||||||
|
sequential: !event.isRefreshing,
|
||||||
|
)
|
||||||
.listen((Story story) {
|
.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));
|
add(StoryLoaded(story: story, type: type));
|
||||||
}).asFuture<void>();
|
}).asFuture<void>();
|
||||||
add(StoriesLoaded(type: type));
|
add(StoriesLoaded(type: type));
|
||||||
@ -161,11 +179,13 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWithRefreshed(type: event.type));
|
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) {
|
void onLoadMore(StoriesLoadMore event, Emitter<StoriesState> emit) {
|
||||||
|
if (state.statusByType[event.type] == Status.inProgress) return;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWithStatusUpdated(
|
state.copyWithStatusUpdated(
|
||||||
type: event.type,
|
type: event.type,
|
||||||
@ -195,16 +215,10 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
upper,
|
upper,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.listen((Story story) {
|
.listen(
|
||||||
add(
|
(Story story) => add(StoryLoaded(story: story, type: event.type)),
|
||||||
StoryLoaded(
|
)
|
||||||
story: story,
|
.onDone(() => add(StoriesLoaded(type: event.type)));
|
||||||
type: event.type,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).onDone(() {
|
|
||||||
add(StoriesLoaded(type: event.type));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
_hackerNewsRepository
|
_hackerNewsRepository
|
||||||
.fetchStoriesStream(
|
.fetchStoriesStream(
|
||||||
@ -213,16 +227,10 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
upper,
|
upper,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.listen((Story story) {
|
.listen(
|
||||||
add(
|
(Story story) => add(StoryLoaded(story: story, type: event.type)),
|
||||||
StoryLoaded(
|
)
|
||||||
story: story,
|
.onDone(() => add(StoriesLoaded(type: event.type)));
|
||||||
type: event.type,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).onDone(() {
|
|
||||||
add(StoriesLoaded(type: event.type));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emit(
|
emit(
|
||||||
|
@ -6,12 +6,16 @@ abstract class StoriesEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadStories extends StoriesEvent {
|
class LoadStories extends StoriesEvent {
|
||||||
LoadStories({required this.type});
|
LoadStories({required this.type, this.isRefreshing = false});
|
||||||
|
|
||||||
final StoryType type;
|
final StoryType type;
|
||||||
|
final bool isRefreshing;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[type];
|
List<Object?> get props => <Object?>[
|
||||||
|
type,
|
||||||
|
isRefreshing,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class StoriesInitialize extends StoriesEvent {
|
class StoriesInitialize extends StoriesEvent {
|
||||||
|
@ -150,12 +150,6 @@ class StoriesState extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StoriesState copyWithRefreshed({required StoryType type}) {
|
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 =
|
final Map<StoryType, Status> newStatusMap =
|
||||||
Map<StoryType, Status>.from(statusByType);
|
Map<StoryType, Status>.from(statusByType);
|
||||||
newStatusMap[type] = Status.inProgress;
|
newStatusMap[type] = Status.inProgress;
|
||||||
@ -163,8 +157,6 @@ class StoriesState extends Equatable {
|
|||||||
Map<StoryType, int>.from(currentPageByType);
|
Map<StoryType, int>.from(currentPageByType);
|
||||||
newCurrentPageMap[type] = 0;
|
newCurrentPageMap[type] = 0;
|
||||||
return copyWith(
|
return copyWith(
|
||||||
storiesByType: newStoriesMap,
|
|
||||||
storyIdsByType: newStoryIdsMap,
|
|
||||||
statusByType: newStatusMap,
|
statusByType: newStatusMap,
|
||||||
currentPageByType: newCurrentPageMap,
|
currentPageByType: newCurrentPageMap,
|
||||||
);
|
);
|
||||||
|
@ -281,13 +281,21 @@ class HackiApp extends StatelessWidget {
|
|||||||
.instance.platformDispatcher.platformBrightness,
|
.instance.platformDispatcher.platformBrightness,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
final bool isDarkModeEnabled = mode == null ||
|
final bool isDarkModeEnabled = () {
|
||||||
mode == AdaptiveThemeMode.dark ||
|
if (mode == null) {
|
||||||
|
return View.of(context)
|
||||||
|
.platformDispatcher
|
||||||
|
.platformBrightness ==
|
||||||
|
Brightness.dark;
|
||||||
|
} else {
|
||||||
|
return mode == AdaptiveThemeMode.dark ||
|
||||||
(mode == AdaptiveThemeMode.system &&
|
(mode == AdaptiveThemeMode.system &&
|
||||||
View.of(context)
|
View.of(context)
|
||||||
.platformDispatcher
|
.platformDispatcher
|
||||||
.platformBrightness ==
|
.platformBrightness ==
|
||||||
Brightness.dark);
|
Brightness.dark);
|
||||||
|
}
|
||||||
|
}();
|
||||||
final ColorScheme colorScheme = ColorScheme.fromSeed(
|
final ColorScheme colorScheme = ColorScheme.fromSeed(
|
||||||
brightness:
|
brightness:
|
||||||
isDarkModeEnabled ? Brightness.dark : Brightness.light,
|
isDarkModeEnabled ? Brightness.dark : Brightness.light,
|
||||||
|
@ -329,7 +329,11 @@ class HackerNewsRepository {
|
|||||||
|
|
||||||
/// Fetch a list of [Story] based on ids and return results
|
/// Fetch a list of [Story] based on ids and return results
|
||||||
/// using a stream.
|
/// 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) {
|
for (final int id in ids) {
|
||||||
final Story? story =
|
final Story? story =
|
||||||
await _fetchItemJson(id).then((Map<String, dynamic>? json) async {
|
await _fetchItemJson(id).then((Map<String, dynamic>? json) async {
|
||||||
@ -342,6 +346,18 @@ class HackerNewsRepository {
|
|||||||
yield story;
|
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
|
/// 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:flutter_fadein/flutter_fadein.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/extensions/extensions.dart';
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
import 'package:hacki/utils/utils.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class StoryTile extends StatelessWidget {
|
class StoryTile extends StatelessWidget {
|
||||||
@ -123,6 +125,17 @@ class StoryTile extends StatelessWidget {
|
|||||||
excludeSemantics: true,
|
excludeSemantics: true,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
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(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: Dimens.pt12),
|
padding: const EdgeInsets.only(left: Dimens.pt12),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
Reference in New Issue
Block a user