Skip to content

Commit

Permalink
fix: offline toggle and data source dropdown. (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
Livinglist authored Aug 20, 2024
1 parent b9ff92a commit 62bab9d
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 66 deletions.
78 changes: 47 additions & 31 deletions lib/blocs/stories/stories_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
super(const StoriesState.init()) {
on<LoadStories>(
onLoadStories,
transformer: sequential(),
transformer: concurrent(),
);
on<StoriesInitialize>(onInitialize);
on<StoriesRefresh>(onRefresh);
on<StoriesLoadMore>(onLoadMore);
on<StoryLoaded>(
onStoryLoaded,
transformer: concurrent(),
transformer: sequential(),
);
on<StoryRead>(onStoryRead);
on<StoryUnread>(onStoryUnread);
Expand Down Expand Up @@ -109,11 +109,11 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
emit(
state
.copyWithStoryIdsUpdated(type: type, to: ids)
.copyWithCurrentPageUpdated(type: type, to: 0)
.copyWithCurrentPageUpdated(type: type, to: 1)
.copyWithStatusUpdated(type: type, to: Status.inProgress),
);
_offlineRepository
.getCachedStoriesStream(ids: ids)
.getCachedStoriesStream(ids: ids.sublist(0, apiPageSize))
.listen((Story story) => add(StoryLoaded(story: story, type: type)))
.onDone(() => add(StoryLoadingCompleted(type: type)));
} else if (event.useApi || state.dataSource == HackerNewsDataSource.api) {
Expand Down Expand Up @@ -191,57 +191,73 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
StoriesLoadMore event,
Emitter<StoriesState> emit,
) async {
if (state.statusByType[event.type] == Status.inProgress) return;
final StoryType type = event.type;

if (state.statusByType[type] == Status.inProgress) return;

emit(
state.copyWithStatusUpdated(
type: event.type,
type: type,
to: Status.inProgress,
),
);

final int currentPage = state.currentPageByType[event.type]! + 1;
final int currentPage = state.currentPageByType[type]! + 1;

emit(
state.copyWithCurrentPageUpdated(type: event.type, to: currentPage),
state.copyWithCurrentPageUpdated(type: type, to: currentPage),
);

if (state.isOfflineReading) {
emit(
state.copyWithStatusUpdated(
type: event.type,
to: Status.success,
),
final List<int>? ids = state.storyIdsByType[type];
if (ids == null) {
logError('ids should not be null.');
emit(
state.copyWithStatusUpdated(
type: type,
to: Status.failure,
),
);
return;
}
final int length = ids.length;
final int lower = min(length, apiPageSize * (currentPage - 1));
final int upper = min(length, lower + apiPageSize);
final List<int> idsForCurrentPage = ids.sublist(
lower,
upper,
);
_offlineRepository
.getCachedStoriesStream(ids: idsForCurrentPage)
.listen((Story story) => add(StoryLoaded(story: story, type: type)))
.onDone(() => add(StoryLoadingCompleted(type: type)));
} else if (event.useApi || state.dataSource == HackerNewsDataSource.api) {
late final int length;
final List<int>? ids = state.storyIdsByType[event.type];
List<int>? ids = state.storyIdsByType[type];

if (ids?.isEmpty ?? true) {
final List<int> ids =
await _hackerNewsRepository.fetchStoryIds(type: event.type);
if (ids == null || ids.isEmpty) {
ids = await _hackerNewsRepository.fetchStoryIds(type: type);
length = ids.length;
emit(state.copyWith());
emit(state.copyWithStoryIdsUpdated(type: type, to: ids));
} else {
length = ids!.length;
length = ids.length;
}

final int lower = min(length, apiPageSize * (currentPage - 1));
final int upper = min(length, lower + apiPageSize);
final List<int> idsForCurrentPage = ids.sublist(
lower,
upper,
);
_hackerNewsRepository
.fetchStoriesStream(
ids: state.storyIdsByType[event.type]!.sublist(
lower,
upper,
),
)
.fetchStoriesStream(ids: idsForCurrentPage)
.listen(
(Story story) => add(StoryLoaded(story: story, type: event.type)),
(Story story) => add(StoryLoaded(story: story, type: type)),
)
.onDone(() => add(StoryLoadingCompleted(type: event.type)));
.onDone(() => add(StoryLoadingCompleted(type: type)));
} else {
_hackerNewsWebRepository
.fetchStoriesStream(event.type, page: currentPage)
.fetchStoriesStream(type, page: currentPage)
.handleError((dynamic e) {
logError('error loading more stories $e');

Expand All @@ -254,16 +270,16 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
add(event.copyWith(useApi: true));
emit(
state.copyWithCurrentPageUpdated(
type: event.type,
type: type,
to: currentPage - 1,
),
);
}
})
.listen(
(Story story) => add(StoryLoaded(story: story, type: event.type)),
(Story story) => add(StoryLoaded(story: story, type: type)),
)
.onDone(() => add(StoryLoadingCompleted(type: event.type)));
.onDone(() => add(StoryLoadingCompleted(type: type)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class EnterOfflineModeListTile extends StatelessWidget {
builder: (BuildContext context, StoriesState state) {
return SwitchListTile(
value: state.isOfflineReading,
activeColor: Theme.of(context).colorScheme.primary,
title: const Text('Offline Mode'),
onChanged: (bool value) {
HapticFeedbackUtil.light();
Expand Down
67 changes: 36 additions & 31 deletions lib/screens/profile/widgets/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,37 +211,42 @@ class _SettingsState extends State<Settings> with ItemActionMixin, Loggable {
const Text(
'Data source',
),
DropdownMenu<HackerNewsDataSource>(
/// Make sure no stories are being fetched before
/// switching data source.
enabled: !context
.read<StoriesBloc>()
.state
.statusByType
.values
.any(
(Status status) =>
status == Status.inProgress,
),
initialSelection: preferenceState.dataSource,
dropdownMenuEntries: HackerNewsDataSource.values
.map(
(HackerNewsDataSource val) =>
DropdownMenuEntry<HackerNewsDataSource>(
value: val,
label: val.description,
),
)
.toList(),
onSelected: (HackerNewsDataSource? source) {
if (source != null) {
HapticFeedbackUtil.selection();
context.read<PreferenceCubit>().update(
HackerNewsDataSourcePreference(
val: source.index,
),
);
}
BlocSelector<StoriesBloc, StoriesState, bool>(
selector: (StoriesState state) =>
state.statusByType.values.any(
(Status status) => status == Status.inProgress,
),
builder: (
BuildContext context,
bool isInProgress,
) {
return DropdownMenu<HackerNewsDataSource>(
/// Make sure no stories are being fetched
/// before switching data source.
enabled: !isInProgress,
initialSelection: preferenceState.dataSource,
dropdownMenuEntries:
HackerNewsDataSource.values
.map(
(HackerNewsDataSource val) =>
DropdownMenuEntry<
HackerNewsDataSource>(
value: val,
label: val.description,
),
)
.toList(),
onSelected: (HackerNewsDataSource? source) {
if (source != null) {
HapticFeedbackUtil.selection();
context.read<PreferenceCubit>().update(
HackerNewsDataSourcePreference(
val: source.index,
),
);
}
},
);
},
),
],
Expand Down
6 changes: 2 additions & 4 deletions lib/screens/widgets/stories_list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,6 @@ class _StoriesListViewState extends State<StoriesListView>
}
}

void loadMoreStories() {
HapticFeedbackUtil.light();
context.read<StoriesBloc>().add(StoriesLoadMore(type: widget.storyType));
}
void loadMoreStories() =>
context.read<StoriesBloc>().add(StoriesLoadMore(type: widget.storyType));
}

0 comments on commit 62bab9d

Please sign in to comment.