Skip to content

Compass App: Activities screen, error handling and logs #2371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 2, 2024
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
4 changes: 4 additions & 0 deletions compass_app/app/lib/config/assets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Assets {
static const activities = 'assets/activities.json';
static const destinations = 'assets/destinations.json';
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:compass_model/model.dart';
import 'package:flutter/services.dart';

import '../../../config/assets.dart';
import '../../../utils/result.dart';
import 'activity_repository.dart';

Expand All @@ -25,7 +26,7 @@ class ActivityRepositoryLocal implements ActivityRepository {
}

Future<String> _loadAsset() async {
return await rootBundle.loadString('assets/activities.json');
return await rootBundle.loadString(Assets.activities);
}

List<Activity> _parse(String localData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'continent_repository.dart';
/// Local data source with all possible continents.
class ContinentRepositoryLocal implements ContinentRepository {
@override
Future<Result<List<Continent>>> getContinents() {
Future<Result<List<Continent>>> getContinents() async {
return Future.value(
Result.ok(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:compass_model/model.dart';
import 'package:flutter/services.dart' show rootBundle;

import '../../../config/assets.dart';
import '../../../utils/result.dart';
import 'destination_repository.dart';

Expand All @@ -22,7 +23,7 @@ class DestinationRepositoryLocal implements DestinationRepository {
}

Future<String> _loadAsset() async {
return await rootBundle.loadString('assets/destinations.json');
return await rootBundle.loadString(Assets.destinations);
}

List<Destination> _parse(String localData) {
Expand Down
8 changes: 8 additions & 0 deletions compass_app/app/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:flutter_localizations/flutter_localizations.dart';

import 'ui/core/localization/applocalization.dart';
import 'ui/core/themes/theme.dart';
import 'routing/router.dart';
import 'package:flutter/material.dart';
Expand All @@ -17,6 +20,11 @@ class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
AppLocalizationDelegate(),
],
scrollBehavior: AppCustomScrollBehavior(),
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
Expand Down
3 changes: 3 additions & 0 deletions compass_app/app/lib/main_development.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';

import 'config/dependencies.dart';
Expand All @@ -8,6 +9,8 @@ import 'main.dart';
/// Launch with `flutter run --target lib/main_development.dart`.
/// Uses local data.
void main() {
Logger.root.level = Level.ALL;

runApp(
MultiProvider(
providers: providersLocal,
Expand Down
3 changes: 3 additions & 0 deletions compass_app/app/lib/main_staging.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';

import 'config/dependencies.dart';
Expand All @@ -8,6 +9,8 @@ import 'main.dart';
/// Launch with `flutter run --target lib/main_staging.dart`.
/// Uses remote data from a server.
void main() {
Logger.root.level = Level.ALL;

runApp(
MultiProvider(
providers: providersRemote,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:compass_model/model.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';

import '../../../data/repositories/activity/activity_repository.dart';
import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
Expand All @@ -13,70 +14,108 @@ class ActivitiesViewModel extends ChangeNotifier {
}) : _activityRepository = activityRepository,
_itineraryConfigRepository = itineraryConfigRepository {
loadActivities = Command0(_loadActivities)..execute();
saveActivities = Command0(() async {
_log.shout(
'Save activities not implemented',
null,
StackTrace.current,
);
return Result.error(Exception('Not implemented'));
});
}

final _log = Logger('ActivitiesViewModel');
final ActivityRepository _activityRepository;
final ItineraryConfigRepository _itineraryConfigRepository;
List<Activity> _activities = <Activity>[];
List<Activity> _daytimeActivities = <Activity>[];
List<Activity> _eveningActivities = <Activity>[];
final Set<String> _selectedActivities = <String>{};

/// List of [Activity] per destination.
List<Activity> get activities => _activities;
/// List of daytime [Activity] per destination.
List<Activity> get daytimeActivities => _daytimeActivities;

/// List of evening [Activity] per destination.
List<Activity> get eveningActivities => _eveningActivities;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit / nonblocking comment:
I've recently learned about UnmodifiableListView, and I think this is the perfect place to use it :)

  /// List of daytime [Activity] per destination.
  UnmodifiableListView<Activity> get daytimeActivities => UnmodifiableListView(_daytimeActivities);

Copy link
Member Author

Choose a reason for hiding this comment

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

I will give it a try!


/// Selected [Activity] by ref.
Set<String> get selectedActivities => _selectedActivities;

/// Load list of [Activity] for a [Destination] by ref.
late final Command0 loadActivities;

Future<void> _loadActivities() async {
/// Save list [selectedActivities] into itinerary configuration.
late final Command0 saveActivities;

Future<Result<void>> _loadActivities() async {
final result = await _itineraryConfigRepository.getItineraryConfig();
if (result is Error) {
// TODO: Handle error
print(result.asError.error);
return;
_log.warning(
'Failed to load stored ItineraryConfig',
result.asError.error,
);
return result;
}

final destinationRef = result.asOk.value.destination;
if (destinationRef == null) {
// TODO: Error here
return;
_log.severe('Destination missing in ItineraryConfig');
return Result.error(Exception('Destination not found'));
}

final resultActivities =
await _activityRepository.getByDestination(destinationRef);
switch (resultActivities) {
case Ok():
{
_activities = resultActivities.value;
print(_activities);
_daytimeActivities = resultActivities.value
.where((activity) => [
TimeOfDay.any,
TimeOfDay.morning,
TimeOfDay.afternoon,
].contains(activity.timeOfDay))
.toList();

_eveningActivities = resultActivities.value
.where((activity) => [
TimeOfDay.evening,
TimeOfDay.night,
].contains(activity.timeOfDay))
.toList();

_log.fine('Activities (daytime: ${_daytimeActivities.length}, '
'evening: ${_eveningActivities.length}) loaded');
}
case Error():
{
// TODO: Handle error
print(resultActivities.error);
_log.warning('Failed to load activities', resultActivities.error);
}
}

notifyListeners();
return resultActivities;
}

/// Add [Activity] to selected list.
void addActivity(String activityRef) {
assert(
activities.any((activity) => activity.ref == activityRef),
(_daytimeActivities + _eveningActivities)
.any((activity) => activity.ref == activityRef),
"Activity $activityRef not found",
);
_selectedActivities.add(activityRef);
_log.finest('Activity $activityRef added');
notifyListeners();
}

/// Remove [Activity] from selected list.
void removeActivity(String activityRef) {
assert(
activities.any((activity) => activity.ref == activityRef),
(_daytimeActivities + _eveningActivities)
.any((activity) => activity.ref == activityRef),
"Activity $activityRef not found",
);
_selectedActivities.remove(activityRef);
_log.finest('Activity $activityRef removed');
notifyListeners();
}
}
39 changes: 39 additions & 0 deletions compass_app/app/lib/ui/activities/widgets/activities_header.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import '../../core/localization/applocalization.dart';
import '../../core/themes/dimens.dart';
import '../../core/ui/back_button.dart';
import '../../core/ui/home_button.dart';

class ActivitiesHeader extends StatelessWidget {
const ActivitiesHeader({super.key});

@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: Dimens.of(context).paddingScreenHorizontal,
right: Dimens.of(context).paddingScreenHorizontal,
top: Dimens.of(context).paddingScreenVertical,
bottom: Dimens.paddingVertical,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomBackButton(
onTap: () {
// Navigate to ResultsScreen and edit search
context.go('/results');
},
),
Text(
AppLocalization.of(context).activities,
style: Theme.of(context).textTheme.titleLarge,
),
const HomeButton(),
],
),
);
}
}
57 changes: 57 additions & 0 deletions compass_app/app/lib/ui/activities/widgets/activities_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';

import '../../core/themes/dimens.dart';
import '../view_models/activities_viewmodel.dart';
import 'activity_entry.dart';
import 'activity_time_of_day.dart';

class ActivitiesList extends StatelessWidget {
const ActivitiesList({
super.key,
required this.viewModel,
required this.activityTimeOfDay,
});

final ActivitiesViewModel viewModel;
final ActivityTimeOfDay activityTimeOfDay;

@override
Widget build(BuildContext context) {
final list = switch (activityTimeOfDay) {
ActivityTimeOfDay.daytime => viewModel.daytimeActivities,
ActivityTimeOfDay.evening => viewModel.eveningActivities,
};
return SliverPadding(
padding: EdgeInsets.only(
top: Dimens.paddingVertical,
left: Dimens.of(context).paddingScreenHorizontal,
right: Dimens.of(context).paddingScreenHorizontal,
bottom: Dimens.paddingVertical,
),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final activity = list[index];
return Padding(
padding:
EdgeInsets.only(bottom: index < list.length - 1 ? 20 : 0),
child: ActivityEntry(
key: ValueKey(activity.ref),
activity: activity,
selected: viewModel.selectedActivities.contains(activity.ref),
onChanged: (value) {
if (value!) {
viewModel.addActivity(activity.ref);
} else {
viewModel.removeActivity(activity.ref);
}
},
),
);
},
childCount: list.length,
),
),
);
}
}
Loading