Skip to content
Open
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 .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: flutter pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed lib/** test/**
run: dart format --line-length 120 --output=none --set-exit-if-changed lib/** test/**

- name: Analyze project source
run: flutter analyze --no-fatal-infos --no-fatal-warnings lib/ test/
Expand Down
3 changes: 3 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

include: package:lints/recommended.yaml

formatter:
page_width: 120

analyzer:
exclude: [build/**]
language:
Expand Down
87 changes: 28 additions & 59 deletions lib/blocs/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ class StudyAppBLoC extends ChangeNotifier {
final CarpBackend _backend = CarpBackend();
final CarpStudyAppViewModel _appViewModel = CarpStudyAppViewModel();
List<Message> _messages = [];
final StreamController<int> _messageStreamController =
StreamController.broadcast();
final StreamController<int> _messageStreamController = StreamController.broadcast();

/// The state of this BloC.
StudyAppState get state => _state;
Expand Down Expand Up @@ -86,43 +85,34 @@ class StudyAppBLoC extends ChangeNotifier {

/// Create the BLoC for the app.
StudyAppBLoC() : super() {
const dep =
String.fromEnvironment('deployment-mode', defaultValue: 'production');
deploymentMode =
DeploymentMode.values.where((element) => element.name == dep).first;
const dep = String.fromEnvironment('deployment-mode', defaultValue: 'production');
deploymentMode = DeploymentMode.values.where((element) => element.name == dep).first;

const deb = String.fromEnvironment('debug-level', defaultValue: 'info');
debugLevel =
DebugLevel.values.where((element) => element.name == deb).first;
debugLevel = DebugLevel.values.where((element) => element.name == deb).first;

info('$runtimeType created. '
'DeploymentMode: ${deploymentMode.name}, '
'DebugLevel: ${debugLevel.name}');
}

LocalizationManager get localizationManager =>
(deploymentMode == DeploymentMode.local
? LocalResourceManager()
: CarpResourceManager()) as LocalizationManager;
(deploymentMode == DeploymentMode.local ? LocalResourceManager() : CarpResourceManager()) as LocalizationManager;

LocalizationLoader get localizationLoader {
debug('$runtimeType - using localizationManager: $localizationManager');
return ResourceLocalizationLoader(localizationManager);
}

MessageManager get messageManager => (deploymentMode == DeploymentMode.local
? LocalResourceManager()
: CarpResourceManager()) as MessageManager;
MessageManager get messageManager =>
(deploymentMode == DeploymentMode.local ? LocalResourceManager() : CarpResourceManager()) as MessageManager;

InformedConsentManager get informedConsentManager =>
(bloc.deploymentMode == DeploymentMode.local
? LocalResourceManager()
: CarpResourceManager()) as InformedConsentManager;
(bloc.deploymentMode == DeploymentMode.local ? LocalResourceManager() : CarpResourceManager())
as InformedConsentManager;

ParticipationService get participationService =>
(bloc.deploymentMode == DeploymentMode.local
? LocalParticipationService()
: CarpParticipationService());
(bloc.deploymentMode == DeploymentMode.local ? LocalParticipationService() : CarpParticipationService());

CarpBackend get backend => _backend;

Expand All @@ -138,13 +128,11 @@ class StudyAppBLoC extends ChangeNotifier {
/// The deployment running on this phone.
SmartphoneDeployment? get deployment => Sensing().controller?.deployment;

Set<ExpectedParticipantData?> get expectedParticipantData =>
deployment?.expectedParticipantData ?? {};
Set<ExpectedParticipantData?> get expectedParticipantData => deployment?.expectedParticipantData ?? {};

/// Get the status for the current study deployment.
/// Returns null if the study is not yet deployed on this phone.
Future<StudyDeploymentStatus?> get studyDeploymentStatus async =>
await Sensing().getStudyDeploymentStatus();
Future<StudyDeploymentStatus?> get studyDeploymentStatus async => await Sensing().getStudyDeploymentStatus();

/// When was this study deployed on this phone.
DateTime? get studyStartTimestamp => deployment?.deployed;
Expand Down Expand Up @@ -185,12 +173,9 @@ class StudyAppBLoC extends ChangeNotifier {

/// Is the phone connected to the internet either via wifi or mobile network?
Future<bool> checkConnectivity() async {
final List<ConnectivityResult> results =
await (Connectivity().checkConnectivity());
final List<ConnectivityResult> results = await (Connectivity().checkConnectivity());

return results.any((element) =>
element == ConnectivityResult.mobile ||
element == ConnectivityResult.wifi);
return results.any((element) => element == ConnectivityResult.mobile || element == ConnectivityResult.wifi);
}

/// Check if the Health database is installed on this phone.
Expand All @@ -202,8 +187,7 @@ class StudyAppBLoC extends ChangeNotifier {

try {
final apps = await appCheck.getInstalledApps() ?? [];
return apps.any(
(app) => app.packageName == LocalSettings.healthConnectPackageName);
return apps.any((app) => app.packageName == LocalSettings.healthConnectPackageName);
} catch (e) {
debug("$runtimeType - Error checking Health Connect installation: $e");
return false;
Expand Down Expand Up @@ -243,8 +227,7 @@ class StudyAppBLoC extends ChangeNotifier {
var protocol = await LocalResourceManager().getStudyProtocol('');

// Deploy this protocol using the on-phone deployment service.
final status =
await SmartphoneDeploymentService().createStudyDeployment(protocol!);
final status = await SmartphoneDeploymentService().createStudyDeployment(protocol!);

// Save the participant and study on the phone for use across app restart.
var participant = Participant(
Expand Down Expand Up @@ -326,10 +309,7 @@ class StudyAppBLoC extends ChangeNotifier {
}

Future<List<ParticipantData>> getParticipantDataListFromDeployment() async =>
(deployment == null)
? []
: await participationService
.getParticipantDataList([deployment!.studyDeploymentId]);
(deployment == null) ? [] : await participationService.getParticipantDataList([deployment!.studyDeploymentId]);

/// Set the participant data for this study.
void setParticipantData(
Expand All @@ -352,9 +332,7 @@ class StudyAppBLoC extends ChangeNotifier {

/// Has the informed consent been accepted by the user?
bool get hasInformedConsentBeenAccepted =>
backend.getInformedConsentByRole(
study!.studyDeploymentId, study!.participantRoleName) !=
null;
backend.getInformedConsentByRole(study!.studyDeploymentId, study!.participantRoleName) != null;

set hasInformedConsentBeenAccepted(bool accepted) {
var participant = LocalSettings().participant;
Expand Down Expand Up @@ -406,11 +384,10 @@ class StudyAppBLoC extends ChangeNotifier {
/// Does this [deployment] have any measures?
bool hasMeasures() => (deployment == null)
? false
: (deployment!.measures.any((measure) =>
(measure.type != SurveyUserTask.VIDEO_TYPE &&
measure.type != SurveyUserTask.IMAGE_TYPE &&
measure.type != SurveyUserTask.AUDIO_TYPE &&
measure.type != SurveyUserTask.SURVEY_TYPE)));
: (deployment!.measures.any((measure) => (measure.type != SurveyUserTask.VIDEO_TYPE &&
measure.type != SurveyUserTask.IMAGE_TYPE &&
measure.type != SurveyUserTask.AUDIO_TYPE &&
measure.type != SurveyUserTask.SURVEY_TYPE)));

/// Does this [deployment] have the measure of type [type]?
bool hasMeasure(String type) {
Expand All @@ -426,21 +403,16 @@ class StudyAppBLoC extends ChangeNotifier {
}

/// Does this [deployment] have any user tasks?
bool hasUserTasks() => (deployment == null)
? false
: deployment!.tasks.whereType<AppTask>().isNotEmpty;
bool hasUserTasks() => (deployment == null) ? false : deployment!.tasks.whereType<AppTask>().isNotEmpty;

/// Does this [deployment] have any connected devices?
bool hasDevices() =>
(deployment == null) ? false : deployment!.connectedDevices.isNotEmpty;
bool hasDevices() => (deployment == null) ? false : deployment!.connectedDevices.isNotEmpty;

/// Is sensing running, i.e. has the study executor been resumed?
bool get isRunning => Sensing().isRunning;

/// the list of running - i.e. used - probes in this study.
List<Probe> get runningProbes => (Sensing().controller != null)
? Sensing().controller!.executor.probes
: [];
List<Probe> get runningProbes => (Sensing().controller != null) ? Sensing().controller!.executor.probes : [];

DeploymentService get deploymentService => Sensing().deploymentService;

Expand All @@ -450,8 +422,7 @@ class StudyAppBLoC extends ChangeNotifier {

/// Start sensing.
Future<void> start() async {
assert(Sensing().controller != null,
'No Study Controller - the study has not been deployed.');
assert(Sensing().controller != null, 'No Study Controller - the study has not been deployed.');
if (!Sensing().isRunning) Sensing().controller?.start();
}

Expand All @@ -466,12 +437,10 @@ class StudyAppBLoC extends ChangeNotifier {
}

/// Add [measurement] to the stream of collected measurements.
void addMeasurement(Measurement measurement) =>
Sensing().controller?.executor.addMeasurement(measurement);
void addMeasurement(Measurement measurement) => Sensing().controller?.executor.addMeasurement(measurement);

/// Add [error] to the stream of measurements.
void addError(Object error, [StackTrace? stacktrace]) =>
Sensing().controller?.executor.addError(error, stacktrace);
void addError(Object error, [StackTrace? stacktrace]) => Sensing().controller?.executor.addError(error, stacktrace);

/// Leave the study deployed on this phone.
///
Expand Down
40 changes: 12 additions & 28 deletions lib/blocs/sensing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ class Sensing {

/// The deployment service used in this app.
DeploymentService get deploymentService =>
bloc.deploymentMode == DeploymentMode.local
? SmartphoneDeploymentService()
: CarpDeploymentService();
bloc.deploymentMode == DeploymentMode.local ? SmartphoneDeploymentService() : CarpDeploymentService();

/// The study running on this phone.
/// Only available after [addStudy] is called.
Expand All @@ -51,13 +49,10 @@ class Sensing {
SmartphoneDeploymentController? get controller => _controller;

/// Is sensing running, i.e. has the study executor been started?
bool get isRunning =>
(controller != null) &&
controller!.executor.state == ExecutorState.started;
bool get isRunning => (controller != null) && controller!.executor.state == ExecutorState.started;

/// The list of running - i.e. used - probes in this study.
List<Probe> get runningProbes =>
(_controller != null) ? _controller!.executor.probes : [];
List<Probe> get runningProbes => (_controller != null) ? _controller!.executor.probes : [];

/// The list of all device managers used in the current deployment.
///
Expand All @@ -69,8 +64,7 @@ class Sensing {
.deviceController
.devices
.values
.where((manager) => deployment!.devices
.any((element) => element.type == manager.type))
.where((manager) => deployment!.devices.any((element) => element.type == manager.type))
.toList()
: [];

Expand All @@ -79,8 +73,7 @@ class Sensing {
SmartPhoneClientManager().deviceController.smartphoneDeviceManager;

/// The list of connected devices.
List<DeviceManager>? get connectedDevices =>
SmartPhoneClientManager().deviceController.connectedDevices;
List<DeviceManager>? get connectedDevices => SmartPhoneClientManager().deviceController.connectedDevices;

/// The singleton sensing instance
factory Sensing() => _instance;
Expand Down Expand Up @@ -131,13 +124,11 @@ class Sensing {
Future<StudyStatus> addStudy() async {
assert(SmartPhoneClientManager().isConfigured,
'The client manager is not yet configured. Call SmartPhoneClientManager().configure() before adding a study.');
assert(bloc.study != null,
'No study is provided. Cannot start deployment w/o a study.');
assert(bloc.study != null, 'No study is provided. Cannot start deployment w/o a study.');

// Add the study to the client.
_study = await SmartPhoneClientManager().addStudy(bloc.study!);
_controller =
SmartPhoneClientManager().getStudyRuntime(study!.studyDeploymentId);
_controller = SmartPhoneClientManager().getStudyRuntime(study!.studyDeploymentId);

// Get the study controller and try to deploy the study.
return await tryDeployment();
Expand All @@ -150,8 +141,7 @@ class Sensing {
/// If not deployed before (i.e., cached) the study deployment will be
/// fetched from the deployment service.
Future<StudyStatus> tryDeployment() async {
assert(controller != null,
'No study or controller is provided. Cannot start deployment w/o a study.');
assert(controller != null, 'No study or controller is provided. Cannot start deployment w/o a study.');

StudyStatus status = await controller!.tryDeployment(useCached: true);

Expand All @@ -163,8 +153,7 @@ class Sensing {
await controller?.configure();

// Listening on the data stream and print them as json to the debug console
controller?.measurements
.listen((measurement) => debugPrint(toJsonString(measurement)));
controller?.measurements.listen((measurement) => debugPrint(toJsonString(measurement)));

info('$runtimeType - Study added, deployment id: $studyDeploymentId');
return status;
Expand All @@ -184,10 +173,7 @@ class Sensing {
/// Get the status for the current study deployment.
/// Returns null if the study is not yet deployed on this phone.
Future<StudyDeploymentStatus?> getStudyDeploymentStatus() async =>
studyDeploymentId != null
? _status = await deploymentService
.getStudyDeploymentStatus(studyDeploymentId!)
: null;
studyDeploymentId != null ? _status = await deploymentService.getStudyDeploymentStatus(studyDeploymentId!) : null;

/// Translate the title and description of all AppTask in the study protocol
/// of the current master deployment.
Expand All @@ -198,8 +184,7 @@ class Sensing {
if (bloc.localization == null) return;

// Fast out, if not configured or no protocol
if (controller?.status != StudyStatus.Deployed ||
controller?.deployment == null) {
if (controller?.status != StudyStatus.Deployed || controller?.deployment == null) {
return;
}

Expand All @@ -210,7 +195,6 @@ class Sensing {
}
}

info(
"$runtimeType - Study protocol translated to locale '${bloc.localization!.locale}'");
info("$runtimeType - Study protocol translated to locale '${bloc.localization!.locale}'");
}
}
3 changes: 1 addition & 2 deletions lib/blocs/util.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
part of carp_study_app;

extension StringExtension on String {
String truncateTo(int maxLength) =>
(length <= maxLength) ? this : '${substring(0, maxLength)}...';
String truncateTo(int maxLength) => (length <= maxLength) ? this : '${substring(0, maxLength)}...';
}

extension Humanize on Duration {
Expand Down
Loading
Loading