Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ void main() async {

Adaptive.ignoreHeight = true;

setPathUrlStrategy();
// setPathUrlStrategy();

// initializeEchidnaApi(baseUrl: kEchidnaHost, clientKey: kEchidnaClientKey, clientId: kEchidnaClientID);

Expand Down
7 changes: 4 additions & 3 deletions lib/src/app/presentation/widgets/title_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,12 @@ class TitleBarState extends State<TitleBar> with WindowListener, RouteAware, Ada
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: Spacing.mediumSpacing,
children: [
ConditionalWrapper(
condition: _parentRoute != null,
wrapper: (_, child) => TextButton(
onPressed: () => Modular.to.navigate(_parentRoute!),
onPressed: () => Modular.to.pushNamed(_parentRoute!),
child: Row(
children: [
const Icon(FontAwesome5Solid.angle_left),
Expand Down Expand Up @@ -314,7 +315,7 @@ class TitleBarState extends State<TitleBar> with WindowListener, RouteAware, Ada
if (user.vintage != null)
user.vintage!.humanReadable.text.color(context.theme.colorScheme.primary).color(context.theme.colorScheme.primary)
else if (user.capabilities.isNotEmpty)
Text(user.capabilities.highest.translate(context)).color(context.theme.colorScheme.primary),
Text(user.capabilities.map((c) => c.translate(context)).join(', ')).color(context.theme.colorScheme.primary),
],
),
].show(),
Expand Down Expand Up @@ -367,7 +368,7 @@ class TitleBarState extends State<TitleBar> with WindowListener, RouteAware, Ada
ConditionalWrapper(
condition: _parentRoute != null,
wrapper: (_, child) => TextButton(
onPressed: () => Modular.to.navigate(_parentRoute!),
onPressed: () => Modular.to.pushNamed(_parentRoute!),
child: Row(
children: [
const Icon(FontAwesome5Solid.angle_left),
Expand Down
2 changes: 1 addition & 1 deletion lib/src/app/utils/no_mobile_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mixin NoMobile on Adaptive {
image: Assets.mobile,
).expanded(),
ElevatedButton(
onPressed: () => Modular.to.navigate('/dashboard/'),
onPressed: () => Modular.to.pushNamed('/dashboard/'),
child: Text(context.t.app_noMobile_goBack),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class _CourseOverviewScreenState extends State<CourseOverviewScreen> with Adapti
).toList(),
),
),
),
).greedy(),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class _CoursesOverviewScreenState extends State<CoursesOverviewScreen> with Adap
return Padding(
padding: PaddingAll(),
child: SingleChildScrollView(
clipBehavior: Clip.none,
child: Wrap(
runSpacing: Spacing.mediumSpacing,
spacing: Spacing.mediumSpacing,
Expand All @@ -51,7 +52,7 @@ class _CoursesOverviewScreenState extends State<CoursesOverviewScreen> with Adap
),
].show(),
),
),
).greedy(),
);
}
}
21 changes: 9 additions & 12 deletions lib/src/moodle/presentation/widgets/user_widget.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:eduplanner/eduplanner.dart';
import 'package:flutter/material.dart';

/// Displays the user's profile image and name.
class UserWidget extends StatelessWidget {
/// Displays the user's profile image and name.
const UserWidget({super.key, required this.user, this.size = 20, this.style, this.expand = false});
const UserWidget({super.key, required this.user, this.size = 20, this.style, this.flexible = false});

/// The user to display.
final User user;
Expand All @@ -16,27 +17,23 @@ class UserWidget extends StatelessWidget {
final TextStyle? style;

/// If true the widget will expand to fill the available space.
final bool expand;
final bool flexible;

@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: expand ? MainAxisSize.max : MainAxisSize.min,
mainAxisSize: MainAxisSize.min,
children: [
UserProfileImage(
userId: user.id,
size: size,
),
Spacing.smallHorizontal(),
ConditionalWrapper(
condition: expand,
wrapper: (context, child) => Expanded(child: child),
child: Text(
user.fullname,
style: style,
overflow: TextOverflow.ellipsis,
),
),
Text(
user.fullname,
style: style,
overflow: TextOverflow.ellipsis,
).flexible(),
],
Comment on lines +32 to 37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore optional flex behavior

flexible is never read now—the text is always wrapped in .flexible(), so callers setting flexible: false still get a Flexible child, altering layouts (and regressing previous behavior). Gate the modifier on the flag instead of forcing it.

-        Text(
-          user.fullname,
-          style: style,
-          overflow: TextOverflow.ellipsis,
-        ).flexible(),
+        final name = Text(
+          user.fullname,
+          style: style,
+          overflow: TextOverflow.ellipsis,
+        );
+
+        return Row(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            UserProfileImage(
+              userId: user.id,
+              size: size,
+            ),
+            Spacing.smallHorizontal(),
+            flexible ? name.flexible() : name,
+          ],
+        );

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/src/moodle/presentation/widgets/user_widget.dart around lines 32 to 37,
the Text widget is always wrapped with .flexible() which ignores the flexible
flag and alters layouts; change the code to conditionally wrap the Text: if the
widget's flexible flag is true wrap the Text with .flexible(), otherwise return
the plain Text widget so callers passing flexible: false get an unwrapped child.

);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/slots/domain/models/slot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ enum Weekday {
for (var i = 0; i < 7; i++) {
final date = now.add(Duration(days: i));
if (date.weekday == index + 1) {
return date;
return date.copyWith(hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0);
}
}
throw StateError('Could not find next date for $this');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ class SlotMasterCoursesRepository extends Repository<AsyncValue<List<MoodleCours
}).toList();
}

/// Gets the course with the given [id], or null if no such course exists.

MoodleCourse? getById(int id) {
if (!state.hasData) {
log('Cannot get course by id: No data available.');

return null;
}

try {
return state.requireData.firstWhere((element) => element.id == id);
} catch (e) {
return null;
}
}

@override
void dispose() {
super.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import 'package:mcquenji_core/mcquenji_core.dart';
class SlotMasterSlotsRepository extends Repository<AsyncValue<List<Slot>>> with Tracable {
final AuthRepository _auth;
final SlotsDatasource _datasource;
final SlotMasterCoursesRepository _courses;
final UsersRepository _users;

/// Provides data for the slot master screen.
SlotMasterSlotsRepository(this._auth, this._datasource) : super(AsyncValue.loading()) {
SlotMasterSlotsRepository(this._auth, this._datasource, this._courses, this._users) : super(AsyncValue.loading()) {
watchAsync(_auth);
watchAsync(_courses);
watchAsync(_users);

_datasource.parent = this;
}
Expand Down Expand Up @@ -360,6 +364,42 @@ class SlotMasterSlotsRepository extends Repository<AsyncValue<List<Slot>>> with
};
}

/// Queries the slots for the given [query].
///
/// A query matches a slot if the room, any course name, any vintage or any supervisor's name contains the query (case-insensitive).
List<Slot> query(String query, {Iterable<Slot>? slots}) {
final _slots = slots ?? state.data;

if (_slots == null) {
log('Cannot query slots: No data');
return [];
}

if (query.isEmpty) {
return _slots.toList();
}

return _slots.where((slot) {
if (slot.room.containsIgnoreCase(query)) {
return true;
}

if (slot.mappings.any((m) => m.vintage.humanReadable.containsIgnoreCase(query))) {
return true;
}

if (slot.mappings.any((m) => _courses.getById(m.courseId)?.name.containsIgnoreCase(query) ?? false)) {
return true;
}

if (_users.filter(ids: slot.supervisors).any((u) => u.fullname.containsIgnoreCase(query))) {
return true;
}

return false;
}).toList();
}

@override
void dispose() {
_datasource.dispose();
Expand All @@ -368,6 +408,7 @@ class SlotMasterSlotsRepository extends Repository<AsyncValue<List<Slot>>> with
}

/// Adds a query method to the [Slot] iterable.
@Deprecated('Use SlotsRepository.query instead')
extension QueryX on Iterable<Slot> {
/// Queries the slots for the given [query].
List<Slot> query(String query) {
Expand Down
25 changes: 8 additions & 17 deletions lib/src/slots/presentation/screens/slot_details_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class _SlotDetailsScreenState extends State<SlotDetailsScreen> with AdaptiveStat
void didChangeDependencies() {
super.didChangeDependencies();

Data.of<TitleBarState>(context).setParentRoute('/slots/overview/');
Data.of<TitleBarState>(context)
..setParentRoute('/slots/overview/')
..setTrailingWidget(const SlotsViewSwitcher());
}

@override
Expand All @@ -44,7 +46,7 @@ class _SlotDetailsScreenState extends State<SlotDetailsScreen> with AdaptiveStat

final courseVintage = slot.mappings
.map((m) {
final course = courseRepository.filter(id: m.courseId).firstOrNull;
final course = courseRepository.getById(m.courseId);
final vintage = m.vintage;

return (course, vintage);
Expand Down Expand Up @@ -103,7 +105,7 @@ class _SlotDetailsScreenState extends State<SlotDetailsScreen> with AdaptiveStat
children: [
for (final supervisor in supervisors) UserWidget(user: supervisor),
],
).stretch(),
).expanded(),
],
),
),
Expand All @@ -121,20 +123,9 @@ class _SlotDetailsScreenState extends State<SlotDetailsScreen> with AdaptiveStat
spacing: Spacing.mediumSpacing,
runSpacing: Spacing.mediumSpacing,
children: [
for (final (course, vintage) in courseVintage)
Row(
mainAxisSize: MainAxisSize.min,
children: [
CourseTag(course: course!),
Spacing.xsHorizontal(),
Text(course.name),
Spacing.smallHorizontal(),
Text(vintage.humanReadable),
Spacing.mediumHorizontal(),
],
),
for (final (course, vintage) in courseVintage) MappingWidget(course: course!, vintage: vintage),
],
).stretch(),
).expanded(),
],
),
),
Expand Down Expand Up @@ -180,7 +171,7 @@ class _SlotDetailsScreenState extends State<SlotDetailsScreen> with AdaptiveStat
),
],
),
),
).stretch(),
),
).expanded(flex: 6),
],
Expand Down
10 changes: 6 additions & 4 deletions lib/src/slots/presentation/screens/slot_master_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ class _SlotMasterScreenState extends State<SlotMasterScreen> with AdaptiveState,
void didChangeDependencies() {
super.didChangeDependencies();

Data.of<TitleBarState>(context).setSearchController(searchController);
Data.of<TitleBarState>(context)
..setSearchController(searchController)
..setTrailingWidget(const SlotsViewSwitcher());
}

void createSlot(Weekday weekday, SlotTimeUnit startUnit) {
Expand Down Expand Up @@ -80,15 +82,15 @@ class _SlotMasterScreenState extends State<SlotMasterScreen> with AdaptiveState,
child: TabBarView(
controller: _tabController,
children: [
for (final weekday in Weekday.values) slotTimeTable(groups[weekday] ?? <SlotTimeUnit, List<Slot>>{}, weekday),
for (final weekday in Weekday.values) slotTimeTable(groups[weekday] ?? <SlotTimeUnit, List<Slot>>{}, weekday, slots),
],
),
),
),
);
}

Widget slotTimeTable(Map<SlotTimeUnit, List<Slot>> activeGroup, Weekday weekday) {
Widget slotTimeTable(Map<SlotTimeUnit, List<Slot>> activeGroup, Weekday weekday, SlotMasterSlotsRepository repo) {
return SingleChildScrollView(
child: Column(
spacing: Spacing.largeSpacing,
Expand Down Expand Up @@ -117,7 +119,7 @@ class _SlotMasterScreenState extends State<SlotMasterScreen> with AdaptiveState,
runSpacing: Spacing.mediumSpacing,
children: [
// TODO(mastermarcohd): implement more intelligent sorting to account for building and floor.
for (final slot in (activeGroup[timeUnit] ?? <Slot>[]).query(searchController.text).sortedBy((s) => s.room))
for (final slot in repo.query(searchController.text, slots: activeGroup[timeUnit] ?? <Slot>[]).sortedBy((s) => s.room))
SizedBox(
key: ValueKey(slot),
width: tileWidth,
Expand Down
Loading
Loading