Skip to content

Commit

Permalink
Fix of Device and Data Viz pages (#210)
Browse files Browse the repository at this point in the history
Co-authored-by: Lars Refsgaard <[email protected]>

Fixes #204, #186, #207 

Added support for health in devices page
  • Loading branch information
bardram authored Jan 26, 2024
1 parent 462ca0d commit af38871
Show file tree
Hide file tree
Showing 24 changed files with 229 additions and 154 deletions.
55 changes: 55 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- Check whether Health Connect is installed or not -->
<queries>
<package android:name="com.google.android.apps.healthdata" />
<intent>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent>
</queries>

<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
Expand Down Expand Up @@ -49,6 +57,48 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />

<!-- Permissions for Health Connect API -->
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.WRITE_STEPS"/>
<uses-permission android:name="android.permission.health.READ_WEIGHT"/>
<uses-permission android:name="android.permission.health.WRITE_WEIGHT"/>
<uses-permission android:name="android.permission.health.READ_HEIGHT"/>
<uses-permission android:name="android.permission.health.WRITE_HEIGHT"/>
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_SLEEP"/>
<uses-permission android:name="android.permission.health.WRITE_SLEEP"/>
<uses-permission android:name="android.permission.health.READ_SPEED"/>
<uses-permission android:name="android.permission.health.WRITE_SPEED"/>
<uses-permission android:name="android.permission.health.READ_DISTANCE"/>
<uses-permission android:name="android.permission.health.WRITE_DISTANCE"/>
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE"/>
<uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE"/>
<uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE"/>
<uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE"/>
<uses-permission android:name="android.permission.health.READ_BODY_FAT"/>
<uses-permission android:name="android.permission.health.WRITE_BODY_FAT"/>
<uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE"/>
<uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE"/>
<uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION"/>
<uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION"/>
<uses-permission android:name="android.permission.health.READ_HYDRATION"/>
<uses-permission android:name="android.permission.health.WRITE_HYDRATION"/>
<uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED"/>
<uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED"/>
<uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE"/>
<uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE"/>
<uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE"/>

<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
Expand Down Expand Up @@ -125,6 +175,11 @@

<data android:scheme="carp-studies" />
</intent-filter>

<!-- Intention to show Permissions screen for Health Connect API -->
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>

<!-- service for using the Android activity recognition API
Expand Down
3 changes: 2 additions & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.useAndroidX=true
android.enableR8=true
2 changes: 2 additions & 0 deletions assets/lang/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@
"pages.devices.type.weather.name": "Vejr",
"pages.devices.type.air_quality.name": "Luftkvalitet",
"pages.devices.type.location.name": "Lokalitet",
"pages.devices.type.health.name": "Sundhed",
"pages.devices.type.smartphone.description": "Denne telefon",
"pages.devices.type.esense.description": "eSense ear plug",
"pages.devices.type.polar.description": "Polar hjertemonitor",
"pages.devices.type.weather.description": "Vejrtjeneste",
"pages.devices.type.air_quality.description": "Luftkvalitetstjeneste",
"pages.devices.type.location.description": "Lokalitetstjeneste",
"pages.devices.type.health.description": "Sundhedstjeneste",
"pages.devices.type.smartphone.instructions": "Denne telefon ",
"pages.devices.type.esense.instructions": "Du skal forbinde den VENSTRE øreprop til dette studie. Tænd øreprobben ved at trykke knappen ned indtil den blinker rød. Nu skulle du kunne vælge den venstre øreprop i listen.\n\nHvis du vil anvende eSense ørepropperne til lyd og telefoni, skal du forbinde den HØJRE øreprop til telefonen. Dette gøres gennem telefonens Bluetooth INDSTILLINGER. For at parre ørepropperne til telefonen skal du trykke knappen på den HØJRE øreprop ned indtil den begyder at blinke rød og blå. Så er ørepropperne i parringstilstand. Når ørepropperne er forbundet til telefonen, vil de blinke blå.",
"pages.devices.type.polar.instructions": "Sørg for at din Polar enhed er tændt. Polar H10 pulsmåleren tændes automatisk når du tager den på. Polar Verity Sense armbåndet tændes ved at trykke den blanke knap på siden ind indtil LED dioderne lyser.\n\nFor a parre Polar enheden med telefonen skal du åbne instillingerne på telefonen og trykke på Polar enheden.",
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@
"pages.devices.type.weather.name": "Weather",
"pages.devices.type.air_quality.name": "Air quality",
"pages.devices.type.location.name": "Location",
"pages.devices.type.health.name": "Health",
"pages.devices.type.smartphone.description": "This phone",
"pages.devices.type.esense.description": "eSense ear plug",
"pages.devices.type.polar.description": "Polar heart rate monitor",
"pages.devices.type.weather.description": "Weather service",
"pages.devices.type.air_quality.description": "Air quality service",
"pages.devices.type.location.description": "Location service",
"pages.devices.type.health.description": "Health service",
"pages.devices.type.smartphone.instructions": "This phone is already connected to the study",
"pages.devices.type.esense.instructions": "You should connect the LEFT earbud to this study. Turn on the left earbud by pressing and holding the push button until it blinks red. Now you should be able to select the left earbud in the list of devices.\n\nIf you want to use the eSense earbuds for audio streaming, you should connect the RIGT earbud to the phone using the phone's Bluetooth SETTINGS. To pair an earbud to the phone, press and hold the button until it starts blinking blue and red. Now the earbud is in paring mode. Once the earbud is connected to the phone, it will indicate so by blinking in blue.",
"pages.devices.type.polar.instructions": "Make sure that your Polar device is turned on. The Polar H10 chest strap is automatically turned on when it is attached to your chest. The Polar Verity Sense arm strap needs to be turned on by pressing the button on the side until the LEDs turns on.\n\nTo pair the Polar device with the phone, open the Bluetooth settings of the phone and click on the Polar device.",
Expand Down
4 changes: 2 additions & 2 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@
<key>NSContactsUsageDescription</key>
<string>CARP uses contacts</string>
<key>NSHealthShareUsageDescription</key>
<string>CARP collects data from the Apple Health app to give you better health insights</string>
<string>CARP collects data from the Apple Health database to give you better health insights</string>
<key>NSHealthUpdateUsageDescription</key>
<string>CARP collects data from the Apple Health app to give you better health insights</string>
<string>CARP collects data from the Apple Health database to give you better health insights</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>CARP uses the location API to extract overall mobility patterns</string>
<key>NSLocationAlwaysUsageDescription</key>
Expand Down
6 changes: 5 additions & 1 deletion ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<key>com.apple.developer.healthkit</key>
<true/>
<key>com.apple.developer.healthkit.access</key>
<array/>
<array>
<string>health-records</string>
</array>
<key>com.apple.developer.healthkit.background-delivery</key>
<true/>
</dict>
</plist>
6 changes: 5 additions & 1 deletion ios/Runner/RunnerRelease.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<key>com.apple.developer.healthkit</key>
<true/>
<key>com.apple.developer.healthkit.access</key>
<array/>
<array>
<string>health-records</string>
</array>
<key>com.apple.developer.healthkit.background-delivery</key>
<true/>
</dict>
</plist>
14 changes: 8 additions & 6 deletions lib/blocs/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ enum StudyAppState {
class StudyAppBLoC {
StudyAppState _state = StudyAppState.created;
final CarpBackend _backend = CarpBackend();
final CarpStudyAppViewModel _data = CarpStudyAppViewModel();
final CarpStudyAppViewModel _appViewModel = CarpStudyAppViewModel();
StudyDeploymentStatus? _status;
final StreamController<StudiesAppState> _stateStream =
StreamController.broadcast();
Expand Down Expand Up @@ -102,8 +102,8 @@ class StudyAppBLoC {
DateTime? get studyStartTimestamp => deployment?.deployed;

/// The overall data model for this app
CarpStudyAppViewModel get data => _data;
Future<void>? dataPageInitialization;
CarpStudyAppViewModel get appViewModel => _appViewModel;
// Future<void>? dataPageInitialization;

/// Initialize this BLOC. Called before being used for anything.
Future<void> initialize() async {
Expand Down Expand Up @@ -149,7 +149,6 @@ class StudyAppBLoC {
if (isConfiguring) return;

stateStream.sink.add(StudiesAppState.configuring);
info('$runtimeType configuring...');

// set up and initialize sensing
await Sensing().initialize();
Expand All @@ -158,7 +157,9 @@ class StudyAppBLoC {
await Sensing().addStudy();

// initialize the UI data models
dataPageInitialization = data.init(Sensing().controller!);
appViewModel.init(Sensing().controller!);

debug('$runtimeType - done init() of view model');

// set up the messaging part
messageManager.initialize().then(
Expand Down Expand Up @@ -241,7 +242,8 @@ class StudyAppBLoC {
? user!.username
: Sensing().controller!.deployment!.userId!;

/// The name used for friendly greeting - '' if no user logged in.
/// The name used for friendly greeting.
/// Returns an empty string if no user logged in.
String? get friendlyUsername => (user != null) ? user!.firstName : '';

/// Does this [deployment] have any measures?
Expand Down
19 changes: 13 additions & 6 deletions lib/blocs/common.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
part of '../main.dart';

/// Enumeration of different types of deployments on the CARP Web Service (CAWS).
enum DeploymentMode {
/// Use the CARP production server to get the study deployment and store data.
production,
Expand All @@ -14,20 +15,26 @@ enum DeploymentMode {
dev,
}

/// Enumeration of of different user authentication states.
enum LoginStatus {
/// No invitation selected (tap outside the invitation box) - Navigate to login screen
/// No invitation selected (tap outside the invitation box).
/// Navigate to login screen.
noSelection,

/// Informed Consent not accepted - Navigate to message screen / login
/// Informed Consent not accepted.
/// Navigate to message screen or login.
noConsent,

/// User registered but no current ongoing studies - Navigate to message screen
/// User registered but no current ongoing studies.
/// Navigate to message screen.
noInvitation,

/// User temporary blocked for introducing the login credentials wrongly 3 times - Navigate to message screen
/// User temporary blocked based on 3 wrongly login credentials.
/// Navigate to message screen.
temporaryBlock,

/// Successful login - Navigate to home page
/// Successful login.
/// Navigate to home page.
successful,
}

Expand All @@ -37,9 +44,9 @@ enum ProcessStatus {
other,
}

/// Enumeration of different app states.
enum StudiesAppState {
initialized,
loginpage,
authenticating,
accessTokenRetrieved,
configuring,
Expand Down
12 changes: 6 additions & 6 deletions lib/carp_study_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) => CustomTransitionPage(
child: TaskListPage(
bloc.data.taskListPageViewModel,
bloc.appViewModel.taskListPageViewModel,
),
transitionsBuilder: bottomNavigationBarAnimation,
),
Expand All @@ -58,7 +58,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) => CustomTransitionPage(
child: StudyPage(
bloc.data.studyPageViewModel,
bloc.appViewModel.studyPageViewModel,
),
transitionsBuilder: bottomNavigationBarAnimation,
),
Expand All @@ -68,7 +68,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) => CustomTransitionPage(
child: DataVisualizationPage(
bloc.data.dataVisualizationPageViewModel),
bloc.appViewModel.dataVisualizationPageViewModel),
transitionsBuilder: bottomNavigationBarAnimation,
),
),
Expand All @@ -84,7 +84,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
path: '/profile',
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) => CustomTransitionPage(
child: ProfilePage(bloc.data.profilePageViewModel),
child: ProfilePage(bloc.appViewModel.profilePageViewModel),
transitionsBuilder: bottomNavigationBarAnimation,
),
),
Expand All @@ -111,7 +111,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
? firstRoute
: (bloc.studyId == null ? '/invitations' : null),
builder: (context, state) => InformedConsentPage(
bloc.data.informedConsentViewModel,
bloc.appViewModel.informedConsentViewModel,
),
),
GoRoute(
Expand Down Expand Up @@ -139,7 +139,7 @@ class CarpStudyAppState extends State<CarpStudyApp> {
? '/consent'
: (bloc.user == null ? '/login' : null),
builder: (context, state) =>
InvitationListPage(bloc.data.invitationsListViewModel),
InvitationListPage(bloc.appViewModel.invitationsListViewModel),
),
],
debugLogDiagnostics: true,
Expand Down
4 changes: 2 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import 'package:carp_serializable/carp_serializable.dart';
import 'package:carp_core/carp_core.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';
import 'package:carp_audio_package/media.dart';
//import 'package:carp_health_package/health_package.dart';
import 'package:carp_health_package/health_package.dart';
//import 'package:carp_connectivity_package/connectivity.dart';
//import 'package:carp_communication_package/communication.dart';
import 'package:carp_context_package/carp_context_package.dart';
Expand Down Expand Up @@ -140,6 +140,6 @@ void main() async {
/// Configure the debug level and deployment mode here before running the app
/// or deploying it.
final bloc = StudyAppBLoC(
debugLevel: DebugLevel.info,
debugLevel: DebugLevel.debug,
deploymentMode: DeploymentMode.dev,
);
2 changes: 1 addition & 1 deletion lib/sensing/sensing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Sensing {
//SamplingPackageRegistry.register(CommunicationSamplingPackage());
SamplingPackageRegistry().register(MediaSamplingPackage());
SamplingPackageRegistry().register(SurveySamplingPackage());
//SamplingPackageRegistry.register(HealthSamplingPackage());
SamplingPackageRegistry().register(HealthSamplingPackage());
SamplingPackageRegistry().register(ESenseSamplingPackage());
SamplingPackageRegistry().register(PolarSamplingPackage());

Expand Down
10 changes: 1 addition & 9 deletions lib/ui/cards/distance_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,7 @@ class _DistanceCardState extends State<DistanceCard> {
width: MediaQuery.of(context).size.width * 0.9,
child: StreamBuilder(
stream: widget.model.mobilityEvents,
builder: (context, snapshot) {
if (snapshot.hasData) {
return barCharts;
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
builder: (context, snapshot) => barCharts,
),
),
],
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/cards/heart_rate_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ class HeartRateCardWidgetState extends State<HeartRateCardWidget>
margin: const EdgeInsets.only(left: 8, right: 2),
child: Text(
min == null || max == null
? locale.translate('cards.no_data')
// ? locale.translate('cards.no_data')
? '-'
: '${(min.toInt())} - ${(max.toInt())}',
style: hrVisualisationTextStyle(
fontSize: 40,
Expand Down
Loading

0 comments on commit af38871

Please sign in to comment.