Skip to content

Commit

Permalink
Merge pull request #2350 from acterglobal/kumar/providers-optmizations
Browse files Browse the repository at this point in the history
Optimised Pins providers usage
  • Loading branch information
kumarpalsinh25 authored Nov 7, 2024
2 parents ded72ce + d73c4d0 commit 19e9eb0
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 65 deletions.
21 changes: 10 additions & 11 deletions app/lib/features/pins/pages/pins_list_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'package:acter/common/extensions/options.dart';
import 'package:acter/common/providers/common_providers.dart';
import 'package:acter/common/utils/routes.dart';
import 'package:acter/common/widgets/acter_search_widget.dart';
import 'package:acter/common/widgets/add_button_with_can_permission.dart';
import 'package:acter/common/widgets/space_name_widget.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_empty_state.dart';
import 'package:acter/features/pins/widgets/pin_list_widget.dart';
import 'package:flutter/material.dart';
Expand All @@ -26,15 +25,14 @@ class PinsListPage extends ConsumerStatefulWidget {
}

class _AllPinsPageConsumerState extends ConsumerState<PinsListPage> {
String get searchValue => ref.watch(searchValueProvider);
String get _searchValue => ref.watch(pinListSearchTermProvider);

@override
void initState() {
super.initState();
widget.searchQuery.map((query) {
WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
ref.read(searchValueProvider.notifier).state = query;
});
WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
ref.read(pinListSearchTermProvider.notifier).state =
widget.searchQuery ?? '';
});
}

Expand Down Expand Up @@ -78,22 +76,23 @@ class _AllPinsPageConsumerState extends ConsumerState<PinsListPage> {
ActerSearchWidget(
initialText: widget.searchQuery,
onChanged: (value) {
final notifier = ref.read(searchValueProvider.notifier);
final notifier = ref.read(pinListSearchTermProvider.notifier);
notifier.state = value;
},
onClear: () {
final notifier = ref.read(searchValueProvider.notifier);
final notifier = ref.read(pinListSearchTermProvider.notifier);
notifier.state = '';
},
),
Expanded(
child: PinListWidget(
pinListProvider: pinListSearchedProvider(widget.spaceId),
spaceId: widget.spaceId,
shrinkWrap: false,
searchValue: searchValue,
searchValue: _searchValue,
emptyState: PinListEmptyState(
spaceId: widget.spaceId,
isSearchApplied: searchValue.isNotEmpty,
isSearchApplied: _searchValue.isNotEmpty,
),
),
),
Expand Down
81 changes: 49 additions & 32 deletions app/lib/features/pins/providers/pins_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,71 @@ import 'package:acter/features/pins/models/pin_edit_state/pin_edit_state.dart';
import 'package:acter/features/pins/providers/notifiers/create_pin_notifier.dart';
import 'package:acter/features/pins/providers/notifiers/edit_state_notifier.dart';
import 'package:acter/features/pins/providers/notifiers/pins_notifiers.dart';
import 'package:acter/features/search/providers/quick_search_providers.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart';
import 'package:riverpod/riverpod.dart';

//Search Value provider for pin list
final pinListSearchTermProvider = StateProvider<String>((ref) => '');

//SpaceId == null : GET LIST OF ALL PINs
//SpaceId != null : GET LIST OF SPACE PINs
final pinListProvider =
final _pinsProvider =
AsyncNotifierProvider.family<AsyncPinListNotifier, List<ActerPin>, String?>(
() => AsyncPinListNotifier(),
);

//Search any pins
typedef AllPinsSearchParams = ({String? spaceId, String searchText});
//All Pins List Provider
final allPinListProvider =
FutureProvider.autoDispose.family<List<ActerPin>, String?>(
(ref, spaceId) async => await ref.watch(_pinsProvider(spaceId).future),
);

// Pins with the bookmarked pins in front
final pinsProvider = FutureProvider.autoDispose
.family<List<ActerPin>, String?>((ref, spaceId) async {
final bookmarks =
await ref.watch(bookmarkByTypeProvider(BookmarkType.pins).future);
final pins = await ref.watch(pinListProvider(spaceId).future);
if (bookmarks.isEmpty) {
return pins;
}
// put the bookmarked pins in the front
final returnPins =
List<ActerPin?>.filled(bookmarks.length, null, growable: true);
final remaining = List<ActerPin>.empty(growable: true);
for (final pin in pins) {
final index = bookmarks.indexOf(pin.eventIdStr());
if (index != -1) {
returnPins[index] = pin;
} else {
remaining.add(pin);
final allPinListWithBookmarkFrontProvider =
FutureProvider.autoDispose.family<List<ActerPin>, String?>(
(ref, spaceId) async {
final pinList = await ref.watch(_pinsProvider(spaceId).future);
final bookmarks =
await ref.watch(bookmarkByTypeProvider(BookmarkType.pins).future);
if (bookmarks.isEmpty) return pinList;

//Put the bookmarked pins in the front
final bookmarkedPins = <ActerPin>[];
final otherPins = <ActerPin>[];

for (final pin in pinList) {
if (bookmarks.contains(pin.eventIdStr())) {
bookmarkedPins.add(pin);
} else {
otherPins.add(pin);
}
}
}
return returnPins
.where((a) => a != null)
.cast<ActerPin>()
.followedBy(remaining)
return [...bookmarkedPins, ...otherPins];
},
);

//Pin list with it's own search value provider
final pinListSearchedProvider = FutureProvider.autoDispose
.family<List<ActerPin>, String?>((ref, spaceId) async {
final pinList =
await ref.watch(allPinListWithBookmarkFrontProvider(spaceId).future);
final searchTerm = ref.watch(pinListSearchTermProvider).trim().toLowerCase();
if (searchTerm.isEmpty) return pinList;
return pinList
.where((pin) => pin.title().toLowerCase().contains(searchTerm))
.toList();
});

final pinListSearchProvider = FutureProvider.autoDispose
.family<List<ActerPin>, AllPinsSearchParams>((ref, params) async {
final pinList = await ref.watch(pinsProvider(params.spaceId).future);
final search = params.searchText.toLowerCase();
if (search.isEmpty) return pinList;
//Pin list for quick search value provider
final pinListQuickSearchedProvider =
FutureProvider.autoDispose<List<ActerPin>>((ref) async {
final pinList =
await ref.watch(allPinListWithBookmarkFrontProvider(null).future);
final searchTerm = ref.watch(quickSearchValueProvider).trim().toLowerCase();
if (searchTerm.isEmpty) return pinList;
return pinList
.where((pin) => pin.title().toLowerCase().contains(search))
.where((pin) => pin.title().toLowerCase().contains(searchTerm))
.toList();
});

Expand Down
18 changes: 5 additions & 13 deletions app/lib/features/pins/widgets/pin_list_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:acter/common/extensions/options.dart';
import 'package:acter/common/toolkit/errors/error_page.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_item_widget.dart';
import 'package:acter/features/pins/widgets/pin_list_skeleton.dart';
import 'package:acter/features/space/widgets/space_sections/section_header.dart';
Expand All @@ -13,6 +12,7 @@ import 'package:logging/logging.dart';
final _log = Logger('a3::pins::list');

class PinListWidget extends ConsumerWidget {
final ProviderBase<AsyncValue<List<ActerPin>>> pinListProvider;
final String? spaceId;
final String? searchValue;
final int? limit;
Expand All @@ -23,6 +23,7 @@ class PinListWidget extends ConsumerWidget {

const PinListWidget({
super.key,
required this.pinListProvider,
this.limit,
this.spaceId,
this.searchValue,
Expand All @@ -34,9 +35,8 @@ class PinListWidget extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final pinsLoader = ref.watch(
pinListSearchProvider((spaceId: spaceId, searchText: searchValue ?? '')),
);
final pinsLoader = ref.watch(pinListProvider);

return pinsLoader.when(
data: (pinList) => buildPinSectionUI(context, pinList),
error: (error, stack) => pinListErrorWidget(context, ref, error, stack),
Expand All @@ -57,15 +57,7 @@ class PinListWidget extends ConsumerWidget {
stack: stack,
textBuilder: L10n.of(context).loadingFailed,
onRetryTap: () {
if (searchValue?.isNotEmpty == true) {
ref.invalidate(
pinListSearchProvider(
(spaceId: spaceId, searchText: searchValue ?? ''),
),
);
} else {
ref.invalidate(pinListProvider(spaceId));
}
ref.invalidate(pinListProvider);
},
);
}
Expand Down
2 changes: 2 additions & 0 deletions app/lib/features/search/pages/quick_search_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:acter/common/utils/routes.dart';
import 'package:acter/common/widgets/acter_search_widget.dart';
import 'package:acter/features/events/providers/event_providers.dart';
import 'package:acter/features/events/widgets/event_list_widget.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_widget.dart';
import 'package:acter/features/search/model/keys.dart';
import 'package:acter/features/search/providers/quick_search_providers.dart';
Expand Down Expand Up @@ -129,6 +130,7 @@ class _QuickSearchPageState extends ConsumerState<QuickSearchPage> {
if (quickSearchFilters.value == QuickSearchFilters.all ||
quickSearchFilters.value == QuickSearchFilters.pins)
PinListWidget(
pinListProvider: pinListQuickSearchedProvider,
limit: 3,
searchValue: searchValue,
showSectionHeader: true,
Expand Down
2 changes: 2 additions & 0 deletions app/lib/features/space/pages/space_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:acter/common/utils/routes.dart';
import 'package:acter/common/widgets/scrollable_list_tab_scroller.dart';
import 'package:acter/features/events/providers/event_providers.dart';
import 'package:acter/features/events/widgets/event_list_widget.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_widget.dart';
import 'package:acter/features/space/dialogs/suggested_rooms.dart';
import 'package:acter/features/space/providers/space_navbar_provider.dart';
Expand Down Expand Up @@ -264,6 +265,7 @@ class _SpaceDetailsPageState extends ConsumerState<SpaceDetailsPage> {
TabEntry.overview => AboutSection(spaceId: widget.spaceId),
TabEntry.news => NewsSection(spaceId: widget.spaceId),
TabEntry.pins => PinListWidget(
pinListProvider: allPinListWithBookmarkFrontProvider(widget.spaceId),
spaceId: widget.spaceId,
showSectionHeader: true,
limit: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final tabsProvider =
}

if (appSettings.pins().active()) {
final pinsList = await ref.watch(pinListProvider(spaceId).future);
final pinsList = await ref.watch(allPinListProvider(spaceId).future);
if (pinsList.isNotEmpty) {
tabs.add(TabEntry.pins);
}
Expand Down
30 changes: 22 additions & 8 deletions app/test/features/pins/error_pages_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ import '../../helpers/test_util.dart';
void main() {
group('Pin List Error Pages', () {
testWidgets('full list', (tester) async {
final mockedPinListNotifier = RetryMockAsyncPinListNotifier();
bool shouldFail = true;
await tester.pumpProviderWidget(
overrides: [
bookmarkByTypeProvider.overrideWith((a, r) => []),
pinListProvider.overrideWith(() => mockedPinListNotifier),
pinListSearchedProvider.overrideWith((ref, spaceId) {
if (shouldFail) {
// toggle failure so the retry works
shouldFail = !shouldFail;
throw 'Expected fail: Space not loaded';
}
return [];
}),
hasSpaceWithPermissionProvider.overrideWith((_, ref) => false),
],
child: const PinsListPage(),
Expand All @@ -30,9 +37,9 @@ void main() {

await tester.pumpProviderWidget(
overrides: [
searchValueProvider
pinListSearchTermProvider
.overrideWith((_) => 'some string'), // set a search string
pinListSearchProvider.overrideWith((_, params) async {
pinListSearchedProvider.overrideWith((_, params) async {
if (shouldFail) {
shouldFail = false;
throw 'Some Error';
Expand All @@ -48,12 +55,19 @@ void main() {
});

testWidgets('space list', (tester) async {
final mockedPinListNotifier = RetryMockAsyncPinListNotifier();
bool shouldFail = true;
await tester.pumpProviderWidget(
overrides: [
bookmarkByTypeProvider.overrideWith((a, r) => []),
roomDisplayNameProvider.overrideWith((a, b) => 'test'),
pinListProvider.overrideWith(() => mockedPinListNotifier),
pinListSearchedProvider.overrideWith((ref, spaceId) {
if (shouldFail) {
// toggle failure so the retry works
shouldFail = !shouldFail;
throw 'Expected fail: Space not loaded';
}
return [];
}),
roomMembershipProvider.overrideWith((a, b) => null),
hasSpaceWithPermissionProvider.overrideWith((_, ref) => false),
],
Expand All @@ -70,9 +84,9 @@ void main() {
overrides: [
roomDisplayNameProvider.overrideWith((a, b) => 'test'),
roomMembershipProvider.overrideWith((a, b) => null),
searchValueProvider
pinListSearchTermProvider
.overrideWith((_) => 'some other string'), // set a search string
pinListSearchProvider.overrideWith((_, params) async {
pinListSearchedProvider.overrideWith((_, params) async {
if (shouldFail) {
shouldFail = false;
throw 'Some Error';
Expand Down

0 comments on commit 19e9eb0

Please sign in to comment.