Skip to content

Commit

Permalink
feat: much faster UI updates on habit completion and new checklist item
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasn committed Jan 4, 2025
1 parent 4cc5327 commit 7e079a3
Show file tree
Hide file tree
Showing 13 changed files with 60 additions and 42 deletions.
42 changes: 24 additions & 18 deletions lib/blocs/habits/habits_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:lotti/classes/entity_definitions.dart';
import 'package:lotti/classes/journal_entities.dart';
import 'package:lotti/database/database.dart';
import 'package:lotti/get_it.dart';
import 'package:lotti/services/db_notification.dart';
import 'package:lotti/utils/date_utils_extension.dart';
import 'package:lotti/utils/platform.dart';
import 'package:lotti/widgets/charts/utils.dart';
Expand Down Expand Up @@ -76,26 +77,34 @@ class HabitsCubit extends Cubit<HabitsState> {
emitState();
}

void startWatching() {
_completionsStream = _journalDb
.watchHabitCompletionsInRange(
rangeStart:
DateTime.now().dayAtMidnight.subtract(const Duration(days: 180)),
)
Future<void> startWatching() async {
await fetchHabitCompletions();
final subscribedIds = <String>{habitCompletionNotification};

_updateSubscription = getIt<UpdateNotifications>()
.updateStream
.throttleTime(
const Duration(seconds: 5),
const Duration(milliseconds: 200),
leading: false,
trailing: true,
leading: true,
);

_completionsSubscription = _completionsStream.listen((habitCompletions) {
_habitCompletions = habitCompletions;
if (_isVisible) {
)
.listen((affectedIds) async {
if (affectedIds.intersection(subscribedIds).isNotEmpty) {
await fetchHabitCompletions();
determineHabitSuccessByDays();
}
});
}

Future<void> fetchHabitCompletions() async {
final rangeStart = DateTime.now().dayAtMidnight.subtract(
const Duration(days: 180),
);
_habitCompletions = await _journalDb.getHabitCompletionsInRange(
rangeStart: rangeStart,
);
}

void determineHabitSuccessByDays() {
_completedToday = <String>{};
_successfulToday = <String>{};
Expand Down Expand Up @@ -210,7 +219,6 @@ class HabitsCubit extends Cubit<HabitsState> {

_shortStreakCount = shortStreakCount;
_longStreakCount = longStreakCount;

emitState();
}

Expand Down Expand Up @@ -309,9 +317,7 @@ class HabitsCubit extends Cubit<HabitsState> {

late final Stream<List<HabitDefinition>> _definitionsStream;
late final StreamSubscription<List<HabitDefinition>> _definitionsSubscription;

late Stream<List<JournalEntity>> _completionsStream;
late final StreamSubscription<List<JournalEntity>> _completionsSubscription;
late final StreamSubscription<Set<String>>? _updateSubscription;

void emitState() {
emit(
Expand Down Expand Up @@ -372,7 +378,7 @@ class HabitsCubit extends Cubit<HabitsState> {
@override
Future<void> close() async {
await _definitionsSubscription.cancel();
await _completionsSubscription.cancel();
await _updateSubscription?.cancel();
await super.close();
}
}
Expand Down
13 changes: 12 additions & 1 deletion lib/classes/journal_entities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:lotti/classes/geolocation.dart';
import 'package:lotti/classes/health.dart';
import 'package:lotti/classes/task.dart';
import 'package:lotti/features/sync/vector_clock.dart';
import 'package:lotti/services/db_notification.dart';
import 'package:research_package/model.dart';

part 'journal_entities.freezed.dart';
Expand Down Expand Up @@ -199,7 +200,9 @@ extension JournalEntityExtension on JournalEntity {

if (this is HabitCompletionEntry) {
final habitCompletion = this as HabitCompletionEntry;
ids.add(habitCompletion.data.habitId);
ids
..add(habitCompletion.data.habitId)
..add(habitCompletionNotification);
}

if (this is Checklist) {
Expand All @@ -224,6 +227,14 @@ extension JournalEntityExtension on JournalEntity {
ids.add(checklistItem.data.dataType);
}

if (this is Task) {
ids.add(taskNotification);
}

if (this is JournalEntry) {
ids.add(textEntryNotification);
}

return ids;
}
}
7 changes: 4 additions & 3 deletions lib/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -630,10 +630,11 @@ class JournalDb extends _$JournalDb {
return res.map(fromDbEntity).toList();
}

Stream<List<JournalEntity>> watchHabitCompletionsInRange({
Future<List<JournalEntity>> getHabitCompletionsInRange({
required DateTime rangeStart,
}) {
return habitCompletionsInRange(rangeStart).watch().map(entityStreamMapper);
}) async {
final res = await habitCompletionsInRange(rangeStart).get();
return res.map(fromDbEntity).toList();
}

Future<List<JournalEntity>> getQuantitativeByType({
Expand Down
6 changes: 4 additions & 2 deletions lib/features/calendar/state/day_view_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ class DayViewController extends _$DayViewController {
bool _isVisible = false;

void listen() {
final subscribedIds = <String>{textEntryNotification};

_updateSubscription = getIt<UpdateNotifications>()
.updateStream
.throttleTime(
const Duration(seconds: 5),
leading: false,
trailing: true,
)
.listen((_) async {
if (_isVisible) {
.listen((affectedIds) async {
if (affectedIds.intersection(subscribedIds).isNotEmpty && _isVisible) {
final timeSpanDays = ref.read(timeFrameControllerProvider);
final latest = await _fetch(timeSpanDays: timeSpanDays);
state = AsyncData(latest);
Expand Down
2 changes: 1 addition & 1 deletion lib/features/calendar/state/day_view_controller.g.dart

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

6 changes: 4 additions & 2 deletions lib/features/calendar/state/time_by_category_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ class TimeByCategoryController extends _$TimeByCategoryController {
bool _isVisible = false;

void listen() {
final subscribedIds = <String>{textEntryNotification};

_updateSubscription = getIt<UpdateNotifications>()
.updateStream
.throttleTime(
const Duration(seconds: 5),
leading: false,
trailing: true,
)
.listen((_) async {
if (_isVisible) {
.listen((affectedIds) async {
if (affectedIds.intersection(subscribedIds).isNotEmpty && _isVisible) {
final timeSpanDays = ref.read(timeFrameControllerProvider);
final latest = await _fetch(timeSpanDays);
state = AsyncData(latest);
Expand Down

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

2 changes: 1 addition & 1 deletion lib/features/tasks/state/tasks_count_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ part 'tasks_count_controller.g.dart';

@Riverpod(keepAlive: true)
class TasksCountController extends _$TasksCountController {
final subscribedIds = <String>{'TASK'};
final subscribedIds = <String>{taskNotification};
StreamSubscription<Set<String>>? _updateSubscription;

void listen() {
Expand Down
2 changes: 1 addition & 1 deletion lib/features/tasks/state/tasks_count_controller.g.dart

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

4 changes: 0 additions & 4 deletions lib/logic/persistence_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -689,10 +689,6 @@ class PersistenceLogic {

await getIt<NotificationService>().updateBadge();

if (journalEntity is Task) {
_updateNotifications.notify({'TASK'});
}

return true;
} catch (exception, stackTrace) {
_loggingService.captureException(
Expand Down
4 changes: 4 additions & 0 deletions lib/services/db_notification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ class UpdateNotifications {
}
}
}

const habitCompletionNotification = 'HABIT_COMPLETION';
const textEntryNotification = 'TEXT_ENTRY';
const taskNotification = 'TASK';
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: lotti
description: Achieve your goals and keep your data private with Lotti.
publish_to: 'none'
version: 0.9.552+2810
version: 0.9.552+2811

msix_config:
display_name: LottiApp
Expand Down
10 changes: 3 additions & 7 deletions test/pages/habits/habits_tab_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:lotti/blocs/habits/habits_cubit.dart';
import 'package:lotti/blocs/habits/habits_state.dart';
import 'package:lotti/classes/entity_definitions.dart';
import 'package:lotti/classes/journal_entities.dart';
import 'package:lotti/database/database.dart';
import 'package:lotti/features/habits/ui/habits_page.dart';
import 'package:lotti/features/user_activity/state/user_activity_service.dart';
Expand Down Expand Up @@ -77,15 +76,12 @@ void main() {
).thenAnswer((_) async => []);

when(
() => mockJournalDb.watchHabitCompletionsInRange(
() => mockJournalDb.getHabitCompletionsInRange(
rangeStart: any(named: 'rangeStart'),
),
).thenAnswer(
(_) => Stream<List<JournalEntity>>.fromIterable([
[testHabitCompletionEntry],
]),
);
).thenAnswer((_) async => [testHabitCompletionEntry]);
});

tearDown(getIt.reset);

testWidgets('habits page is rendered', (tester) async {
Expand Down

0 comments on commit 7e079a3

Please sign in to comment.