Skip to content

Commit

Permalink
feat(#432): add pins to study table and refactor code
Browse files Browse the repository at this point in the history
  • Loading branch information
johannesvedder committed Jun 23, 2023
1 parent f4fb163 commit b0e5648
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 113 deletions.
16 changes: 11 additions & 5 deletions core/lib/src/models/tables/user.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 33 additions & 25 deletions designer_v2/lib/common_views/standard_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,11 @@ class _StandardTableState<T> extends State<StandardTable<T>> {
],
);
}

return tableWidget;
}

void _sortColumn(List<T> items, int columnIndex) {
void _sortColumn(int columnIndex) {
if (!(columnIndex >= 0 && columnIndex < widget.inputColumns.length)) return;
final sortAscending = widget.inputColumns[columnIndex].sortAscending;

// Save default order to restore later
Expand All @@ -222,30 +222,38 @@ class _StandardTableState<T> extends State<StandardTable<T>> {
}

if (sortAscending != null) {
if (columnIndex >= 0 && columnIndex < widget.inputColumns.length) {
final sortPredicate = widget.sortColumnPredicates;
items.sort((a, b) {
if (sortPredicate != null && sortPredicate[columnIndex] != null) {
return sortAscending ? sortPredicate[columnIndex]!(a, b) : sortPredicate[columnIndex]!(b, a);
} else if (a is Comparable && b is Comparable) {
// If sortPredicate is not provided, use default comparison logic
return sortAscending ? Comparable.compare(a, b) : Comparable.compare(b, a);
} else {
return 0;
}
});
// Extract and insert pinned items at the top
if (widget.pinnedPredicates != null) {
items.sort((a, b) {
return widget.pinnedPredicates!(a, b);
});
}
_cachedRows.clear();
}
widget.items.sort((a, b) {
return _sortLogic(a, b, columnIndex: columnIndex, sortAscending: sortAscending);
});
_sortPinnedStudies(widget.items, columnIndex: columnIndex, sortAscending: sortAscending);
} else {
widget.items.clear();
widget.items.addAll(sortDefaultOrder!);
_cachedRows.clear();
_sortPinnedStudies(widget.items, columnIndex: columnIndex);
}
_cachedRows.clear();
}

void _sortPinnedStudies(List<T> items, {required int columnIndex, bool? sortAscending}) {
// Extract and insert pinned items at the top
if (widget.pinnedPredicates != null) {
items.sort((a, b) {
int ret = widget.pinnedPredicates!(a, b);
// Fallback to default sorting algorithm
return ret == 0 ? _sortLogic(a, b, columnIndex: columnIndex, sortAscending: sortAscending) : ret;
});
}
}

int _sortLogic(T a, T b, {required int columnIndex, required bool? sortAscending}) {
final sortPredicate = widget.sortColumnPredicates;
if (sortPredicate != null && sortPredicate[columnIndex] != null) {
return sortAscending ?? true ? sortPredicate[columnIndex]!(a, b) : sortPredicate[columnIndex]!(b, a);
} else if (a is Comparable && b is Comparable) {
// If sortPredicate is not provided, use default comparison logic
return sortAscending ?? true ? Comparable.compare(a, b) : Comparable.compare(b, a);
} else {
return 0;
}
}

Expand Down Expand Up @@ -357,8 +365,8 @@ class _StandardTableState<T> extends State<StandardTable<T>> {
widget.inputColumns[i].sortableIcon = null;
break;
}
_sortColumn(widget.items, i);
// No sorting is active and hovered
_sortColumn(i);
// No sorting icon is active or hovered
} else if (widget.inputColumns[i].sortAscending == null) {
if (hover is PointerEnterEvent) {
widget.inputColumns[i].sortableIcon = hoveredIcon;
Expand Down
4 changes: 2 additions & 2 deletions designer_v2/lib/domain/study.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:supabase_flutter/supabase_flutter.dart' as sb;

enum StudyActionType {
pin,
unpin,
pinoff,
edit,
duplicate,
duplicateDraft,
Expand All @@ -22,7 +22,7 @@ extension StudyActionTypeFormatted on StudyActionType {
switch (this) {
case StudyActionType.pin:
return tr.action_pin;
case StudyActionType.unpin:
case StudyActionType.pinoff:
return tr.action_unpin;
case StudyActionType.edit:
return tr.action_edit;
Expand Down
20 changes: 18 additions & 2 deletions designer_v2/lib/features/dashboard/dashboard_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:studyu_designer_v2/features/study/study_actions.dart';
import 'package:studyu_designer_v2/repositories/auth_repository.dart';
import 'package:studyu_designer_v2/repositories/model_repository.dart';
import 'package:studyu_designer_v2/repositories/study_repository.dart';
import 'package:studyu_designer_v2/repositories/user_repository.dart';
import 'package:studyu_designer_v2/routing/router.dart';
import 'package:studyu_designer_v2/routing/router_intent.dart';
import 'package:studyu_designer_v2/utils/model_action.dart';
Expand All @@ -17,6 +18,7 @@ class DashboardController extends StateNotifier<DashboardState> implements IMode
/// References to the data repositories injected by Riverpod
final IStudyRepository studyRepository;
final IAuthRepository authRepository;
final IUserRepository userRepository;

/// Reference to services injected via Riverpod
final GoRouter router;
Expand All @@ -27,6 +29,7 @@ class DashboardController extends StateNotifier<DashboardState> implements IMode
DashboardController({
required this.studyRepository,
required this.authRepository,
required this.userRepository,
required this.router,
}) : super(DashboardState(currentUser: authRepository.currentUser!)) {
_subscribeStudies();
Expand Down Expand Up @@ -67,8 +70,20 @@ class DashboardController extends StateNotifier<DashboardState> implements IMode
}
}

void sortAndUpdate(Set<String> pinnedStudies) {
final studies = state.sort(pinnedStudies: pinnedStudies);
Future<void> pinStudy(String modelId) async {
userRepository.user.preferences.pinnedStudies.add(modelId);
await userRepository.saveUser();
sortStudies();
}

Future<void> pinOffStudy(String modelId) async {
userRepository.user.preferences.pinnedStudies.remove(modelId);
await userRepository.saveUser();
sortStudies();
}

void sortStudies() async {
final studies = state.sort(pinnedStudies: userRepository.user.preferences.pinnedStudies);
state = state.copyWith(
studies: () => AsyncValue.data(studies),
);
Expand All @@ -93,6 +108,7 @@ final dashboardControllerProvider = StateNotifierProvider.autoDispose<DashboardC
final dashboardController = DashboardController(
studyRepository: ref.watch(studyRepositoryProvider),
authRepository: ref.watch(authRepositoryProvider),
userRepository: ref.watch(userRepositoryProvider),
router: ref.watch(routerProvider),
);
dashboardController.addListener((state) {
Expand Down
70 changes: 38 additions & 32 deletions designer_v2/lib/features/dashboard/dashboard_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
final theme = Theme.of(context);
final controller = ref.read(dashboardControllerProvider.notifier);
final state = ref.watch(dashboardControllerProvider);
final userRepo = ref.watch(userProvider);
final userRepo = ref.watch(userRepositoryProvider);

return DashboardScaffold(
body: Column(
Expand Down Expand Up @@ -95,37 +95,43 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
],
),
const SizedBox(height: 24.0), // spacing between body elements
AsyncValueWidget<UserRepository>(
value: userRepo,
data: (userRepository) => AsyncValueWidget<List<Study>>(
value: state.visibleStudies(searchQuery, userRepository.user.preferences.pinnedStudies),
data: (visibleStudies) => StudiesTable(
studies: visibleStudies,
pinnedStudies: userRepository.user.preferences.pinnedStudies,
onSelect: controller.onSelectStudy,
getActions: controller.availableActions,
emptyWidget: (widget.filter == null || widget.filter == StudiesFilter.owned)
? (searchQuery != null && searchQuery!.isNotEmpty)
? Padding(
padding: const EdgeInsets.only(top: 24.0),
child: EmptyBody(
icon: Icons.content_paste_search_rounded,
title: tr.studies_not_found,
description: tr.modify_query,
),
)
: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: EmptyBody(
icon: Icons.content_paste_search_rounded,
title: tr.studies_empty,
description: tr.studies_empty_description,
// "...or create a new draft copy from an already published study!",
/* button: PrimaryButton(text: "From template",); */
),
)
: const SizedBox.shrink(),
))),
FutureBuilder<StudyUUser>(
future: userRepo.fetchUser(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return AsyncValueWidget<List<Study>>(
value: state.visibleStudies(searchQuery, snapshot.data!.preferences.pinnedStudies),
data: (visibleStudies) => StudiesTable(
studies: visibleStudies,
pinnedStudies: snapshot.data!.preferences.pinnedStudies,
dashboardProvider: ref.read(dashboardControllerProvider.notifier),
onSelect: controller.onSelectStudy,
getActions: controller.availableActions,
emptyWidget: (widget.filter == null || widget.filter == StudiesFilter.owned)
? (searchQuery != null && searchQuery!.isNotEmpty)
? Padding(
padding: const EdgeInsets.only(top: 24.0),
child: EmptyBody(
icon: Icons.content_paste_search_rounded,
title: tr.studies_not_found,
description: tr.modify_query,
),
)
: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: EmptyBody(
icon: Icons.content_paste_search_rounded,
title: tr.studies_empty,
description: tr.studies_empty_description,
// "...or create a new draft copy from an already published study!",
/* button: PrimaryButton(text: "From template",); */
),
)
: const SizedBox.shrink(),
));
}
return const SizedBox.shrink();
}),
],
),
);
Expand Down
41 changes: 29 additions & 12 deletions designer_v2/lib/features/dashboard/studies_table.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_designer_v2/common_views/action_popup_menu.dart';
import 'package:studyu_designer_v2/common_views/mouse_events.dart';
import 'package:studyu_designer_v2/common_views/standard_table.dart';
import 'package:studyu_designer_v2/features/dashboard/dashboard_controller.dart';
import 'package:studyu_designer_v2/localization/app_translation.dart';
import 'package:studyu_designer_v2/theme.dart';
import 'package:studyu_designer_v2/utils/extensions.dart';
Expand All @@ -15,14 +18,16 @@ class StudiesTable extends StatelessWidget {
required this.getActions,
required this.emptyWidget,
required this.pinnedStudies,
required this.dashboardProvider,
Key? key,
}) : super(key: key);

final List<Study> studies;
final Iterable<String>? pinnedStudies;
final OnSelectHandler<Study> onSelect;
final ActionsProviderFor<Study> getActions;
final Widget emptyWidget;
final Iterable<String> pinnedStudies;
final DashboardController dashboardProvider;

@override
Widget build(BuildContext context) {
Expand All @@ -37,6 +42,10 @@ class StudiesTable extends StatelessWidget {
return StandardTable<Study>(
items: studies,
columns: [
StandardTableColumn(
label: '',
columnWidth: const FixedColumnWidth(60),
),
StandardTableColumn(
label: tr.studies_list_header_title,
columnWidth: const MaxColumnWidth(FixedColumnWidth(200), FlexColumnWidth(2.4)),
Expand Down Expand Up @@ -82,19 +91,11 @@ class StudiesTable extends StatelessWidget {
);
}

int Function(Study a, Study b)? get pinnedPredicates {
if (pinnedStudies == null)
(
a,
b,
) =>
0;
int Function(Study a, Study b) get pinnedPredicates {
return (Study a, Study b) {
if (pinnedStudies!.contains(a.id) && pinnedStudies!.contains(b.id)) {
return 0;
} else if (pinnedStudies!.contains(a.id)) {
if (pinnedStudies.contains(a.id)) {
return -1;
} else if (pinnedStudies!.contains(b.id)) {
} else if (pinnedStudies.contains(b.id)) {
return 1;
}
return 0;
Expand All @@ -103,6 +104,7 @@ class StudiesTable extends StatelessWidget {

List<int Function(Study a, Study b)?> get _sortColumns {
final predicates = [
(Study a, Study b) => 0, // do not sort pin icon
(Study a, Study b) => a.title!.compareTo(b.title!),
(Study a, Study b) => a.status.index.compareTo(b.status.index),
(Study a, Study b) => a.participation.index.compareTo(b.participation.index),
Expand All @@ -121,7 +123,22 @@ class StudiesTable extends StatelessWidget {
return (value > 0) ? null : ThemeConfig.bodyTextBackground(theme);
}

Icon pinIcon(IconData iconData) {
return Icon(
iconData,
color: Colors.grey,
size: 20,
);
}

return [
pinnedStudies.contains(item.id)
? MouseEventsRegion(
onTap: () => dashboardProvider.pinOffStudy(item.id),
builder: (context, mouseEventState) =>
mouseEventState.contains(MaterialState.hovered) ? pinIcon(MdiIcons.pinOff) : pinIcon(MdiIcons.pin),
)
: const SizedBox.shrink(),
Text(item.title ?? '[Missing study title]'),
StudyStatusBadge(
status: item.status,
Expand Down
2 changes: 1 addition & 1 deletion designer_v2/lib/features/study/study_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:studyu_designer_v2/domain/study.dart';

Map<StudyActionType, IconData> studyActionIcons = {
StudyActionType.pin: MdiIcons.pin,
StudyActionType.unpin: MdiIcons.pinOff,
StudyActionType.pinoff: MdiIcons.pinOff,
StudyActionType.edit: Icons.edit_rounded,
StudyActionType.duplicate: Icons.file_copy_rounded,
StudyActionType.duplicateDraft: Icons.file_copy_rounded,
Expand Down
Loading

0 comments on commit b0e5648

Please sign in to comment.