Skip to content

Commit 9541f27

Browse files
authored
Merge pull request #5 from synyx/material3
Material 3
2 parents 6c74d77 + bf03a81 commit 9541f27

31 files changed

+1023
-305
lines changed

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ subprojects {
2626
project.evaluationDependsOn(':app')
2727
}
2828

29-
task clean(type: Delete) {
29+
tasks.register("clean", Delete) {
3030
delete rootProject.buildDir
3131
}

lib/cubit/theme_mode_cubit.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
import 'package:flutter/material.dart';
3+
import 'package:hydrated_bloc/hydrated_bloc.dart';
4+
5+
class ThemeModeCubit extends HydratedCubit<ThemeMode> {
6+
ThemeModeCubit() : super(ThemeMode.system);
7+
8+
bool? get isDark => switch (state) {
9+
ThemeMode.dark => true,
10+
ThemeMode.light => false,
11+
ThemeMode.system => null,
12+
};
13+
14+
set dark(bool? dark) => emit(switch (dark) {
15+
true => ThemeMode.dark,
16+
false => ThemeMode.light,
17+
null => ThemeMode.system,
18+
});
19+
20+
@override
21+
ThemeMode fromJson(Map<String, dynamic> json) => switch (json['version']) {
22+
<= 1 => switch (json['themeMode']) {
23+
'dark' => ThemeMode.dark,
24+
'light' => ThemeMode.light,
25+
_ => ThemeMode.system,
26+
},
27+
_ => ThemeMode.system,
28+
};
29+
30+
@override
31+
Map<String, dynamic>? toJson(ThemeMode state) {
32+
final themeMode = switch (state) {
33+
ThemeMode.dark => 'dark',
34+
ThemeMode.light => 'light',
35+
_ => 'system',
36+
};
37+
38+
return {
39+
'version': 1,
40+
'themeMode': themeMode,
41+
};
42+
}
43+
}

lib/cubit/time_entries_filter.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library time_entries_filter;
2+
3+
import 'package:built_collection/built_collection.dart';
4+
import 'package:built_value/built_value.dart';
5+
import 'package:built_value/json_object.dart';
6+
import 'package:built_value/serializer.dart';
7+
import 'package:syntrack/model/common/task.dart';
8+
9+
part 'time_entries_filter.g.dart';
10+
11+
abstract class TimeEntriesFilter implements Built<TimeEntriesFilter, TimeEntriesFilterBuilder> {
12+
String? get query;
13+
DateTime? get filterStart;
14+
DateTime? get filterEnd;
15+
Duration? get filterDuration;
16+
bool? get filterBooked;
17+
Set<int?> get filterWeekday;
18+
Task? get filterTask;
19+
Set<String?> get filterWorkInterfaceId;
20+
Set<String?> get filterActivityNames;
21+
22+
TimeEntriesFilter._();
23+
24+
factory TimeEntriesFilter([void Function(TimeEntriesFilterBuilder) updates]) = _$TimeEntriesFilter;
25+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import 'package:easy_debounce/easy_debounce.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:syntrack/cubit/time_entries_filter.dart';
4+
import 'package:syntrack/model/common/time_entry.dart';
5+
import 'package:syntrack/util/date_time_extension.dart';
6+
7+
class TimeEntriesFilterCubit extends Cubit<TimeEntriesFilter> {
8+
TimeEntriesFilterCubit()
9+
: super((TimeEntriesFilterBuilder()
10+
..filterActivityNames = {}
11+
..filterWorkInterfaceId = {}
12+
..filterWeekday = {})
13+
.build());
14+
15+
Iterable<TimeEntry> filter(List<TimeEntry> timeEntries) {
16+
final TimeEntriesFilter(
17+
query: query,
18+
filterActivityNames: filterActivityNames,
19+
filterBooked: filterBooked,
20+
filterTask: filterTask,
21+
filterDuration: filterDuration,
22+
filterWeekday: filterWeekday,
23+
filterWorkInterfaceId: filterWorkInterfaceId,
24+
filterStart: filterStart,
25+
filterEnd: filterEnd,
26+
) = state;
27+
28+
if (query == null &&
29+
filterActivityNames.isEmpty &&
30+
filterBooked == null &&
31+
filterTask == null &&
32+
filterDuration == null &&
33+
filterWeekday.isEmpty &&
34+
filterWorkInterfaceId.isEmpty &&
35+
filterStart == null &&
36+
filterEnd == null) {
37+
return timeEntries;
38+
}
39+
40+
return timeEntries
41+
.where(
42+
(element) => query != null
43+
? '${element.comment.trim()}${element.task?.name ?? '<NO TASK>'}${element.activity?.name ?? '<NO ACTIVITY>'}'
44+
.toLowerCase()
45+
.contains(query.trim().toLowerCase())
46+
: true,
47+
)
48+
.where(
49+
(element) => filterActivityNames.isNotEmpty ? filterActivityNames.contains(element.activity?.name) : true,
50+
)
51+
.where(
52+
(element) => filterBooked != null
53+
? filterBooked
54+
? element.bookingId != null
55+
: element.bookingId == null
56+
: true,
57+
)
58+
.where(
59+
(element) => filterTask != null ? element.task?.id == filterTask.id : true,
60+
)
61+
.where(
62+
(element) => filterWeekday.isNotEmpty ? filterWeekday.contains(element.start.weekday) : true,
63+
)
64+
.where(
65+
(element) =>
66+
filterWorkInterfaceId.isNotEmpty ? filterWorkInterfaceId.contains(element.task?.workInterfaceId) : true,
67+
)
68+
.where(
69+
(element) => filterStart != null ? element.start.startOfDay == filterStart.startOfDay : true,
70+
)
71+
.where(
72+
(element) => filterEnd != null ? element.end.startOfDay == filterEnd.startOfDay : true,
73+
)
74+
.where(
75+
(element) => filterDuration != null ? filterDuration.inSeconds <= element.duration.inSeconds : true,
76+
);
77+
}
78+
79+
void debouncedFilters(Function(TimeEntriesFilterBuilder) updates) {
80+
EasyDebounce.debounce('time_entries_filter_cubit.debouncedFilters', const Duration(milliseconds: 500), () {
81+
setFilters(updates);
82+
});
83+
}
84+
85+
void setFilters(Function(TimeEntriesFilterBuilder) updates) {
86+
emit(state.rebuild(updates));
87+
}
88+
89+
void clearFilters() {
90+
emit(state.rebuild((p0) => p0
91+
..filterActivityNames = const {}
92+
..filterBooked = null
93+
..filterTask = null
94+
..filterDuration = null
95+
..filterWeekday = const {}
96+
..filterWorkInterfaceId = const {}
97+
..filterStart = null
98+
..filterEnd = null));
99+
}
100+
101+
void debouncedQuery(String? query) {
102+
EasyDebounce.debounce(
103+
'time_entries_filter_cubit.debouncedQuery',
104+
const Duration(milliseconds: 750),
105+
() {
106+
immediateQuery(query);
107+
},
108+
);
109+
}
110+
111+
void immediateQuery(String? query) {
112+
emit(state.rebuild((state) => state..query = query));
113+
}
114+
}

lib/cubit/work_interface_cubit.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'dart:convert';
22

33
import 'package:built_collection/built_collection.dart';
4+
import 'package:collection/collection.dart';
45
import 'package:hydrated_bloc/hydrated_bloc.dart';
6+
import 'package:syntrack/model/common/task_search_origin.dart';
57
import 'package:syntrack/model/work/erpnext/erpnext_config.dart';
68
import 'package:syntrack/model/work/redmine/redmine_config.dart';
79
import 'package:syntrack/model/work/work_interface_configs.dart';
@@ -98,4 +100,26 @@ class WorkInterfaceCubit extends HydratedCubit<WorkInterfaceConfigs> {
98100
'data': jsonDecode(state.toJson()),
99101
};
100102
}
103+
104+
TaskSearchOrigin? getOriginFor(String workInterfaceId) {
105+
return state.combinedConfigs
106+
.map((element) => switch (element) {
107+
ErpNextConfig() => element.id == workInterfaceId ? TaskSearchOrigin.erpNext : null,
108+
RedmineConfig() => element.id == workInterfaceId ? TaskSearchOrigin.redmine : null,
109+
_ => null,
110+
})
111+
.firstWhereOrNull((element) => element != null);
112+
}
113+
114+
String getNameFor(dynamic workInterface) => switch (workInterface) {
115+
ErpNextConfig() => workInterface.name,
116+
RedmineConfig() => workInterface.name,
117+
_ => '',
118+
};
119+
120+
String getIdFor(dynamic workInterface) => switch (workInterface) {
121+
ErpNextConfig() => workInterface.id,
122+
RedmineConfig() => workInterface.id,
123+
_ => '',
124+
};
101125
}

lib/main.dart

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
88
import 'package:path_provider/path_provider.dart';
99
import 'package:sizer/sizer.dart';
1010
import 'package:syntrack/cubit/booking_cubit.dart';
11+
import 'package:syntrack/cubit/theme_mode_cubit.dart';
1112
import 'package:syntrack/cubit/task_search_cubit.dart';
1213
import 'package:syntrack/cubit/time_entries_cubit.dart';
14+
import 'package:syntrack/cubit/time_entries_filter_cubit.dart';
1315
import 'package:syntrack/cubit/time_tracking_cubit.dart';
1416
import 'package:syntrack/cubit/work_interface_cubit.dart';
1517
import 'package:syntrack/repository/data/latest_bookings_data_provider.dart';
@@ -59,7 +61,6 @@ Future<void> createHydratedBoxBackup(Directory appDir, {int maxBackups = 10}) as
5961
}
6062

6163
class SynTrack extends StatelessWidget {
62-
static const primarySwatch = Colors.blue;
6364
final _appRouter = AppRouter();
6465

6566
SynTrack({super.key});
@@ -71,9 +72,15 @@ class SynTrack extends StatelessWidget {
7172
create: (context) => WorkRepository(),
7273
child: MultiBlocProvider(
7374
providers: [
75+
BlocProvider(
76+
create: (context) => ThemeModeCubit(),
77+
),
7478
BlocProvider(
7579
create: (context) => TimeEntriesCubit(),
7680
),
81+
BlocProvider(
82+
create: (context) => TimeEntriesFilterCubit(),
83+
),
7784
BlocProvider(
7885
lazy: false,
7986
create: (context) {
@@ -112,9 +119,16 @@ class SynTrack extends StatelessWidget {
112119
],
113120
title: 'synTrack',
114121
theme: ThemeData(
115-
primarySwatch: primarySwatch,
116-
useMaterial3: false,
122+
brightness: Brightness.light,
123+
useMaterial3: true,
124+
colorSchemeSeed: const Color(0xFFFF5250),
125+
),
126+
darkTheme: ThemeData(
127+
brightness: Brightness.dark,
128+
useMaterial3: true,
129+
colorSchemeSeed: const Color(0xFF1923DC),
117130
),
131+
themeMode: context.watch<ThemeModeCubit>().state,
118132
routerDelegate: _appRouter.delegate(),
119133
routeInformationParser: _appRouter.defaultRouteParser(),
120134
);

lib/model/common/task_search_result.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'package:syntrack/model/serializer/serializers.dart';
1212
part 'task_search_result.g.dart';
1313

1414
abstract class TaskSearchResult implements Built<TaskSearchResult, TaskSearchResultBuilder> {
15-
Task get task;
15+
Task? get task;
1616
TaskSearchOrigin get origin;
1717
Activity? get activity;
1818
String? get comment;

lib/repository/data/latest_bookings_data_provider.dart

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,24 @@ class LatestBookingsDataProvider extends WorkDataProvider<void> {
3535
Stream<TaskSearchResult> search(config, String query) async* {
3636
final tasks = <Task>{};
3737

38+
query = query.toLowerCase();
39+
3840
yield* Stream.fromIterable(timeEntriesCubit.state).where(
3941
(timeEntry) {
40-
return timeEntry.comment.toLowerCase().contains(query) ||
41-
(timeEntry.task != null && timeEntry.task!.name.toLowerCase().contains(query));
42+
return timeEntry.comment.trim().toLowerCase().contains(query) ||
43+
(timeEntry.task != null && timeEntry.task!.name.trim().toLowerCase().contains(query));
4244
},
43-
).where((event) {
45+
).where((timeEntry) {
4446
// filter out tasks that are already in the list
45-
final contains = !tasks.contains(event.task);
46-
if (event.task != null) {
47-
tasks.add(event.task!);
47+
final contains = !tasks.contains(timeEntry.task);
48+
if (timeEntry.task != null) {
49+
tasks.add(timeEntry.task!);
4850
}
49-
return event.task == null || contains;
51+
return timeEntry.task == null || contains;
5052
}).map(
5153
(e) => TaskSearchResult(
5254
(b) => b
53-
..displayText = '${e.comment}${e.task != null ? ' - ' : ''}${e.task?.name}'
55+
..displayText = e.comment
5456
..origin = TaskSearchOrigin.latestBookings
5557
..activity = e.activity?.toBuilder()
5658
..comment = e.comment

lib/ui/erpnext_edit_page.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class _ErpNextEditPageState extends State<ErpNextEditPage> {
3030
appBar: AppBar(
3131
title: const Text('ERPNext'),
3232
centerTitle: true,
33+
backgroundColor: Theme.of(context).colorScheme.secondary,
34+
foregroundColor: Theme.of(context).colorScheme.onSecondary,
3335
),
3436
floatingActionButton: FloatingActionButton(
3537
child: const Icon(Icons.save),

lib/ui/redmine_edit_page.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class _RedmineEditPageState extends State<RedmineEditPage> {
3131
appBar: AppBar(
3232
title: const Text('Redmine'),
3333
centerTitle: true,
34+
backgroundColor: Theme.of(context).colorScheme.secondary,
35+
foregroundColor: Theme.of(context).colorScheme.onSecondary,
3436
),
3537
floatingActionButton: FloatingActionButton(
3638
child: const Icon(Icons.save),

0 commit comments

Comments
 (0)